YOLOv4 win10 配置 + 訓練自己的資料 + 測試

重糖愛好者發表於2021-01-27

配置

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的對應路徑下。

  1. Copy ...\cuda\bin\cudnn64_7.6.5.32.dll to C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.2\bin.
  2. Copy ...\cuda\ include\cudnn.h to C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.2\include.
  3. 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編譯的時候會自動找到這個資料夾
新增環境變數如下圖所示,在此電腦右擊屬性,彈出下圖介面,點選高階系統設定。

點選環境變數,新建環境變數,
Alt
新建環境變數,變數名為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

在這裡插入圖片描述

測試結果

  1. 自帶的dog影像
    在這裡插入圖片描述

  2. 羊群
    在這裡插入圖片描述

訓練

標註工具

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

提高目標檢測準確率

  1. 為了在一張影像上檢測大量的目標:修改cfg檔案,在最後一個[yolo]層增加max=200或者更大的值
  2. 為了檢測更小的目標(自己的影像縮放到416X416目標尺寸小於16X16):
895行 layers = 23. 
892行 stride=4
989行 stride=4
  1. 如果你的資料左右物件作為不同的類,比如:左右手、向左轉、向右轉路標
    在資料增強部分 17行增加flip=0

  2. 如果小目標和巨大的目標一起訓練,需要修改模型

  3. 為了使定位框更準確,在每個[yolo]層增加ignore_thresh = .9 iou_normalizer=0.5 iou_loss=giou——這會提高mAP@0.9, 但是會降低mAP@0.5.

  4. 如果你很精通,可以重新計算自己資料集的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

相關文章