“本篇文章將開始我們訓練自己的物體檢測模型之旅的第一步—— 數據標注。”
上篇文章介紹了如何基於訓練好的模型檢測圖片和視頻中的物體,若你也想先感受一下物體檢測,可以看看上篇文章:《手把手教你用深度學習做物體檢測(一):快速感受物體檢測的酷炫 》。
其實,網上關於數據標注的文章已有很多,但大多數都會有一些細節問題,比如中文編碼問題,比如標注的數據放置的目錄結構不對導致訓練報錯的問題等等,而這些問題,在本篇文章中都考慮到了,所以只要你按照步驟一步步來,並且使用本文中的代碼,將會避免遇到上面所說的問題。
我們已經知道,物體檢測,簡言之就是框出圖像中的目標物體,就像下圖這樣:

然而,能夠識別出該圖中的人、狗、馬的模型是經過了大量數據訓練得到的,這些訓練用的數據,包含了圖片本身,圖片中的待檢測目標的類別和矩形框的坐標等。一般而言,初始的數據都是需要人工來標注的,比如下面這張圖:

我們除了要把圖片本身喂給神經網絡,還要把圖片中的長頸鹿、斑馬的類別以及在圖片中的位置信息一並喂給神經網絡,現在你可能會想,類別信息倒還好,看一眼就知道有哪些類別了,但是目標的位置信息如何得到?難道要用像素尺量么?
其實,已經有很多物體檢測的先驅者們開發出了一些便捷的物體檢測樣本標注工具,這里我們會介紹一個很好用的工具——labelImg,該工具已經在github上開源了,地址:https://github.com/tzutalin/labelImg

該工具對於windows、Linux、Mac操作系統都支持,這里介紹windows和Linux下的安裝方法,Mac下的安裝可以去看項目的README文檔。
- Windows
github上提供了windows下的exe文件,下載下來后直接雙擊運行即可打開labelImg,進行數據的標注,下載鏈接如下:https://github.com/tzutalin/labelImg/files/2638199/windows_v1.8.1.zip - Linux
Linux下的安裝,需要從源碼構建,README文檔中提供了python2 + Qt4和python3+Qt5的構建方法,這里僅介紹后者,在終端中輸入以下命令:
--構建
sudo apt-get install pyqt5-dev-tools
sudo pip3 install -r requirements/requirements-linux-python3.txt
make qt5py3
--打開
python3 labelImg.py
python3 labelImg.py [IMAGE_PATH] [PRE-DEFINED CLASS FILE]
無論是windows還是linux下,都提供了一個預定義的類別文件,data/predefined_classes.txt,其內容如下:

這是方便我們在標注目標類別的時候可以從下拉框中選擇,所以當然也可以修改這個文件,定義好自己要檢測的目標的類別,支持中文。
接下來,我們以windows為例,雙擊labelImage.exe,稍等幾秒鍾,就會看到如下界面:

然后,我們加載一個圖片目錄,第一張圖片會自動打開,此時我們按下 w 鍵,就可以標注目標了,如果發現快捷鍵不能用,可能是目前處在中文輸入法狀態,切換到英文狀態就好了:

標注完成后記得保存操作,然后按下快捷鍵 d,就可以切換到下一張繼續標注。當所有的圖片標注完成后,我們還有一些事情要做,就是按照voc2007的數據集標准將圖片和xml文件放到固定的目錄結構下,具體的結構如下:

接着,我們要將圖片數據集划分成訓練集、驗證集、測試集,可以使用如下python代碼,將該代碼文件和ImageSets目錄放在同一級執行:
"""
將voc_2007格式的數據集划分下訓練集、測試集和驗證集
"""
import os
import random
trainval_percent = 0.96
train_percent = 0.9
xmlfilepath = 'Annotations'
txtsavepath = 'ImageSets\Main'
total_xml = os.listdir(xmlfilepath)
num=len(total_xml)
list=range(num)
tv=int(num*trainval_percent)
tr=int(tv*train_percent)
trainval= random.sample(list,tv)
train=random.sample(trainval,tr)
ftrainval = open('ImageSets/Main/trainval.txt', 'w', encoding="utf-8")
ftest = open('ImageSets/Main/test.txt', 'w', encoding="utf-8")
ftrain = open('ImageSets/Main/train.txt', 'w', encoding="utf-8")
fval = open('ImageSets/Main/val.txt', 'w', encoding="utf-8")
for i in list:
name=total_xml[i][:-4]+'\n'
if i in trainval:
ftrainval.write(name)
if i in train:
ftrain.write(name)
else:
fval.write(name)
else:
ftest.write(name)
ftrainval.close()
ftrain.close()
fval.close()
ftest .close()
執行后,會在ImageSets/Main目錄下生成如下文件:

