本文參考了:dspeia的製作教程,Rani_zZ的製作教程,Darknet官網中的YOLO教程。
首先對YOLO進行簡要說明。YOLO是一個one-stage網絡,其直接回歸所檢測目標的邊框參數(左上點x,y,寬高width,height),置信度值以及屬於各類的概率。YOLOv3將整張圖片切割成了7x7塊,並在每一塊中都檢測三個目標,也即每一塊中所迴歸出的結果應當是3x(5+類的數量)。
1.製作自己的數據集
目前,網上製作數據集的教程幾乎都用的是labelImg,其標註結果的格式與VOC數據集完全一致,因此可以直接使用YOLO作者製作的數據讀取文件。具體過程不再贅述。
在標註完成後,將製作好的數據集放到你的數據集文件夾內(我的是/home/xxx/dataset/VOC2007),其文件夾構成如圖:
其中,JPEGImages裏是你的所有圖片,Annotation裏是與你的圖片一一對應的XML標註文件(如果某個圖不包含物體,請在標註時隨便圈一下再刪除,隨後保存,否則不會生成XML,會報錯!)
接下來,在Main裏創建一個訓練與測試列表。在VOC2007文件夾內創建一個split.py,將下列代碼複製進去:
import os
import random
trainval_percent = 0.1
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')
ftest = open('ImageSets/Main/test.txt', 'w')
ftrain = open('ImageSets/Main/train.txt', 'w')
fval = open('ImageSets/Main/val.txt', 'w')
for i in list:
name = total_xml[i][:-4] + '\n'
if i in trainval:
ftrainval.write(name)
if i in train:
ftest.write(name)
else:
fval.write(name)
else:
ftrain.write(name)
ftrainval.close()
ftrain.close()
fval.close()
ftest.close()
接着,命令行執行該文件
$ python split.py
執行後你應該能在Main裏看到4個文件,裏面分別存儲了被作爲四類數據的文件的名稱。
2.下載並編譯Darknet和YOLO
按照官網的教程,下載並編譯測試Darknet,同時測試CUDA,cuDNN和OpenCV。CUDA和cuDNN安裝與OpenCV安裝見我之前的博客。
接着測試YOLO,同樣跟隨官網教程。
3.將數據集鏈接至Darknet中。
網上的一些教程將數據直接放在Darknet文件夾中,但我個人覺得這個方法很不適合後續維護。我的習慣是將數據都放在一個文件夾內方便查找,因此可以建立軟鏈接實現這一過程。
在darknet文件夾下新建一個文件夾:
mkdir VOCdevkit
創建從你的數據文件夾到該文件夾的軟鏈接:
ln -s /path/to/your/dataset/fold/VOC2007/ /path/to/your/darknet/fold/VOCdevkit/VOC2007
這樣就可以方便的訪問數據了。
4.轉換數據格式
labelImg使用的是VOC的數據格式,而YOLO則需要使用txt數據。因此,需要對數據格式進行轉換。
在darknet/scripts文件夾下有一個文件voc_label就是幹這個用的。
將該文件複製至darknet文件夾內,然後修改該文件,將上面部分修改的適配你的數據集,比如我只需要檢測car,則改爲:
sets=[('2007', 'train'), ('2007', 'val'), ('2007', 'test')]
classes = ["car"]
文件最下面兩行可以刪除:
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")
然後執行該文件
$ python voc_label.py
你應該會看到文件夾裏生成了幾個txt,而打開數據集會看到label文件夾,裏面存放了符合YOLO格式的訓練數據。例如:
0 0.11328125 0.574305555556 0.034375 0.0263888888889
0 0.154296875 0.611111111111 0.02734375 0.025
0 0.173828125 0.645833333333 0.02734375 0.025
0 0.235546875 0.643055555556 0.02734375 0.025
0 0.300390625 0.60625 0.03046875 0.0208333333333
0 0.237109375 0.671527777778 0.03046875 0.0208333333333
0 0.283203125 0.638194444444 0.03046875 0.0208333333333
0 0.276171875 0.675694444444 0.03046875 0.0208333333333
0 0.344140625 0.610416666667 0.03046875 0.0208333333333
0 0.362109375 0.574305555556 0.03046875 0.0208333333333
0 0.344921875 0.678472222222 0.03046875 0.0208333333333
0 0.50859375 0.121527777778 0.0171875 0.0597222222222
0 0.46875 0.0291666666667 0.0171875 0.0583333333333
0 0.517578125 0.251388888889 0.01484375 0.05
0 0.53515625 0.385416666667 0.0234375 0.0569444444444
0 0.6328125 0.288194444444 0.028125 0.0597222222222
0 0.5890625 0.44375 0.028125 0.0597222222222
0 0.590625 0.527083333333 0.028125 0.0597222222222
0 0.63125 0.495138888889 0.028125 0.0597222222222
0 0.7171875 0.450694444444 0.0296875 0.0291666666667
0 0.763671875 0.459027777778 0.03359375 0.0291666666667
0 0.717578125 0.490972222222 0.03359375 0.0291666666667
0 0.669140625 0.605555555556 0.03359375 0.0416666666667
0 0.669140625 0.605555555556 0.03359375 0.0416666666667
0 0.698046875 0.56875 0.06484375 0.0819444444444
0 0.8125 0.624305555556 0.075 0.0541666666667
0 0.941015625 0.643055555556 0.07265625 0.0444444444444
0 0.76484375 0.675 0.0328125 0.0361111111111
0 0.907421875 0.678472222222 0.03359375 0.0319444444444
0 0.99140625 0.588888888889 0.015625 0.0222222222222
這裏第一個數據是類的序號,因爲我只檢測car所以只有0.第2-5項對應邊框的四個值,這裏都已經基於邊框的總寬高進行了歸一化處理。
5.準備訓練
下載ImageNet上的預訓練權重:
wget https://pjreddie.com/media/files/darknet53.conv.74
修改data/voc.name(記得備份),裏面放你的所有類的名稱(回車分隔,最後一行無回車)。
car
修改cfg/voc.data(記得備份)
classes= 1 #classes爲訓練樣本集的類別總數,我的算法只檢測車,因此是1
train = /darknet/2007_train.txt #train的路徑爲訓練樣本集所在的路徑,上一步中生成
valid = /darknet/2007_val.txt #valid的路徑爲驗證樣本集所在的路徑,上一步中生成
names = data/voc.names #names的路徑爲data/voc.names文件所在的路徑
backup = backup
修改cfg / yolov3-voc.cfg(記得備份)最上面部分,註釋掉test對應部分,使用train部分。這裏subdivisions是指將數據分爲多少份輸入,例如16則是每次輸入4張圖同時訓練,最後64張圖的所有結果視爲一個batch,共同優化。這裏需要根據GPU顯存自行調整。
[net]
# Testing
# batch=1
# subdivisions=1
# Training
batch=64
subdivisions=16
......
[convolutional]
size=1
stride=1
pad=1
filters=18 #---------------修改爲3*(5+classes)即3*(5+1)=18
activation=linear
[yolo]
mask = 6,7,8
anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326
classes=1 #---------------修改爲標籤類別個數,1類
num=9
jitter=.3
ignore_thresh = .5
truth_thresh = 1
random=0 #1,如果顯存很小,將random設置爲0,關閉多尺度訓練;(轉自別的blog,還不太明白)
......
[convolutional]
size=1
stride=1
pad=1
filters=18 #---------------修改同上
activation=linear
[yolo]
mask = 3,4,5
anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326
classes=1 #---------------修改同上
num=9
jitter=.3
ignore_thresh = .5
truth_thresh = 1
random=0
......
[convolutional]
size=1
stride=1
pad=1
filters=18 #---------------修改同上
activation=linear
[yolo]
mask = 0,1,2
anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326
classes=1 #---------------修改同上
num=9
jitter=.3
ignore_thresh = .5
truth_thresh = 1
random=0
6.開始訓練
執行下面的代碼進行訓練,其中-gpus是多顯卡執行的命令,若只有單顯卡可不加。
./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg scripts/darknet53.conv.74 -gpus 0,1
訓練過程中的執行結果應當如下:
……
Loaded: 0.000050 seconds
Region 82 Avg IOU: -nan, Class: -nan, Obj: -nan, No Obj: 0.005261, .5R: -nan, .75R: -nan, count: 0
Region 94 Avg IOU: -nan, Class: -nan, Obj: -nan, No Obj: 0.002821, .5R: -nan, .75R: -nan, count: 0
Region 106 Avg IOU: 0.306019, Class: 0.971707, Obj: 0.377151, No Obj: 0.003877, .5R: 0.285714, .75R: 0.000000, count: 14
299: 18.237528, 28.675016 avg, 0.000008 rate, 0.065416 seconds, 299 images
……
這裏只要有一個Region的IOU有顯示就是正確的。隨着訓練過程進行,IOU應當會越來越高,例如對於我的任務最終都在75%左右。訓練好的模型會被存儲在darknet/backup中,名字如yolov3-voc_900.weights,這裏的數字是執行epoch的數目。YOLO默認最多隻會存儲到900,後面雖然程序沒停,但這個backup不會再增加。所以不要以程序停止作爲訓練結束的標誌。
訓練過程中可以查看GPU顯存佔用情況。nvidia-smi可以,但是隻能查看一次。如果需要一直監控可執行
watch -n 0.1 nvidia-smi
其中-n後面的數字是多少秒刷新一次。
中間部分2727MiB/11777MiB就是你當前使用的gpu顯存和總顯存數。如果發現gpu不太滿,可以減小上面subdivision數字的大小,從而提高訓練速度。