本文轉(zhuǎn)載自:
http://blog.csdn.net/ch_liu23/article/details/53558549
?最近在用yolo來(lái)做視頻中的人員檢測(cè),選擇YOLO是從速度考慮,當(dāng)然也可以用ssd。YOLO相關(guān)可看主頁(yè)Darknet,有相關(guān)代碼和使用方法。由于之前做自己的數(shù)據(jù)訓(xùn)練過(guò)程中出現(xiàn)各種問(wèn)題,參照了各種博客才跑通,現(xiàn)在記錄下以防后面忘記,也方便自己總結(jié)一下。
????? YOLO本身使用的是VOC的數(shù)據(jù)集,所以可以按照VOC數(shù)據(jù)集的架構(gòu)來(lái)構(gòu)建自己的數(shù)據(jù)集。
1.構(gòu)建VOC數(shù)據(jù)集
1.準(zhǔn)備數(shù)據(jù)
首先準(zhǔn)備好自己的數(shù)據(jù)集,最好固定格式,此處以VOC為例,采用jpg格式的圖像,在名字上最好使用像VOC一樣類似000001.jpg、000002.jpg這樣。可參照下面示例代碼
[cpp] ?view plaincopy
?? ?? ?? #include?"stdafx.h" ?? #include?<stdio.h> ?? #include?<string.h> ?? #include<io.h>?? ?? #include?<opencv2\opencv.hpp> ?? #define?IMGNUM?20000?//圖片所在文件夾中圖片的最大數(shù)量?? ?? char ?img_files[IMGNUM][1000];?? ?? using ? namespace ?cv;?? int ?getFiles( char ?*path)?? {?? ????int ?num_of_img?=?0;?? ????long ???hFile?=?0;?? ????struct ? _finddata_t ?fileinfo;?? ????char ?p[700]?=?{?0?};?? ????strcpy(p,?path);?? ????strcat(p,?"\\*" );?? ????if ?((hFile?=?_findfirst(p,?&fileinfo))?!=?-1)?? ????{?? ????????do ?? ????????{?? ????????????if ?((fileinfo.attrib?&?_A_SUBDIR))?? ????????????{?? ????????????????if ?(strcmp(fileinfo.name,? "." )?!=?0?&&?strcmp(fileinfo.name,? ".." )?!=?0)?? ????????????????????continue ;?? ????????????}?? ????????????else ?? ????????????{?? ????????????????strcpy(img_files[num_of_img],?path);?? ????????????????strcat(img_files[num_of_img],?"\\" );?? ????????????????strcat(img_files[num_of_img++],?fileinfo.name);?? ????????????}?? ????????}?while ?(_findnext(hFile,?&fileinfo)?==?0);?? ????????_findclose(hFile);?? ????}?? ????return ?num_of_img;?? }?? int ?main()?? {?? ????char ?path[]?=? "SrcImage" ;??????????????????????????????? ?? ????char ?dstpath[]?=? "DstImage" ;???????????????????????????? ?? ????int ?num?=?getFiles(path);?? ????int ?i;?? ????char ?order[1000];?? ????FILE ?*fp?=?fopen( "train.txt" ,? "w" );?? ????for ?(i?=?0;?i<num;?++i)?? ????{?? ????????printf("%s\n" ,?img_files[i]);?? ????????IplImage?*pSrc?=?cvLoadImage(img_files[i]);?? ????????sprintf(order,?"DstImage\\%05d.jpg" ,?i);?? ????????fprintf(fp,?"%05d\n" ,?i);?? ????????cvSaveImage(order,?pSrc);?? ????????printf("Saving?%s!\n" ,?order);?? ????????cvReleaseImage(&pSrc);?? ????}?? ????fclose(fp);?? ????return ?0;?? }?? 讀取某文件夾下的所有圖像然后統(tǒng)一命名,用了opencv所以順便還可以改格式。
準(zhǔn)備好了自己的圖像后,需要按VOC數(shù)據(jù)集的結(jié)構(gòu)放置圖像文件。VOC的結(jié)構(gòu)如下
[plain] ?view plaincopy
--VOC?? ????--Annotations?? ????--ImageSets?? ??????--Main?? ??????--Layout?? ??????--Segmentation?? ????--JPEGImages?? ????--SegmentationClass?? ????--SegmentationObject??
????? 這里面用到的文件夾是
Annotation 、ImageSets和JPEGImages。其中文件夾
Annotation 中主要存放xml文件,每一個(gè)xml對(duì)應(yīng)一張圖像,并且每個(gè)xml中存放的是標(biāo)記的各個(gè)目標(biāo)的位置和類別信息,命名通常與對(duì)應(yīng)的原始圖像一樣;而ImageSets我們只需要用到Main文件夾,這里面存放的是一些文本文件,通常為train.txt、test.txt等,該文本文件里面的內(nèi)容是需要用來(lái)訓(xùn)練或測(cè)試的圖像的名字(無(wú)后綴無(wú)路徑);JPEGImages文件夾中放我們已按統(tǒng)一規(guī)則命名好的原始圖像。
????? 因此,首先
????? 1.新建文件夾VOC2007(通常命名為這個(gè),也可以用其他命名,但一定是名字+年份,例如MYDATA2016,無(wú)論叫什么后面都需要改相關(guān)代碼匹配這里,本例中以
VOC2007為例)
????? 2.在VOC2007文件夾下新建三個(gè)文件夾
Annotation 、ImageSets和JPEGImages,并把準(zhǔn)備好的自己的原始圖像放在JPEGImages文件夾下
????? 3.在ImageSets文件夾中,新建三個(gè)空文件夾Layout、Main、Segmentation,然后把寫了訓(xùn)練或測(cè)試的圖像的名字的文本拷到Main文件夾下,按目的命名,我這里所
有圖像用來(lái)訓(xùn)練,故而Main文件夾下只有train.txt文件。上面說(shuō)的小代碼運(yùn)行后會(huì)生成該文件,把它拷進(jìn)去即可。
2.標(biāo)記圖像目標(biāo)區(qū)域
?????? 因?yàn)樽龅氖悄繕?biāo)檢測(cè),所以接下來(lái)需要標(biāo)記原始圖像中的目標(biāo)區(qū)域。相關(guān)方法和工具有很多,這里需用labelImg,相關(guān)用法也有說(shuō)明,基本就是框住目標(biāo)區(qū)域然后雙擊類別,標(biāo)記完整張圖像后點(diǎn)擊保存即可。操作界面如下:
通常save之后會(huì)將標(biāo)記的信息保存在xml文件,其名字通常與對(duì)應(yīng)的原始圖像一樣。最后生成的畫風(fēng)是這樣的
其中每個(gè)xml文件是這樣的畫風(fēng)
[html] ?view plaincopy
<? xml ? version = "1.0" ? ?> ?? < annotation > ?? ????< folder > JPEGImages </ folder > ?? ????< filename > 00000 </ filename > ?? ????< path > /home/kinglch/VOC2007/JPEGImages/00000.jpg </ path > ?? ????< source > ?? ????????< database > Unknown </ database > ?? ????</ source > ?? ????< size > ?? ????????< width > 704 </ width > ?? ????????< height > 576 </ height > ?? ????????< depth > 3 </ depth > ?? ????</ size > ?? ????< segmented > 0 </ segmented > ?? ????< object > ?? ????????< name > person </ name > ?? ????????< pose > Unspecified </ pose > ?? ????????< truncated > 0 </ truncated > ?? ????????< difficult > 0 </ difficult > ?? ????????< bndbox > ?? ????????????< xmin > 73 </ xmin > ?? ????????????< ymin > 139 </ ymin > ?? ????????????< xmax > 142 </ xmax > ?? ????????????< ymax > 247 </ ymax > ?? ????????</ bndbox > ?? ????</ object > ?? ????< object > ?? ????????< name > person </ name > ?? ????????< pose > Unspecified </ pose > ?? ????????< truncated > 0 </ truncated > ?? ????????< difficult > 0 </ difficult > ?? ????????< bndbox > ?? ????????????< xmin > 180 </ xmin > ?? ????????????< ymin > 65 </ ymin > ?? ????????????< xmax > 209 </ xmax > ?? ????????????< ymax > 151 </ ymax > ?? ????????</ bndbox > ?? ????</ object > ?? ????< object > ?? ????????< name > person </ name > ?? ????????< pose > Unspecified </ pose > ?? ????????< truncated > 0 </ truncated > ?? ????????< difficult > 0 </ difficult > ?? ????????< bndbox > ?? ????????????< xmin > 152 </ xmin > ?? ????????????< ymin > 70 </ ymin > ?? ????????????< xmax > 181 </ xmax > ?? ????????????< ymax > 144 </ ymax > ?? ????????</ bndbox > ?? ????</ object > ?? </ annotation > ?? 注意filename中文件的文件名名沒(méi)有后綴,因此需要統(tǒng)一加上后綴。只需一段命令即可
[plain] ?view plaincopy
find?-name?'*.xml'?|xargs?perl?-pi?-e?'s|</filename>|.jpg</filename>|g'?? 在對(duì)應(yīng)目錄下執(zhí)行即可,這樣就可以把后綴添上。這樣就做按照VOC做好了我們的數(shù)據(jù)集,接下來(lái)就是放到算法中去訓(xùn)練跑起來(lái)。
2.用YOLOv2訓(xùn)練
1.生成相關(guān)文件
??? 按darknet的說(shuō)明編譯好后,接下來(lái)在darknet-master/scripts文件夾中新建文件夾VOCdevkit,然后將整個(gè)VOC2007文件夾都拷到VOCdevkit文件夾下。
??? 然后,需要利用scripts文件夾中的voc_label.py文件生成一系列訓(xùn)練文件和label,具體操作如下:
??? 首先需要修改voc_label.py中的代碼,這里主要修改數(shù)據(jù)集名,以及類別信息,我的是VOC2007,并且所有樣本用來(lái)訓(xùn)練,沒(méi)有val或test,并且只檢測(cè)人,故只有一類
目標(biāo),因此按如下設(shè)置
[python] ?view plaincopy
import ?xml.etree.ElementTree?as?ET?? import ?pickle?? import ?os?? from ?os? import ?listdir,?getcwd?? from ?os.path? import ?join?? ?? ?? ?? ?? ?? sets=[('2007' ,? 'train' )]?? classes?=?[?"person" ]?? ?? ?? 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)?? ?? 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()??
修改好后在該目錄下運(yùn)行命令:python voc_label.py,之后則在文件夾scripts\VOCdevkit\VOC2007下生成了文件夾lable,該文件夾下的畫風(fēng)是這樣的
這里包含了類別和對(duì)應(yīng)歸一化后的位置(i guess,如有錯(cuò)請(qǐng)指正)。同時(shí)在scripts\下應(yīng)該也生成了train_2007.txt這個(gè)文件,里面包含了所有訓(xùn)練樣本的絕對(duì)路徑。
2.配置文件修改 ????? 做好了上述準(zhǔn)備,就可以根據(jù)不同的網(wǎng)絡(luò)設(shè)置(cfg文件)來(lái)訓(xùn)練了。在文件夾cfg中有很多cfg文件,應(yīng)該跟caffe中的prototxt文件是一個(gè)意思。這里以tiny-yolo-voc.cfg為例,該網(wǎng)絡(luò)是yolo-voc的簡(jiǎn)版,相對(duì)速度會(huì)快些。主要修改參數(shù)如下
[plain] ?view plaincopy
.?? .?? .?? [convolutional]?? size=1?? stride=1?? pad=1?? filters=30??//修改最后一層卷積層核參數(shù)個(gè)數(shù),計(jì)算公式是依舊自己數(shù)據(jù)的類別數(shù)filter=num×(classes?+?coords?+?1)=5×(1+4+1)=30?? activation=linear?? ?? [region]?? anchors?=?1.08,1.19,??3.42,4.41,??6.63,11.38,??9.42,5.11,??16.62,10.52?? bias_match=1?? classes=1??//類別數(shù),本例為1類?? coords=4?? num=5?? softmax=1?? jitter=.2?? rescore=1?? ?? object_scale=5?? noobject_scale=1?? class_scale=1?? coord_scale=1?? ?? absolute=1?? thresh?=?.6?? random=1?? 另外也可根據(jù)需要修改learning_rate、max_batches等參數(shù)。這里歪個(gè)樓吐槽一下其他網(wǎng)絡(luò)配置,一開(kāi)始是想用tiny.cfg來(lái)訓(xùn)練的官網(wǎng)作者說(shuō)它夠小也夠快,但是它的網(wǎng)絡(luò)配置最后幾層是這樣的畫風(fēng):
[html] ?view plaincopy
[convolutional]?? filters = 1000 ?? size = 1 ?? stride = 1 ?? pad = 1 ?? activation = linear ?? ?? [avgpool]?? ?? [softmax]?? groups = 1 ?? ?? [cost]?? type = sse ?? 這里沒(méi)有類別數(shù),完全不知道怎么修改,強(qiáng)行把最后一層卷積層卷積核個(gè)數(shù)修改又跑不通會(huì)出錯(cuò),如有大神知道還望賜教。
????? Back to the point。修改好了cfg文件之后,就需要修改兩個(gè)文件,首先是data文件下的voc.names。打開(kāi)voc.names文件可以看到有20類的名稱,本例中只有一類,檢測(cè)人,因此將原來(lái)所有內(nèi)容清空,僅寫上person并保存。名字仍然用這個(gè)名字,如果喜歡用其他名字則請(qǐng)按一開(kāi)始制作自己數(shù)據(jù)集的時(shí)候的名字來(lái)修改。
????? 接著需要修改cfg文件夾中的voc.data文件。也是按自己需求修改,我的修改之后是這樣的畫風(fēng):
[plain] ?view plaincopy
classes=?1??//類別數(shù)?? train??=?/home/kinglch/darknet-master/scripts/2007_train.txt??//訓(xùn)練樣本的絕對(duì)路徑文件,也就是上文2.1中最后生成的?? //valid??=?/home/pjreddie/data/voc/2007_test.txt??//本例未用到?? names?=?data/voc.names??//上一步修改的voc.names文件?? backup?=?/home/kinglch/darknet-master/results/??//指示訓(xùn)練后生成的權(quán)重放在哪?? 修改后按原名保存最好,接下來(lái)就可以訓(xùn)練了。
ps:yolo v1中這些細(xì)節(jié)是直接在源代碼的yolo.c中修改的,源代碼如下
比如這里的類別,訓(xùn)練樣本的路徑文件和模型保存路徑均在此指定,修改后從新編譯。而yolov2似乎擯棄了這種做法,所以訓(xùn)練的命令也與v1版本的不一樣。
3.運(yùn)行訓(xùn)練 ????? 上面完成了就可以命令訓(xùn)練了,可以在官網(wǎng)上找到一些預(yù)訓(xùn)練的模型作為參數(shù)初始值,也可以直接訓(xùn)練,訓(xùn)練命令為
[plain] ?view plaincopy
$./darknet?detector?train?./cfg/voc.data?cfg/tiny-yolo-voc.cfg?? 如果用官網(wǎng)的預(yù)訓(xùn)練模型darknet.conv.weights做初始化,則訓(xùn)練命令為
[plain] ?view plaincopy
$./darknet?detector?train?./cfg/voc.data?.cfg/tiny-yolo-voc.cfg?darknet.conv.weights?? 不過(guò)我沒(méi)試成功,加上這個(gè)模型直接就除了final,不知道啥情況。當(dāng)然也可以用自己訓(xùn)練的模型做參數(shù)初始化,萬(wàn)一訓(xùn)練的時(shí)候被人終端了,可以再用訓(xùn)練好的模型接上去接著訓(xùn)練。
????? 訓(xùn)練過(guò)程中會(huì)根據(jù)迭代次數(shù)保存訓(xùn)練的權(quán)重模型,然后就可以拿來(lái)測(cè)試了,測(cè)試的命令同理:
. /darknet detector test
cfg/voc
. data cfg/tiny-yolo
- voc. cfg results/tiny-yolo
-voc_6000 . weights data/images.jpg
????? 這樣就完成了整個(gè)流程,目前測(cè)試感覺(jué)同種網(wǎng)絡(luò)v2版本似乎比v1版本慢很多,莫非是為了精度的提高犧牲了部分速度。然而我需要的是速度,這個(gè)就尷尬了。
????? 初學(xué)yolo,如有問(wèn)題歡迎加我q675143196討論交流.
總結(jié)
以上是生活随笔 為你收集整理的YOLOv2训练:制作VOC格式的数据集 的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔 網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔 推薦給好友。