接下來,可以生成 yolov3 需要的數據格式了,我們使用如下代碼,將代碼文件和VOCdevkit目錄放在同一級執行,注意修改代碼中的classes為你想要檢測的目標類別集合:
import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join
# sets=[('2012', 'train'), ('2012', 'val'), ('2007', 'train'), ('2007', 'val'), ('2007', 'test')]
sets=[('2007', 'train'), ('2007', 'val'), ('2007', 'test')]
# classes = ["aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"]
classes = ["人","狗","鼠標","車"]
def convert(size, box):
dw = 1./(size[0])
dh = 1./(size[1])
x = (box[0] + box[1])/2.0 - 1
y = (box[2] + box[3])/2.0 - 1
w = box[1] - box[0]
h = box[3] - box[2]
x = x*dw
w = w*dw
y = y*dh
h = h*dh
return (x,y,w,h)
def convert_annotation(year, image_id):
in_file = open('VOCdevkit/VOC%s/Annotations/%s.xml'%(year, image_id))
out_file = open('VOCdevkit/VOC%s/labels/%s.txt'%(year, image_id), 'w')
tree=ET.parse(in_file)
root = tree.getroot()
size = root.find('size')
w = int(size.find('width').text)
h = int(size.find('height').text)
for obj in root.iter('object'):
difficult = obj.find('difficult').text
cls = obj.find('name').text
if cls not in classes or int(difficult)==1:
continue
cls_id = classes.index(cls)
xmlbox = obj.find('bndbox')
b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text), float(xmlbox.find('ymax').text))
bb = convert((w,h), b)
out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')
wd = getcwd()
for year, image_set in sets:
if not os.path.exists('VOCdevkit/VOC%s/labels/'%(year)):
os.makedirs('VOCdevkit/VOC%s/labels/'%(year))
image_ids = open('VOCdevkit/VOC%s/ImageSets/Main/%s.txt'%(year, image_set)).read().strip().split()
list_file = open('%s_%s.txt'%(year, image_set), 'w')
for image_id in image_ids:
list_file.write('%s/VOCdevkit/VOC%s/JPEGImages/%s.jpg\n'%(wd, year, image_id))
convert_annotation(year, image_id)
list_file.close()
# os.system("cat 2007_train.txt 2007_val.txt 2012_train.txt 2012_val.txt > train.txt")
# os.system("cat 2007_train.txt 2007_val.txt 2007_test.txt 2012_train.txt 2012_val.txt > train.all.txt")
os.system("cat 2007_train.txt 2007_val.txt > train.txt")
os.system("cat 2007_train.txt 2007_val.txt 2007_test.txt > train.all.txt")
執行后,會在當前目錄生成幾個文件:
2007_train.txt ——訓練集
2007_val.txt ——驗證集
2007_test.txt ——測試集
train.txt —— 訓練集+驗證集
train.all.txt —— 訓練集+驗證集+測試集
我們只需要測試集和訓練集,所以保留train.txt和2007_test.txt,其它文件可以刪除,然后把train.txt重命名為2007_train.txt(不重命名也可以的,只是為了和2007_test.txt名字看起來風格一致),如此我們就有了兩個符合yolov3訓練和測試要求的數據集2007_train.txt和2007_test.txt,注意,這兩個txt中包含的僅僅是圖片的路徑。
除了上面的幾個文件外,我們還會發現在VOCdevkit/VOC2007目錄下生成了一個labels目錄,該目錄下生成了和JPEGImages目錄下每張圖片對應的txt文件,所以如果有500張圖片,就會有500個txt,具體內容如下:

可以看到,每一行代表當前txt所對應的圖片里的一個目標的標注信息,總共有5列,第一列是該目標的類別,第二、三列是目標的歸一化后的中心位置坐標,第四、五列是目標的歸一化后的寬和高。
當我們得到了2007_train.txt、2007_test.txt、labels目錄和其下的txt文件后,數據標注工作就算完成了,那么如何使用這些數據來訓練我們自己的物體檢測模型呢?
既然我們准備的數據是符合yolov3要求的,那么我們當然是基於yolov3算法來使用這些數據訓練出我們自己的模型,具體步驟將會在下一篇《手把手教你用深度學習做物體檢測(三):模型訓練》中介紹。
ok,本篇就這么多內容啦,感謝閱讀O(∩_∩)O,88
名句分享
孩兒立志出鄉関,學不成名誓不還,埋骨何須桑梓地,人生無處不青山。——毛澤東
為您推薦
手把手教你用深度學習做物體檢測(一): 快速感受物體檢測的酷炫
ubuntu16.04安裝Anaconda3
Unbuntu下持續觀察NvidiaGPU的狀態
想看更多好文?長按識別下方二維碼關注滌生吧O(∩_∩)O~

