配置
YOLOv4是最近開源的一個又快又準確的目標檢測器。
首先看一下Github上的版本要求及下載地址:
系統:Windows or Linux
CMake >= 3.12: https://cmake.org/download/
CUDA 10.0: https://developer.nvidia.com/cuda-toolkit-archive
OpenCV >= 2.4: 直接從Opencv官網下載就好
cuDNN >= 7.0 for CUDA 10.0: https://developer.nvidia.com/rdp/cudnn-archive
Visual Studio 2015/2017/2019: https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community
YOLO v4原始碼
YOLOv4倉庫github地址: https://github.com/AlexeyAB/darknet.
為了防止下載壓縮包解壓後會丟失一些檔案,建議在碼雲上clone下來。
CMake安裝
進入官網下載最新的版本即可,圖中紅色標註的位置,下載後一路預設安裝點下來就好。
CUDA安裝
CUDA是我當初安裝tensorflow2.0時安的
主要是從官網上下載安裝檔案,
解壓縮,如圖:
同意並繼續,選擇精簡,進入安裝,大概要幾分鐘的時間。
安裝完成後檢查是否安裝成功。
win+R 進入執行介面,輸入cmd進入命令列介面,輸入nvcc --version 檢視是否安裝成功
cuDNN安裝
在官網下載前需要註冊NVIDIA DEVELPOER 的賬號,選擇與CUDA10.0對應的版本cuDNN7.6.4,點選library for win10檔案,大概有240M左右。如下圖所示:
官網的安裝教程:https://docs.nvidia.com/deeplearning/sdk/cudnn-install/index.html#installwindows
其實就是將解壓後將下面幾個檔案複製到CUDA的對應路徑下。
- Copy ...\cuda\bin\cudnn64_7.6.5.32.dll to C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.2\bin.
- Copy ...\cuda\ include\cudnn.h to C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.2\include.
- Copy ...\cuda\lib\x64\cudnn.lib to C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.2\lib\x64.
簡便快捷的方法就是把下載好的CUDNN檔案 全部複製到C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.2中
OpenCV安裝
從官網下載,版本要大於2.4
下載好直接解壓就可以,並將installpath\opencv\build\路徑新增到系統的環境變數中,
CMake編譯的時候會自動找到這個資料夾
新增環境變數如下圖所示,在此電腦右擊屬性,彈出下圖介面,點選高階系統設定。
點選環境變數,新建環境變數,
新建環境變數,變數名為OpenCV_DIR,變數地址為C:\Program Files\opencv\build
Cmake編譯
如果你想用GPU加快檢測速度,需要將Makefile檔案用記事本開啟,更改GPU=1 CUDNN=1 CUDNN_HALF=1 OPENCV=1。如下圖所示:
選擇Browse Source 為原始碼所在資料夾,Browse Build 可以選擇同一資料夾,點選Configure,彈出如下介面:
第一項選擇你所安裝的VS版本,第二項平臺選擇x64 ,點選Finish,點選Configure,沒有錯誤後點選生成。
成功後點選Open Project 開啟專案檔案。
VS編譯
注意:選擇release版本,x64,直接生成解決方案。
將D:\github\darknet\Release資料夾下的darknet.exe檔案複製
開啟到D:\github\darknet\build\darknet\x64資料夾內進行貼上,如下圖所示
將opencv ...\opencv\build\x64\vc14\bin 下的兩個opencv_world330.dll和 opencv_ffmpeg330_64.dll dll檔案複製到上述資料夾內。如下圖所示:
將cuDNN中的cudnn64_7.dll複製到上述資料夾。
影像測試
為了驗證是否配置成功,下載推薦的yolov4.weights檔案,檔案大概有245M,百度雲.密碼:wg0r
將下載後的檔案放在D:\github\darknet\build\darknet\x64資料夾下。
開啟cmd命令列,轉到上述的資料夾內,如下圖所示:
按照GitHub,測試影像和視訊複製對應的命令:
Yolo v4 COCO - image: darknet.exe detector test cfg/coco.data cfg/yolov4.cfg yolov4.weights -thresh 0.25
Output coordinates of objects: darknet.exe detector test cfg/coco.data yolov4.cfg yolov4.weights -ext_output dog.jpg
Yolo v4 COCO - video: darknet.exe detector demo cfg/coco.data cfg/yolov4.cfg yolov4.weights -ext_output test.mp4
測試結果
-
自帶的dog影像
-
羊群
訓練
標註工具
labelImg: https://github.com/tzutalin/labelImg適用於影像檢測的資料集製作,可以直接生成yolo的標註格式。
label 檔案格式:
<目標類別> <中心點X座標> <中心點Y座標> <寬> <高>
(歸一化到了【0,1】之間,除以影像寬高)
eg:<x> = <absolute_x> / <image_width>
<height> = <absolute_height> / <image_height>
幾個常用的快捷鍵:
w | 建立矩形框 |
---|---|
d | 下一張圖片 |
a | 上一張圖片 |
另一個工具Yolo_mark:https://github.com/AlexeyAB/Yolo_mark
準備資料集
下面是標註檔案為xml格式的處理過程,txt檔案的話直接複製到labels資料夾即可
資料夾格式
VOCdevkit
——VOC2020
————---Annotations #放入所有的xml檔案
————---ImageSets
——————-----Main #放入train.txt,val.txt檔案
————---JPEGImages #放入所有的訓練圖片檔案
————---labels #放入所有的txt檔案,會自動生成此資料夾
————---TESTImages #放入所有的測試圖片檔案
#Main中的檔案分別表示test.txt是測試集,train.txt是訓練集,val.txt是驗證集
建立資料夾的程式
import os
import time
def mkFolder(path):
year = str(time.localtime()[0])
floderName = 'VOC' + year
# path = os.path.normpath(path)
floderPath = os.path.join(path, floderName)
if not os.path.exists(floderPath):
os.makedirs(floderPath)
print(floderPath)
subfloderName = ['Annotations', 'ImageSets', 'JPEGImages', 'labels', 'TESTImages']
for name in subfloderName:
subfloderPath = os.path.join(path, floderName, name)
print(subfloderPath)
if not os.path.exists(subfloderPath):
os.makedirs(subfloderPath)
if name == 'ImageSets':
secSubFolderName = 'Main'
secSubFolderPath = os.path.join(path, floderName, name, secSubFolderName)
print(secSubFolderPath)
if not os.path.exists(secSubFolderPath):
os.makedirs(secSubFolderPath)
if __name__ == '__main__':
path = r'D:\program' #更換自己的資料夾路徑
mkFolder(path)
獲取所有檔名
可以修改訓練集和驗證集的比例,生成train.txt 和val.txt 檔案
import os
from os import listdir, getcwd
from os.path import join
if __name__ == '__main__':
source_folder = 'D:/github/darknet/build/darknet/x64/scripts/VOCdevkit/VOC2020/JPEGImages'
dest = 'D:/github/darknet/build/darknet/x64/scripts/VOCdevkit/VOC2020/ImageSets/Main/train.txt' # train.txt檔案路徑
dest2 = 'D:/github/darknet/build/darknet/x64/scripts/VOCdevkit/VOC2020/ImageSets/Main/val.txt' # val.txt檔案路徑
file_list = os.listdir(source_folder)
train_file = open(dest, 'a')
val_file = open(dest2, 'a')
file_num = 0
for file_obj in file_list:
file_path = os.path.join(source_folder, file_obj)
file_name, file_extend = os.path.splitext(file_obj)
file_num = file_num + 1
if (file_num % 4 == 0): # 每隔4張選取一張驗證集
val_file.write(file_name + '\n')
else:
train_file.write(file_name + '\n')
# val_file.write(file_name + '\n')
#
# train_file.write(file_name + '\n')
train_file.close()
val_file.close()
執行voc_label
將voc_label.py放到VOCdevkit資料夾下,如下圖所示:
import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join
sets=[('2020', 'train'), ('2020', 'val')] ##這裡要與Main中的txt檔案一致
#classes = ["bubble", "adhension","outer","inner"]
classes = ["bubble"] # #你所標註的類別名
def convert(size, box):
dw = 1./(size[0])
dh = 1./(size[1])
x = (box[0] + box[1])/2.0 - 1
y = (box[2] + box[3])/2.0 - 1
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), encoding="utf8")
out_file = open('VOCdevkit/VOC%s/labels/%s.txt'%(year, image_id), 'w', encoding="utf8")
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('\n').split('\n')
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()
#os.system("cat 2020_train.txt 2020_val.txt > train.txt")
新建obj.data
複製cfg資料夾下的voc.data,重新命名為obj.data
classes= 1
train = scripts/2020_train.txt
valid = scripts/2020_val.txt
names = data/obj.names
backup = backup/
新建obj.names
複製data目錄下的voc.name,改為obj.name,裡面寫標籤的名字,每行一個
修改cfg檔案
把第三行batch改為batch=64
把subdivisions那一行改為 subdivisions=16
將max_batch更改為(資料集標籤種類數(classes)*2000 但不小於訓練的圖片數量以及不小於6000)
將第20的steps改為max_batch的0.8倍和0.9倍
把位於8-9行設為width=416 height=416 或者其他32的倍數:
將classes=80 改為你的類別數 (有三個地方,969行,1056行,1143行)
改正[filters=255] 為 filters=(classes + 5)x3 (位置為查詢yolo,每個yolo前的[convolutional]裡,注意只修改最接近yolo的那個filters需要修改,一共應該有三處)
如果使用 [Gaussian_yolo] 層,修改 filters=(classes + 9)x3 (位置為CRRL+F查詢Gaussian_yolo,每個Gaussian_yolo前的[convolutional]裡,注意只修改最接近Gaussian_yolo的那個filters需要修改,一共應該有三處)
訓練資料
開始訓練
yolov4.conv.137為預訓練權重,沒有的話會隨機初始化權重
預訓練權重,密碼:jirs
darknet.exe detector train data/obj.data yolov-obj.cfg yolov4.conv.137 -map
mAP(均值平均精度) = 所有類別的平均精度求和除以所有類別
每4個Epochs計算一次map
訓練生成的權重檔案在<backup>目錄下:
last_weights 每迭代100次儲存一次
xxxx_weights 每迭代1000次儲存一席
繼續訓練
每迭代100步可以手動停止,下次訓練載入此次的權重檔案便可以接著訓練。
eg:從2000步停止訓練後,可以使用如下命令繼續訓練
darknet.exe detector train data/obj.data yolo-obj.cfg backup\yolo-obj_2000.weights
停止訓練
停止訓練的條件:
訓練過程中如果 avg出現nan,訓練可能出錯,需要停止
如果nan出現在其他行,訓練正常
如果迭代很多次後avg補再下降,需要停止
avg越低越好—,也要防止過擬合
對小的模型、簡單的資料集,avg一般為0.05
對大的模型、複雜的資料集,avg一般為3
提高目標檢測準確率
- 為了在一張影像上檢測大量的目標:修改cfg檔案,在最後一個[yolo]層增加max=200或者更大的值
- 為了檢測更小的目標(自己的影像縮放到416X416目標尺寸小於16X16):
895行 layers = 23.
892行 stride=4
989行 stride=4
-
如果你的資料左右物件作為不同的類,比如:左右手、向左轉、向右轉路標
在資料增強部分 17行增加flip=0 -
如果小目標和巨大的目標一起訓練,需要修改模型
-
為了使定位框更準確,在每個[yolo]層增加ignore_thresh = .9 iou_normalizer=0.5 iou_loss=giou——這會提高mAP@0.9, 但是會降低mAP@0.5.
-
如果你很精通,可以重新計算自己資料集的anchor
darknet.exe detector calc_anchors data/obj.data -num_of_clusters 9 -width 416 -height 416 -show
同目錄下會生成anchors.txt
7. 更高的準確率需要更高的解析度,608X608或者832X832,如果視訊記憶體不夠,增加subdivisions=16, 32 or 64
測試
批量測試
還是修改原始碼吧,修改
detector.c
大不了重新編譯一次,終於發現了之前GetFilename函式獲取檔名的問題,只能複製檔名的前3個字元給儲存的結果檔名,就導致之前出現一直會覆蓋掉之前檔案的錯誤
char *GetFilename(char *p)
{
static char name[20] = { "" };
char *q = strrchr(p, '/') + 1; //在檔名中從後往前查詢第一個/
memset(name, ' ', sizeof(name)); //清除記憶體位置
strncpy(name, q, 10); // 把檔名複製到name中,最多複製n個字元
return name;
}
現在這樣就好啦!
記得把下面這塊也改一改,在1659行左右,
方法ctrl+F查詢draw_detections_v3,修改上面那個
draw_detections_v3(im, dets, nboxes, thresh, names, alphabet, l.classes, ext_output);
//save_image(im, "predictions");
char b[1024];
sprintf(b, "output/%s", GetFilename(input));
save_image(im, b);
測試前修改cfg檔案,把#Testing 下的兩行解除註釋
[net]
# Testing
batch=1
subdivisions=1
# Training
batch=32
subdivisions=16
width=416
height=416
channels=3
momentum=0.949
decay=0.0005
angle=0
saturation = 1.5
exposure = 1.5
hue=.1
最後貼一下訓練及測試的命令,謹此銘記
訓練資料
darknet.exe detector train data/obj.data yolov-obj.cfg yolov4.conv.137
單張測試
darknet.exe detector test data/obj.data yolov-obj.cfg backup/yolov-obj_800.weights test.jpg -i 0 -thresh 0.25 -gpus 0,1,2,3
批量測試結果儲存至output資料夾
darknet.exe detector test data/obj.data yolov-obj.cfg backup/yolov-obj_800.weights -ext_output < data/test.txt > result.txt -gpus 0,1,2,3
不想顯示的話
darknet.exe detector test data/obj.data yolov-obj.cfg backup/yolov-obj_800.weights -ext_output -dont_show < data/test.txt > result.txt -gpus 0,1,2,3
想要輸出座標的話
darknet.exe detector test data/obj.data yolov-obj.cfg backup/yolov-obj_800.weights -ext_output dog.jpg