一、前言
本文主要使用yolo v2 訓練自己的車牌圖片數據,並能夠框出測試圖片中存在的車牌區域,也即車牌檢測。本文參考了博文http://m.blog.csdn.net/qq_34484472/article/details/73135354和http://blog.csdn.net/zhuiqiuk/article/details/72722227
二、準備工作
首先需要下載正確配置好darknet, 使用./darknet detect cfg/yolo.cfg yolo.weights data/dog.jpg 命令可得檢測結果。本文主要是爲了檢測車牌區域,在darknet下新建一文件夾plate_imgae,其中存放train和val、pic三個文件夾,train文件夾下爲1296張訓練圖片,val文件夾下爲196張驗證圖片,pic爲236張待檢測的圖片,圖片格式均爲jpg格式,訓練模型主要用到train和val這兩個文件夾。
三、製作標籤文件
yolo v2使用VOC格式的數據集,故這裏首先需要對訓練集及驗證集的圖片進行標註,每張圖片均可得到相應的xml文件,然後再將xml文件轉化爲txt標籤文件。參考這裏http://blog.csdn.net/jesse_mx/article/details/53606897 安裝標註工具LabelImg,安裝完成後,./labelImg.py打開標註工具,先對train文件夾內的圖片作標註,在plate_image文件夾下新建train_xml文件夾,用於存放生成的xml文件。所生成的xml文件如下所示:
同樣的方法,也在在plate_image文件夾下新建val_xml文件夾,保存驗證集的xml標註文件。這樣就有VOC格式標記的XML文件,接下來需要將xml文件轉換爲txt文件,使用python轉換,將train_xml中的xml文件,轉換爲txt保存於文件夾train_txt下;同理,將val_xml中的xml文件轉換後保存於val_txt文件夾下
import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join
xml_label_Dir = 'train_xml' #需轉換的xml路徑
txt_label_Dir = 'train_txt/' #轉換得的txt文件保存路徑
def convert(size, box): #歸一化操作
dw = 1./size[0]
dh = 1./size[1]
x = (box[0] + box[1])/2.0
y = (box[2] + box[3])/2.0
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)
if not os.path.exists(txt_label_Dir):
os.makedirs(txt_label_Dir)
for rootDir,dirs,files in os.walk(xml_label_Dir):
for file in files:
file_name = file.split('.')[0]
out_file = open(txt_label_Dir+'%s.txt'%(file_name),'w')
in_file = open("%s/%s"%(rootDir,file))
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'):
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("0" + " " + " ".join([str(a) for a in bb]) + '\n') #only one class,index 0
out_file.close()
接着還需要獲得訓練集圖片的絕對路徑train_imgae_path.txt和驗證集的絕對路徑val_image_path.txt,同樣使用以下python代碼import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join
TrainDir = '/home/jyang/darknet/plate_image/train'
out_file = open('train_image_path.txt','w')
for root,dirs,files in os.walk(TrainDir):
for file in files:
out_file.write('%s/%s\n'%(root,file))
out_file.close()
YOLO是直接通過替換原圖片絕對路徑的後綴名來找到對應標記文件的。比如原圖片的絕對路徑爲/home/ubuntu/data/train/1.jpg。則YOLO將會直接認爲其對應的標記文件路徑爲/home/ubuntu/data/train/1.txt。所以,我們將之前生成的txt文件都放到相應的圖片路徑下,即將train_txt下txt文件放到train文件夾下,將val_txt下txt文件放到val文件夾下看看plate_image下都有啥。
四、修改配置文件
(1)創建names文件
在YOLO主目錄的data文件夾下,創建一個.names文件,文件名任意,我的是myClass.names,在該文件中寫入所有類別的名稱,每一類佔一行,我只檢測車牌,故只在第一行寫licence.
(2)修改data文件
修改darknet主目錄下的 cfg/voc.data文件,修改如下:
classes= 1 #只有一個類別
train = /home/jyang/darknet/plate_image/train_image_path.txt #訓練集的絕對路徑
valid = /home/jyang/darknet/plate_image/val_image_path.txt #驗證集的絕對路徑
names = data/myClass.names
backup = /home/jyang/darknet/plate_image/backup #訓練得的模型保存路徑
(3)修改cfg文件
修改darknet主目錄下的cfg/yolo-voc.cfg。
1.[region]層中classes改爲1
2.[region]層上方的[convolution]層中,filters的數量改成(classes+coords+1)*NUM。我這裏改成了(1+4+1)*5=30.
五、 修改src/yolo.c文件
1.第13行改成 char *voc_names[] = {"licence"}; //如果你是一類的話,就改成這樣,如果你有多類,自己根據需求改。
2.第322行draw_detections函數中,最後一個參數由20改成你的類別數,我這裏是1
3.第354行demo函數中,倒數第三個參數由20改成你的類別數,我這裏是1
4.第17行改成 char *train_images = "<你的路徑>/train_imgae_path";
5.第18行改成 char *backup_directory = "你的路徑/backup/";
6.第121行改成 char *base = "results/comp4_det_test_"
7.第123行改成 list *plist = get_paths("<你的路徑>/val_image_path.txt");
8.第209行改成 char *base = "results/comp4_det_test_"
9.第210行改成 list *plist = get_paths("<你的路徑>/val_image_path.txt");
10.將src/yolo_kernels.cu文件中,第62行draw_detections函數最後一個參數由20改成你的類別數,我這裏是1.
11.scripts/voc_label.py 文件中 ,位置第9行改成:classes=[“licence”],因爲我只有一類
12.將src/detector.c文件中,第372行改成 list *plist = get_paths("<你的路徑>/val_image_path.txt");,第542行option_find_int函數的最後一個參數由20改成1.
六、重新編譯darknet yolo
進入darknet主目錄,make clean後 make -j8
七、下載預訓練文件cfg/darknet19_448.conv.23
爲了加快訓練速度,下載官方提供的預訓練模型,保存至cfg下。下載地址爲
http://pjreddie.com/media/files/darknet19_448.conv.23
八、訓練
在darknet文件夾路徑下運行命令:
./darknet detector train cfg/voc.data cfg/yolo-voc.cfg cfg/darknet19_448.conv.23
系統默認會迭代45000次batch,如果需要修改訓練次數,進入cfg/yolo_voc.cfg修改max_batches的值。
九、訓練過程輸出log
參考自這篇文章http://blog.csdn.net/renhanchi/article/details/71077830?locationNum=13&fps=1,以下摘自這篇文章
Region Avg IOU: 這個是預測出的bbox和實際標註的bbox的交集 除以 他們的並集。顯然,這個數值越大,說明預測的結果越好
Avg Recall: 這個表示平均召回率, 意思是 檢測出物體的個數 除以 標註的所有物體個數。
count: 標註的所有物體的個數。 如果 count = 6, recall = 0.66667, 就是表示一共有6個物體(可能包含不同類別,這個不管類別),然後我預測出來了4個,所以Recall 就是 4 除以 6 = 0.66667
有一行跟上面不一樣的,最開始的是iteration次數,然後是train loss,然後是avg train loss, 然後是學習率, 然後是一batch的處理時間, 然後是已經一共處理了多少張圖片。 重點關注 train loss 和avg train loss,這兩個值應該是隨着iteration增加而逐漸降低的。如果loss增大到幾百那就是訓練發散了,如果loss在一段時間不變,就需要降低learning rate或者改變batch來加強學習效果。當然也可能是訓練已經充分。這個需要自己判斷
十、評估訓練得的模型
訓練了3天,得到了迭代30000次的權值,在plate_image/backup/下生成了yolo-voc_30000.weights,當然還有其他迭代次數的權值,使用以下語句可以測試單張圖片
./darknet detector test cfg/voc.data cfg/yolo-voc.cfg plate_image/backup/yolo-voc_30000.weights plate_image/pic/099999.jpg
顯示的檢測結果爲
訓練的時候只是用到了訓練集,評估檢測性能纔會使用到驗證集。使用以下指令評估
root@computer:/home/jyang/darknet# ./darknet detector recall cfg/voc.data cfg/yolo-voc.cfg plate_image/backup/yolo-voc_30000.weights
根據參考的第2篇博文,使用該指令的時候,會調用src/detector.c裏的validate_detector_recall函數,將其中的閾值從0.001修改爲0.25,防止過多框被識別出,將432行代碼修改爲如下:
fprintf(stderr, "Id:%5d Correct:%5d Total%5d\tRPs/Img: %.2f\tIOU: %.2f\tRecall:%.2f%%\tPrecision:%.2f%%\n", i,
correct, total, (float)proposals/(i+1), avg_iou*100/total, 100.*correct/total ,100.*correct/proposals);
重新make之後,運行結果爲:可見196張圖片,準確率有97%以上。
結語
本文只是使用了yolo v2 檢測了車牌,後續對框下來的車牌圖片作識別,請看《yolo v2之車牌檢測後續識別字符(一)》