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
- 1
其中./darknet表示運行編譯生成的darknet.exe文件,darknet.exe首先調用example文件夾下的darknet.c,該文件中的main函數需要預定義參數,detector即為預定義參數,如下代碼
else if (0 == strcmp(argv[1], "detector")){
run_detector(argc, argv);
- 1
- 2
由‘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"))
- 1
- 2
- 3
- 4
- 5
- 6
其中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
- 1
- 2
- 3
- 4
- 5
- 6
class表示檢測類別,train為訓練中需要用到的訓練數據的列表,valid為驗證集列表,names為檢測類別的名稱,backup為訓練中用到的存放訓練模型的路徑
.cfg文件定義了模型結構,而.weight文件為調用的模型權重文件
運行以上命令,會在終端得到如下提示:
Enter Image Path:
- 1
直接在終端輸入圖像的路徑,就可以對該圖像進行檢測,並在darknet的根目錄生成名為predictions.png的檢測結果,如圖:
- 分類
分類和檢測類似,調用命令如下:
./darknet classifier predict classify.data classify.cfg classify.weights
- 1
與檢測同理,./darknet運行darknet.exe,並調用example中的darknet.c文件,通過classfier調用classifier.c中的run_classifier函數:
else if (0 == strcmp(argv[1], "classifier")){
run_classifier(argc, argv);
- 1
- 2
並通過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);
...
- 1
- 2
- 3
- 4
- 5
- 6
而classify.data,classify.cfg,classify.weights分別表示分類對應的.data文件,模型定義cfg文件和模型權重.weights文件。
- 訓練
- 檢測模型的訓練:
(1)數據准備:
首先,你需要將數據的groundtruth轉化為darknet需要的格式,如果你的gt為voc格式的xml,可以通過如下腳本進行轉換
import xml.etree.ElementTree as ET
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')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
上面的代碼需要自行設置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
- 1
- 2
圖中共有車牌兩個,每行保存一個車牌的信息,第一個0表示檢測object的label,因為我只有一類,所以都是0
后面的四位即為歸一化后的中心點坐標和位置框的長和寬
最后將圖像和對應txt的文件名統一,並拷貝到同一個文件夾(a.jpg對應的txt為a.txt),如圖:
注意,txt和對應jpg文件的名稱除了最后.jpg,.txt的后綴不一樣,其他的必須完全一樣,且需要保存在同一文件夾,訓練過程中會直接將a.jpg的名稱替換為a.txt來尋找該圖像對應的gt。對應的gt文件也不一定必須是txt格式,如果不是txt格式可以去源碼中將這部分代碼進行修改,將.jpg替換為你需要的格式后綴
(2).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
- 1
image_path為你數據集的路徑
(3).cfg文件准備
如果要調用yolo v3,可以直接使用cfg文件夾下的yolov3.cfg,但是需要做如下幾個修改:
首先,將最上方的
# Testing
batch=1
subdivisions=1
# Training
# batch=64
# subdivisions=16
- 1
- 2
- 3
- 4
- 5
- 6
修改為
# Testing
#batch=1
#subdivisions=1
# Training
batch=64
subdivisions=16
- 1
- 2
- 3
- 4
- 5
- 6
其中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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
[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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
圖中的random表示論文中提及的resize network來增強網絡的適應性,如果顯存足夠,最好設置為1,如果顯存不夠,也可以將其設置為0,即不進行network resizing
(4)weights文件准備
如果你使用的是作者提供的cfg模型結構,如yolov3,那么可以到其官網去下載預訓練模型來初始化模型參數,這樣可以加快收斂。當然,你也可以不使用預訓練模型進行訓練
(5)開始訓練
如果使用預訓練模型則使用如下命令
./darknet detector train data/detect.data data/yolov3.cfg data/yolov3.weight
- 1
否則,使用
./darknet detectortrain data/detect.data data/yolov3.cfg
- 1
命令類似與之前檢測的調用命令,只是將test變為了train
- 分類模型的訓練
(1)數據准備
和檢測不一樣,分類的gt只需要一個label即可,不再需要位置框的信息,所以不再需要單獨的txt文件來保存gt,而是直接將label在圖像名稱上進行體現。所以需要對圖像名稱進行重命名,命名規則為:
(圖片序號)_(標簽).(圖片格式),如1_numzero.jpg
需要注意的是:
1.label不區分大小寫,即 numzero和NumZero是一樣的效果
2.label之間不能有包含關系,如ji和jin,不能出現這樣的形式,可改為ji1和jin
(2).data文件准備
和檢測類似:
classes=65
train = data/char/train.list
labels = data/char/labels.txt
backup = backup/
top=2
- 1
- 2
- 3
- 4
- 5
其中top不是在訓練中使用,而是在分類調用時使用,表示輸出Top個最高的可能值
(3).cfg文件准備
可以從cfg文件夾中選擇,也可以自行定義
(4).weights文件
同上面的檢測
(5)開始訓練
使用命令:
./darknet classifier train cfg/cifar.data cfg/cifar_small.cfg (xxx.weights)