轉載自:https://blog.csdn.net/u010122972/article/details/83541978
優點
Darknet是一個比較小衆的深度學習架構,沒有社群,主要靠作者團隊維護,是以推廣較弱,用的人不多。而且由于維護人員有限,功能也不如tensorflow等架構那麼強大,但是該架構還是有一些獨有的優點:
1.易于安裝:在makefile裡面選擇自己需要的附加項(cuda,cudnn,opencv等)直接make即可,幾分鐘完成安裝;
2.沒有任何依賴項:整個架構都用C語言進行編寫,可以不依賴任何庫,連opencv作者都編寫了可以對其進行替代的函數;
3.結構明晰,源代碼檢視、修改友善:其架構的基礎檔案都在src檔案夾,而定義的一些檢測、分類函數則在example檔案夾,可根據需要直接對源代碼進行檢視和修改;
4.友好python接口:雖然darknet使用c語言進行編寫,但是也提供了python的接口,通過python函數,能夠使用python直接對訓練好的.weight格式的模型進行調用;
5.易于移植:該架構部署到機器本地十分簡單,且可以根據機器情況,使用cpu和gpu,特别是檢測識别任務的本地端部署,darknet會顯得異常友善。
代碼結構
下圖是darknet源代碼下載下傳解壓後檔案夾的分布情況:
1.cfg檔案夾内是一些模型的架構,每個cfg檔案類似與caffe的prototxt檔案,通過該檔案定義的整個模型的架構
2.data檔案夾内放置了一些label檔案,如coco9k的類别名等,和一些樣例圖(該檔案夾主要為示範用,或者是直接訓練coco等對應資料集時有用,如果要用自己的資料自行訓練,該檔案夾内的東西都不是我們需要的)
3.src檔案夾内全是最底層的架構定義檔案,所有層的定義等最基本的函數全部在該檔案夾内,可以了解為該檔案夾就是架構的源碼;
4.examples檔案夾是更為高層的一些函數,如檢測函數,識别函數等,這些函數直接調用了底層的函數,我們經常使用的就是example中的函數;
5.include檔案夾,顧名思義,存放頭檔案的地方;
6.python檔案夾裡是使用python對模型的調用方法,基本都在darknet.py中。當然,要實作python的調用,還需要用到darknet的動态庫libdarknet.so,這個動态庫稍後再介紹;
7.scripts檔案夾中是一些腳本,如下載下傳coco資料集,将voc格式的資料集轉換為訓練所需格式的腳本等
8.除了license檔案,剩下的就是Makefile檔案,如下圖,在問價開頭有一些選項,把你需要使用的選項設為1即可
安裝
1.點開Makefile,将需要的選項設定為1,如圖,使用GPU和CUDNN
2.打開終端,進入到darknet檔案夾根目錄,輸入make,開始編譯
3.幾分鐘後編譯完成,檔案夾中會多出一些檔案夾和檔案,obj檔案中存放了編譯過程中的.o檔案,其他的幾個空檔案夾也不需要太大關注,這裡最重要的就是三個:名為darknet的exe檔案,名為libdarknet.a的靜态連結庫和名為libdarknet.so的動态連結庫。如果直接在本地進行模型調用嘗試,可以直接運作darknet這個exe檔案,如果需要移植調用,則需要用到libdarknet.so這個動态連結庫,這個動态連結庫中隻包含了src檔案夾中定義的架構基礎函數,沒有包含examples中的高層函數,是以調用過程中需要自己去定義檢測函數
運作如下代碼
./darknet detector test data/detect.data data/yolov3.cfg data/yolov3.weight
其中./darknet表示運作編譯生成的darknet.exe檔案,darknet.exe首先調用example檔案夾下的darknet.c,該檔案中的main函數需要預定義參數,detector即為預定義參數,如下代碼
else if (0 == strcmp(argv[1], "detector")){
run_detector(argc, argv);
由‘detector’轉而調用run_detector,run_detector存在于example檔案夾下的detector.c中,再根據預定義參數,确定是調用檢測函數,訓練函數還是驗證函數:
if(0==strcmp(argv[2], "test")) test_detector(datacfg, cfg, weights, filename, thresh, hier_thresh, outfile, fullscreen);
else if(0==strcmp(argv[2], "train")) train_detector(datacfg, cfg, weights, gpus, ngpus, clear);
else if(0==strcmp(argv[2], "valid")) validate_detector(datacfg, cfg, weights, outfile);
else if(0==strcmp(argv[2], "valid2")) validate_detector_flip(datacfg, cfg, weights, outfile);
else if(0==strcmp(argv[2], "recall")) validate_detector_recall(cfg, weights);
else if(0==strcmp(argv[2], "demo"))
其中test表示檢測,train表示訓練,valid表示驗證,recall表示測試其召回率,demo為調用攝像頭的實時檢測
指令最後的三個參數表示運作需要的檔案,.data檔案記錄了模型檢測的類别,類名檔案等,如下:
classes= 1
train = /media/seven/yolov3/data/plate2/train.list
#valid = data/coco_val_5k.list
names = data/plate/plate.names
backup = /media/seven/yolov3/data/plate2/models
#eval=coco
class表示檢測類别,train為訓練中需要用到的訓練資料的清單,valid為驗證集清單,names為檢測類别的名稱,backup為訓練中用到的存放訓練模型的路徑
.cfg檔案定義了模型結構,而.weight檔案為調用的模型權重檔案
運作以上指令,會在終端得到如下提示:
Enter Image Path:
直接在終端輸入圖像的路徑,就可以對該圖像進行檢測,并在darknet的根目錄生成名為predictions.png的檢測結果,如圖:
分類
分類和檢測類似,調用指令如下:
./darknet classifier predict classify.data classify.cfg classify.weights
與檢測同理,./darknet運作darknet.exe,并調用example中的darknet.c檔案,通過classfier調用classifier.c中的run_classifier函數:
else if (0 == strcmp(argv[1], "classifier")){
run_classifier(argc, argv);
并通過predict進而調用predict_classifier函數:
if(0==strcmp(argv[2], "predict")) predict_classifier(data, cfg, weights, filename, top);
else if(0==strcmp(argv[2], "fout")) file_output_classifier(data, cfg, weights, filename);
else if(0==strcmp(argv[2], "try")) try_classifier(data, cfg, weights, filename, atoi(layer_s));
else if(0==strcmp(argv[2], "train")) train_classifier(data, cfg, weights, gpus, ngpus, clear);
else if(0==strcmp(argv[2], "demo")) demo_classifier(data, cfg, weights, cam_index, filename);
...
而classify.data,classify.cfg,classify.weights分别表示分類對應的.data檔案,模型定義cfg檔案和模型權重.weights檔案。
訓練
檢測模型的訓練:
資料準備:
首先,你需要将資料的groundtruth轉化為darknet需要的格式,如果你的gt為voc格式的xml,可以通過如下腳本進行轉換
import pickle
import os
from os import listdir, getcwd
from os.path import join
classes = ["plate"]#類别改為自己需要檢測的所有類别
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(image_id):
in_file = open(xml_path)#與圖檔對應的xml檔案所在的位址
out_file = open(txt_save_path,'w') #與此xml對應的轉換後的txt,這個txt的儲存完整路徑
tree=ET.parse(in_file)
root = tree.getroot()
size = root.find('size') #通路size标簽的資料
w = int(size.find('width').text)#讀取size标簽中寬度的資料
h = int(size.find('height').text)#讀取size标簽中高度的資料
for obj in root.iter('object'):
cls = obj.find('name').text
if cls not in classes :#or int(difficult) == 1:
continue
cls_id = classes.index(cls)
xmlbox = obj.find('bndbox') #通路boundbox标簽的資料并進行處理,都按yolo自帶的代碼來,沒有改動
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')
上面的代碼需要自行設定xml_path和txt_save_path
從上面的代碼可以看到,對于object的位置x_min,x_max,y_min,y_max,先求得其中心點坐标center_x,center_y以及位置框的長寬width_rect,height_rect,再将這四個值分别除以長寬以将資料歸一化,如果不是voc格式的資料可以按照這樣的思路進行類似處理
如果是voc格式的資料可以參照我之前的部落格進行一步一步的處理darknet用自己的資料進行訓練
按照上面的流程,每張圖像都生成了對應的txt檔案來儲存其歸一化後的位置資訊,如下圖對應生成的txt如下:
0 0.250925925926 0.576388888889 0.1 0.0263888888889
0 0.485185185185 0.578125 0.0685185185185 0.0201388888889
圖中共有車牌兩個,每行儲存一個車牌的資訊,第一個0表示檢測object的label,因為我隻有一類,是以都是0
後面的四位即為歸一化後的中心點坐标和位置框的長和寬
最後将圖像和對應txt的檔案名統一,并拷貝到同一個檔案夾(a.jpg對應的txt為a.txt),如圖:
注意,txt和對應jpg檔案的名稱除了最後.jpg,.txt的字尾不一樣,其他的必須完全一樣,且需要儲存在同一檔案夾,訓練過程中會直接将a.jpg的名稱替換為a.txt來尋找該圖像對應的gt。對應的gt檔案也不一定必須是txt格式,如果不是txt格式可以去源碼中将這部分代碼進行修改,将.jpg替換為你需要的格式字尾
.data檔案準備
前面已經貼過.data的圖,訓練的時候必須的項目有“class”,“train”,“backup”。“names”最好也設定上,友善以後調用。
“class”表示你要檢測的類别個數,如檢測類别為20則
class=20
“backup”表示訓練過程中的緩存和儲存的模型。訓練過程中,會在該路徑下生成一個字尾為.backup的檔案,該檔案每100個step将模型更新一遍,以防止訓練忽然終端而沒有儲存模型。并且,訓練儲存的模型也會存在該路徑下。預設情況下,每10000step還是多少(記不太清了,我自己修改過)會儲存一個模型,命名為yolov3_疊代次數.weights,最終訓練完成還會儲存一個yolov3_final.weights。這些模型都會儲存在backup路徑下
“names”為儲存檢測object名稱的路徑,names=plate.names
“train”為你訓練集的list路徑,如train=data/trainlist.txt,trainlist.txt中儲存了所有訓練集圖像的路徑,如下圖
可以通過如下指令直接生成該檔案:
find image_path -name \*.jpg > trainlist.txt
image_path為你資料集的路徑
.cfg檔案準備
如果要調用yolo v3,可以直接使用cfg檔案夾下的yolov3.cfg,但是需要做如下幾個修改:
首先,将最上方的
# Testing
batch=1
subdivisions=1
# Training
# batch=64
# subdivisions=16
修改為
# Testing
#batch=1
#subdivisions=1
# Training
batch=64
subdivisions=16
其中batch表示batchsize,而subdivisions是為了解決想要大batchsize而顯存又不夠的情況,每次代碼隻讀取batchsize/subdivisions 個圖像,如圖中設定為64/16=4,但是會将16次的結果也就是64張圖的結果,作為一個batch來統一處理
(調用的時候再将testing部分解除注釋,并将train部分注釋)
然後,根據自己檢測的類别,将每個
[yolo]
(共有三個
[yolo]
) 下方的classes修改為自己需要檢測的類别,如果隻檢測一類則classes=1
然後将每個
[yolo]
上方的第一個filters的值進行修改,計算方式為
(5+classes)*3
,如果classes為1,則為18,修改前後的對比:
[convolutional]
size=1
stride=1
pad=1
filters=255
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=80
num=9
jitter=.3
ignore_thresh = .5
truth_thresh = 1
random=1
[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=1
圖中的random表示論文中提及的resize network來增強網絡的适應性,如果顯存足夠,最好設定為1,如果顯存不夠,也可以将其設定為0,即不進行network resizing
weights檔案準備
如果你使用的是作者提供的cfg模型結構,如yolov3,那麼可以到其官網去下載下傳預訓練模型來初始化模型參數,這樣可以加快收斂。當然,你也可以不使用預訓練模型進行訓練
開始訓練
如果使用預訓練模型則使用如下指令
./darknet detector train data/detect.data data/yolov3.cfg data/yolov3.weight
否則,使用
./darknet detectortrain data/detect.data data/yolov3.cfg
分類模型的訓練
資料準備
和檢測不一樣,分類的gt隻需要一個label即可,不再需要位置框的資訊,是以不再需要單獨的txt檔案來儲存gt,而是直接将label在圖像名稱上進行展現。是以需要對圖像名稱進行重命名,命名規則為:
(圖檔序号)_(标簽).(圖檔格式),如1_numzero.jpg
需要注意的是:
1.label不區分大小寫,即 numzero和NumZero是一樣的效果
2.label之間不能有包含關系,如ji和jin,不能出現這樣的形式,可改為ji1和jin
.data檔案準備
和檢測類似:
classes=65
train = data/char/train.list
labels = data/char/labels.txt
backup = backup/
top=2
其中top不是在訓練中使用,而是在分類調用時使用,表示輸出Top個最高的可能值
.cfg檔案準備
可以從cfg檔案夾中選擇,也可以自行定義
.weights檔案
同上面的檢測
開始訓練
使用指令:
./darknet classifier train cfg/cifar.data cfg/cifar_small.cfg (xxx.weights)