YOLOv3 訓練自己的數據附優化與問題總結
YOLOv3 訓練自己的數據附優化與問題總結
環境說明
系統:ubuntu16.04
顯卡:Tesla k80 12G顯存
python環境: 2.7 && 3.6
前提條件:cuda9.0 cudnn7.0 opencv3.4.0
安裝cuda和cudnn教程
安裝opencv3.4.0教程
實現YOLOV3的demo
首先安裝darknet框架,官網鏈接
git clone https://github.com/pjreddie/darknet.git cd darknet vim Makefile
根據情況修改Makefile,如果使用GPU、cudnn和opencv,就將其標誌位改成1。
編譯
make
編譯完成,測試一下
./darknet
應該會出現以下信息
usage: ./darknet <function>
如果有使用opencv,則可以測試下檢測用例,會出現很多老鷹的圖片說明darknet的安裝odk了
./darknet imtest data/eagle.jpg
使用yolov3預訓練模型檢測物體
首先獲取模型權重
wget https://pjreddie.com/media/files/yolov3.weights
然後運行目標檢測
./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg
這邊運行的時候很可能出現的一個問題就是,沒有出現bbox
原因
如果GPU = 1或CUDNN = 1,darknet將為每個層預先分配GPU虛擬內存,這取決於cfg文件中的批量大小和子分區設置。
對於訓練,批量大小表示將在叠代中對GPU執行多少圖片,更大的值可以減少訓練時間,並且如果GPU在叠代中沒有足夠的內存,則子劃分可以將它們分組以防止存儲器大小限制問題。
保持1檢測,因為這些設置主要用於訓練網絡。參考:
https://github.com/pjreddie/darknet/issues/405
https://github.com/pjreddie/darknet/issues/405
解決方法:
(1)檢測的時候令cudnn=0,
(2)修改cfg文件成batch=1,sub=1
命令簡介
先舉個例子
./darknet detector test cfg/coco.data cfg/yolov3.cfg yolov3.weights data/dog.jpg
darknet :一個可執行的程序,類似win下的exe
detector:是一個第一個參數,執行detector.c
test:detector.c裏面的一個函數test_detector(),用來測試圖片
cfg/coco.data:"cfg/"是訓練的配置文件所在路徑,coco.data是.data配置文件名
cfg/yolov3.cfg:"cfg/"是訓練的配置文件所在路徑,yolov3.cfg是.cfg配置文件名
yolov3.weights:訓練好的模型,yolov3.weights在darknet根目錄
data/dog.jpg:“data/”是要測試圖片所在路徑,dog.jpg是測試圖片文件名
測試
測試圖片
指令格式
./darknet detector test <data_cfg> <test_cfg> <weights> <image_file>(可選)
如果沒有指定
就可以像這樣測試多張圖片
文件中batch和subdivisions兩項須為1或者測試時不使用cudnn 測試時還可以用-thresh調整置信度閾值 ,例如只顯示置信度在60%以上的 bbox
./darknet detector test cfg/coco.data cfg/yolov3.cfg yolov3.weights data/dog.jpg -thresh .6
檢測視頻,需要用到demo函數(src/demo.c)
./darknet detector demo cfg/coco.data cfg/yolov3.cfg yolov3.weights test.avi
在攝像頭上測試,需要用到demo函數
./darknet detector demo cfg/coco.data cfg/yolov3.cfg yolov3.weights
訓練模型
指令格式
./darknet -i <gpu_id> detector train <data_cfg> <train_cfg> <weights>
單GPU訓練
./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg darknet53.conv.74
更換GPU訓練
./darknet detector -i 2 train cfg/voc.data cfg/yolov3-voc.cfg darknet53.conv.74
多GPU訓練
./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg darknet53.conv.74 -gpus 0,1,2,3
CPU訓練
./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg darknet53.conv.74 --nogpu
模型評估recall,生成測試結果
生成測試結果
./darknet detector valid <data_cfg> <test_cfg> <weights> <out_file>
(1)
文件中batch和subdivisions兩項需為1。
(2)測試結果生成在的results指定的目錄下以 開頭的若幹文件中,若 沒有指定results,那麽默認為darknet/results。 計算recall(需要修改detectocr.c請參考第五部分優化與個性化——計算map與recall)
/darknet detector recall <data_cfg> <test_cfg> <weights>
(1)
文件中batch和subdivisions兩項需為1
(2)RPs/Img、IOU、Recall都是到當前測試圖片的均值(意義待續)
訓練自己的數據
首先下載預訓練卷積權重
wget https://pjreddie.com/media/files/darknet53.conv.74
其次標註數據集,生成yolo所需要的txt格式的labels
推薦使用yolo_mark數據集標註工具,可以直接生成txt
下載yolo_mark
git clone https://github.com/AlexeyAB/Yolo_mark.git
要在Linux上編譯,輸入:
cd Yolo_mark cmake . make chmod +x linux_mark.sh ./linux_mark.sh
打開yolo_mark就是GUI的標註了,主要是修改data和names,具體請參考官網
修改配置文件
修改.data文件
classes= 4 (修改成自己訓練的種類數) train = /home/user_name/darknet/data/train.txt (修改成自己train.txt的路徑) valid = /home/user_name/darknet/data/2007_test.txt (評估測試的圖片的路徑,用於後面的評估測試) names = data/voc.names (修改成自己的類別名的路徑) backup = backup (訓練的權重所存放的路徑) results = results (評估測試結果存放路徑,也可以自己定義)
修改.names這個比較簡單,修改自己的類別就可以
例如我的文件是yolov3.names
修改.cfg文件
關於cfg修改,以4類目標檢測為例,主要有以下幾處調整(藍色標出),#表示註釋,根據訓練和測試,自行修改,修改說明:
A:filters的計算和聚類數目分布有關
yolov3:filters=(classes + 5)x3
yolov2: filters=(classes + 5)x5。
B.classes就是類別數量
C.如果想修改默認anchors數值,可以使用k-means;
D.如果顯存夠大,則可以開啟多尺度訓練令random=1,不過loss會有點震蕩;
給出一份我的cfg以供參考,傳送門
[net]
#Testing
#batch=1
#subdivisions=1
#Trainingbatch=64
subdivisions=8
......
[convolutional]
size=1
stride=1
pad=1filters=27###75
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=4###20
num=9
jitter=.3
ignore_thresh = .5
truth_thresh = 1
random=0###1......
[convolutional]
size=1
stride=1
pad=1
filters=27###75
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=4###20
num=9
jitter=.3
ignore_thresh = .5
truth_thresh = 1
random=0###1......
[convolutional]
size=1
stride=1
pad=1
filters=27###75
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=4###20
num=9
jitter=.3
ignore_thresh = .5
truth_thresh = 1
random=0###1
開始訓練
./darknet detector train cfg/yolov3.data cfg/yolov3.cfg darknet53.conv.74
avg:平均loss,越小越好,
rate:學習率,默認為0.001,可以根據自己的訓練情況調整,一般為0.01,0.003,0.001等
訓練參數說明
Region xx: cfg文件中yolo-layer的索引,82是大物體層,94是中物體層,106小物體層;
Avg IOU: 當前叠代中,預測的box與標註的box的平均交並比,越大越好,期望數值為1;
Class: 標註物體的分類準確率,越大越好,期望數值為1;
obj: 越大越好,期望數值為1;
No obj: 越小越好;
.5R: 以IOU=0.5為閾值時候的recall; recall = 檢出的正樣本/實際的正樣本
0.75R: 以IOU=0.75為閾值時候的recall;
count: 正樣本數目。
優化與個性化問題
這邊的更改之後都需要make clean後再make。如果改了之後發現沒變化,請第一時間看看是不是沒有執行這一步。
什麽時候保存模型,又要如何更改呢?
叠代次數小於1000時,每100次保存一次,大於1000時,每10000次保存一次。
自己可以根據需求進行更改,然後重新編譯即可[ 先 make clean ,然後再 make]
代碼位置: examples/detector.c line 138
如何在圖像上添加置信度?
可以看github的修改記錄,也可以看下面的修改,修改傳送門
修改src/image.c文件draw_detections()函數,代碼修改如下:
int i,j; for(i = 0; i < num; ++i){ char labelstr[4096] = {0}; char s1[]={" "};//為了name與置信度之間加空格 int class = -1; char possible[5];//存放檢測的置信值 for(j = 0; j < classes; ++j){ sprintf(possible,"%.2f",dets[i].prob[j]);//置信值截取小數點後兩位賦給possible if (dets[i].prob[j] > thresh){ if (class < 0) { strcat(labelstr, names[j]); strcat(labelstr,s1); //加空格 strcat(labelstr, possible);//標簽中加入置信值 class = j; } else { strcat(labelstr, ", "); strcat(labelstr, names[j]); strcat(labelstr,s1);//加空格 strcat(labelstr, possible);//標簽中加入置信值 } printf("%s: %.0f%%\n", names[j], dets[i].prob[j]*100); } }
如何縮小標簽大小?
src/image.c的draw_detections()函數中,get_label函數調用的參數,修改成合適的大小
效果圖:
如何添加中文標簽?
首先制作中文標簽圖片,
在darknet/data/labels裏面有制作腳本make_labels.py。修改以下就可以用了
例如我的是四類,我就修改成
# -*- coding: utf-8 -*- import os l=["行人","前部","側邊","後部"] def make_labels(s): i = 0 for word in l: os.system("convert -fill black -background white -bordercolor white -border 4 -font /usr/share/fonts/truetype/arphic/ukai.ttc -pointsize %d label:\"%s\" \"cn_%d_%d.png\""%(s,word,i,s/12-1)) i = i + 1 for i in [12,24,36,48,60,72,84,96]: make_labels(i)
運行腳本
python make_labels.py
就可以在labels裏面看到自己制作的圖片標簽
其次修改src/image.c的代碼
具體請看github的修改,傳送門,修改完之後,make clean && make.
測試以下,如圖,Bingo!!!
如何批量檢測?
可以看我github的修改,也可以按照下面的修改。修改傳送門
- 首先在添加一個獲取圖片名字的函數:
#include "darknet.h" #include <opencv2/highgui/highgui.hpp> static int coco_ids[]={1,2,3,4,5,6,7,8,9,10,11,13,14,15,16,17,18,19,20,21,22,23,24,25,27,28,31,32,33,34,35,36,37,38,39,40,41,42,43,44,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,67,70,72,73,74,75,76,77,78,79,80,81,82,84,85,86,87,88,89,90}; char *GetFilename(char *p) { static char name[20]={""}; char *q = strrchr(p,'/') + 1; strncpy(name,q,10);//後面的10是你自己圖片名的長度,可修改 return name; }
- 替換examples/detector.c 中的test_detector函數
void test_detector(char *datacfg, char *cfgfile, char *weightfile, char *filename, float thresh, float hier_thresh, char *outfile, int fullscreen) { list *options = read_data_cfg(datacfg); char *name_list = option_find_str(options, "names", "data/names.list"); char **names = get_labels(name_list); image **alphabet = load_alphabet(); network *net = load_network(cfgfile, weightfile, 0); set_batch_network(net, 1); srand(2222222); double time; char buff[256]; char *input = buff; float nms=.45; int i=0; while(1){ if(filename){ strncpy(input, filename, 256); image im = load_image_color(input,0,0); image sized = letterbox_image(im, net->w, net->h); layer l = net->layers[net->n-1]; float *X = sized.data; time=what_time_is_it_now(); network_predict(net, X); printf("%s: Predicted in %f seconds.\n", input, what_time_is_it_now()-time); int nboxes = 0; detection *dets = get_network_boxes(net, im.w, im.h, thresh, hier_thresh, 0, 1, &nboxes); if (nms) do_nms_sort(dets, nboxes, l.classes, nms); draw_detections(im, dets, nboxes, thresh, names, alphabet, l.classes); free_detections(dets, nboxes); if(outfile) { save_image(im, outfile); } else{ save_image(im, "predictions"); #ifdef OPENCV cvNamedWindow("predictions", CV_WINDOW_NORMAL); if(fullscreen){ cvSetWindowProperty("predictions", CV_WND_PROP_FULLSCREEN, CV_WINDOW_FULLSCREEN); } show_image(im, "predictions",0); cvWaitKey(0); cvDestroyAllWindows(); #endif } free_image(im); free_image(sized); if (filename) break; } else { printf("Enter Image Path: "); fflush(stdout); input = fgets(input, 256, stdin); if(!input) return; strtok(input, "\n"); list *plist = get_paths(input); char **paths = (char **)list_to_array(plist); printf("Start Testing!\n"); int m = plist->size; if(access("/home/lzm/data/test_folder/darknet/car_person/out",0)==-1)//"/homelzm/......"修改成自己要保存圖片的的路徑 { if (mkdir("/home/lzm/data/test_folder/darknet/car_person/out",0777))//"/homelzm/......"修改成自己要保存圖片的的路徑 { printf("creat folder failed!!!"); } } for(i = 0; i < m; ++i){ char *path = paths[i]; image im = load_image_color(path,0,0); image sized = letterbox_image(im, net->w, net->h); //image sized = resize_image(im, net->w, net->h); //image sized2 = resize_max(im, net->w); //image sized = crop_image(sized2, -((net->w - sized2.w)/2), -((net->h - sized2.h)/2), net->w, net->h); //resize_network(net, sized.w, sized.h); layer l = net->layers[net->n-1]; float *X = sized.data; time=what_time_is_it_now(); network_predict(net, X); printf("Try Very Hard:"); printf("%s: Predicted in %f seconds.\n", path, what_time_is_it_now()-time); int nboxes = 0; detection *dets = get_network_boxes(net, im.w, im.h, thresh, hier_thresh, 0, 1, &nboxes); //printf("%d\n", nboxes); //if (nms) do_nms_obj(boxes, probs, l.w*l.h*l.n, l.classes, nms); if (nms) do_nms_sort(dets, nboxes, l.classes, nms); draw_detections(im, dets, nboxes, thresh, names, alphabet, l.classes); free_detections(dets, nboxes); if(outfile){ save_image(im, outfile); } else{ char b[2048]; sprintf(b,"/home/lzm/data/test_folder/darknet/car_person/out/%s",GetFilename(path));//"/homelzm/......"修改成自己要保存圖片的的路徑 save_image(im, b); printf("OJBK!\n",GetFilename(path)); #ifdef OPENCV cvNamedWindow("predictions", CV_WINDOW_NORMAL); if(fullscreen){ cvSetWindowProperty("predictions", CV_WND_PROP_FULLSCREEN, CV_WINDOW_FULLSCREEN); } show_image(im, "predictions",0); cvWaitKey(0); cvDestroyAllWindows(); #endif } free_image(im); free_image(sized); if (filename) break; } } } }
make clean後再make然後批量測試即可,輸入的路徑為那些圖片路徑的txt。
如何輸入檢測結果文本,計算recall,map計算
生成測試結果
./darknet detector valid
文件中batch和subdivisions兩項必須為1。 結果生成在
的results指定的目錄下以 開頭的若幹文件中,若 沒有指定results,那麽默認為 /results。 如果出現以下問題,則說明在.data文件裏面沒有指定valid的路徑,添加即可
Couldn't open file: data/train.list
計算recall
修改examples/detector.c的validate_detector_recall函數,修改傳送門
首先將validate_detector_recall函數定義和調用修改成
void validate_detector_recall(char *datacfg, char *cfgfile, char *weightfile) validate_detector_recall(datacfg, cfg, weights);
list *plist = get_paths("data/coco_val_5k.list"); char **paths = (char **)list_to_array(plist); 修改成 list *options = read_data_cfg(datacfg); char *valid_images = option_find_str(options, "valid", "data/train.list"); list *plist = get_paths(valid_images); char **paths = (char **)list_to_array(plist);
修改完老規矩,make clean後再make
計算recall
./darknet detector recall
文件中batch和subdivisions兩項必須為1。
計算map
先制作xml文件,由於yolo需要的是txt標註,所以之前沒有制作xml的。現在利用一個腳本txt2xml.py進行轉換,txt2xml.py傳送門。
在python3的環境下運行,就可以得到xml文件
計算mAP
借助py-faster-rcnn下的voc_eval.py計算,voc_eval.py傳送門
然後新建一個all_map.py(顧名思義是計算所有類的ap,平均一下就是map)的計算腳本,這邊提供一個例子,根據註釋修改即可。all_map.py傳送門
python2 all_map.py即可看到ap
若不想讓他輸出這麽多rec什麽,可以修改voc.eval.py的返回值只返回ap既可
若要輸出結果就直接在命令後面加一個 > ap_res.txt 即可
若有需求重復計算map,需要刪除生成的annots.pkl
後臺運行並保存訓練日誌
nohup 執行命令 > 輸出文件 2>&1 &
例如
nohup ./darknet detector train head_detector/head.data head_detector/head.cfg head_detector/backup/head.backup -gpus 0,1 > train.log 2>&1 &
loss可視化(待續)
可能出現的問題
出現大量的-nan
原因
有三個可能
可能cfg文件的batch=1,subdivisions=1
可能batch太小了
訓練的圖片中沒有遍布大中小三個層
解決方案
- 修改成Training狀態的參數batch=64, subdicisions=16
在顯存允許的情況下,增加batch
第三種情況屬於正常,因為YOLOv3從三個scale上提取了特征,且不同尺度選取了不同尺度的的檢測框,比如你在訓練小目標,那麽region82和region94出現nan就正常。
CUDA: out of memory
darknet: ./src/cuda.c:36: check_error: Assertion `0' failed.
原因:顯存不夠
解決方案:關閉random,如果還是不行,可以適當調小batch(32或者16)
訓練了很多次itertation,但是還是什麽都沒有檢測出來?
原因
檢測的時候需要batch和sub不是1 ;
如果調低thresh後還沒有出現bbox,那麽就是訓練的時候可能batch和sub都是1,這樣訓練的模 型幾乎不能用
解決方案
- 測試的時候.cfg文件中令 (batch=1,subdivisions=1);
- ?訓練的時候將.cfg文件修改成(batch=64, subdicisions=16)
cfg修改問題?
darknet: ./src/parser.c:348: parse_region: Assertion `l.outputs == params.inputs' failed
原因:filters修改錯誤,沒有對上classes;
解決方案:修改filters
YOLOv2: filters =(classes+5)x5
YOLOv3: filters =(classes+5)x3
can‘t open file :train.txt or can‘t load image ?
Couldn't open file:/home/lzm/test_folder/darknet/car_person/labels/jam6_0116.txt
原因:一般來說是train.txt的文件格式問題;
解決方案:把train.txt的換行格式修改成unix換行格式,另外圖片路徑要正確,參考博文
Segmentation fault (core dumped),訓練幾百次intertation後分段錯誤
原因
- 開了尺度訓練即cfg中random=1,顯存不足;
- 標註的box裏面出現x,y=0的情況;
解決方案
關閉尺度訓練,random=0;
修改坐標讓x,y>0; 參考issue
detector recall的時候出現大量nan
原因:detector.c代碼中的bug,k的最大值必須由nbox確定,而不是
解決方案:修改examples/detector.c的代碼,修改參考
為了批量檢測圖片,修改detector.c後,編譯出現問題
error: too few arguments to function ‘show_image’ show_image(im, "predictions");
原因:show_image函數少了一個參數,有些代碼是比較舊的。解決方案:修改報錯的那一行
show_image(im, "predictions"); 改成 show_image(im, "predictions",0);
為了批量檢測圖片,修改detector.c後,編譯出現問題
error: ‘CV_WINDOW_NORMAL’ undeclared (first use in this function) cvNamedWindow("predictions", CV_WINDOW_NORMAL);
原因:原因鏈接,darknet.h還少了opencv的一個頭文件,#include <opencv2/highgui/highgui.hpp>解決方案:在報錯的程序中加上即可
#include "darknet.h" #include <opencv2/highgui/highgui.hpp>
使用txt2xml.py生成xml時候的問題
問題issue
'encoding' is an invalid keyword argument for this function
原因:在Python 2中,open()函數不帶編碼參數(第三個參數是緩沖選項)
解決方法:有兩種
import io
open改成 io.open
使用python3
References
- https://pjreddie.com/darknet/
- https://blog.csdn.net/Pattorio/article/details/80051988
- https://blog.csdn.net/lilai619/article/details/79695109
- https://blog.csdn.net/mieleizhi0522/article/details/79989754
- https://blog.csdn.net/cgt19910923/article/details/80524173
- https://github.com/AlexeyAB/darknet/blob/master/README.md
YOLOv3 訓練自己的數據附優化與問題總結