1. 前情說明:
窮苦學生一枚,恰好最近在學習object detection,用到了yolov3模型,搗鼓了好幾天,看了各大論壇、貼吧、CSDN,知乎,博客園等好多大佬前輩們寫的文章(吐血.jpg),在這裏將自己的過程和結果寫出來,希望大家能少走點彎路。
2. 環境:
這個很重要!!!!!
- window 10
- pytorch 1.4.0
- opencv-python
- tqdm
- matplotlib
- pycocotools(這個很難裝!!,原作者壓根沒有考慮window環境下的coco tools,真的吐血,具體安裝教程可以參考我的博客:https://blog.csdn.net/weixin_45829462/article/details/104787103
本次使用的是ultralytics-yolov3
爲什麼使用這個版本,因爲這個版本工業應用的比較多,檢測速度也比較快,最重要的是,目前這個版本原作者一直在優化之中。
3. 步驟:
3.1 製作數據集:
在製作數據集需要用到一款標註工具,labellmge,安裝地址:github:https://github.com/tzutalin/labelImg,使用方法:
https://www.cnblogs.com/Terrypython/p/9577657.html
3.2 數據集:
博主使用的數據集是Voc行人數據集,是已經標註好的數據集,下載地址:
大家先將數據集下載到桌面,解壓,等待使用,數據中有兩個文件夾,分別是:
Annotations文件打開後如下:
JPEGImages打開後如下:
3.3 下載模型:
請大家將模型下載下來,地址:https://github.com/ultralytics/yolov3
因爲博主準備訓練關於檢測行人的模型,故命名爲yolov3-person,下載下來重要的源文件包都有:
3.4 裝載數據:
將數據集Annotations、JPEGImages複製到yolov3-person工程目錄下的data文件下;同時新建兩個文件夾,分別命名爲ImageSets和labels,最後我們將JPEGImages文件夾複製粘貼一下,並將文件夾重命名爲images,具體如下注:(data下加上原先的sample文件夾,一共是6個子文件夾):
3.5 建立標籤文件:
在根目錄下新建兩個文件夾,makeTxt.py和voc_label.py,將以下代碼拷貝進py文件中,如下:
- makeTxt.py:
import os
import random
trainval_percent = 0.1
train_percent = 0.9
xmlfilepath = 'data/Annotations'
txtsavepath = 'data/ImageSets'
total_xml = os.listdir(xmlfilepath)
num = len(total_xml)
list = range(num)
tv = int(num * trainval_percent)
tr = int(tv * train_percent)
trainval = random.sample(list, tv)
train = random.sample(trainval, tr)
ftrainval = open('data/ImageSets/trainval.txt', 'w')
ftest = open('data/ImageSets/test.txt', 'w')
ftrain = open('data/ImageSets/train.txt', 'w')
fval = open('data/ImageSets/val.txt', 'w')
for i in list:
name = total_xml[i][:-4] + '\n'
if i in trainval:
ftrainval.write(name)
if i in train:
ftest.write(name)
else:
fval.write(name)
else:
ftrain.write(name)
ftrainval.close()
ftrain.close()
fval.close()
ftest.close()
- voc_label.py:
import xml.etree.ElementTree as ET
import os
from os import listdir, getcwd
sets = ['train', 'test', 'val']
classes = ["person"] # 我們只檢測person這個類別
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('data/Annotations/%s.xml' % (image_id))
out_file = open('data/labels/%s.txt' % (image_id), 'w')
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()
print(wd)
for image_set in sets:
if not os.path.exists('data/labels/'):
os.makedirs('data/labels/')
image_ids = open('data/ImageSets/%s.txt' % (image_set)).read().strip().split()
list_file = open('data/%s.txt' % (image_set), 'w')
for image_id in image_ids:
list_file.write('data/images/%s.jpg\n' % (image_id))
convert_annotation(image_id)
list_file.close()
注:classes=【‘person’】裏面的標籤並不是我們定的,請大家打開自己數據集的xml文件,類目頭標籤是是啥填啥:
3.6 運行標籤文件:
- 運行makeTxt.py後,在ImagesSets文件夾裏面生成4個文件:
- 運行makeTxt.py後,在Iabels文件夾裏面生成文件名以及image中目標的位置信息:
3.7 配置文件:
在data文件下新建person.names文件,內容如下:
person
在data文件下新建person.data文件,內容如下:
classes=1#只檢測一個類,原爲80個
train=data/train.txt
valid=data/test.txt
names=data/person.names#讀取類目信息
backup=backup/
eval=coco#配置標準與coco數據集一致
3.8 修改參數文件:
修改原yolov3-spp.cfg文件爲(可直接拷貝):
[net]
# Testing
# batch=1
# subdivisions=1
# Training
batch=64
subdivisions=16
width=608
height=608
channels=3
momentum=0.9
decay=0.0005
angle=0
saturation = 1.5
exposure = 1.5
hue=.1
[convolutional]
size=1
stride=1
pad=1
filters=18
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=1
num=9
jitter=.3
ignore_thresh = .7
truth_thresh = 1
random=1
[route]
layers = -4
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky
[upsample]
stride=2
[route]
layers = -1, 61
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky
[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=512
activation=leaky
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky
[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=512
activation=leaky
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky
[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=512
activation=leaky
[convolutional]
size=1
stride=1
pad=1
filters=18
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=1
num=9
jitter=.3
ignore_thresh = .7
truth_thresh = 1
random=1
[route]
layers = -4
[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky
[upsample]
stride=2
[route]
layers = -1, 36
[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky
[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=256
activation=leaky
[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky
[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=256
activation=leaky
[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky
[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=256
activation=leaky
[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 = .7
truth_thresh = 1
random=1
提一下,主要修改的是三個yolo層附近的參數,一共六處地方:
修改如下:
①filters=255改爲18:即3*(classes+5),單類的話classes=1
②classes=80改爲1,只檢測person一類
其他參數根據自己需要進行修改:
①如果想修改anchor的值,需要根據kmeans聚類跑自己的數據集得出結果,代碼地址:https://github.com/lars76/kmeans-anchor-boxes
②如果GPU顯存較小,可以設置將random=1設置爲0
③當單類是cls爲0
3.10 配置預權重:
預權重需要哪個根據自己修改的cfg文件進行選擇,以下附一張圖。但要提一點,如果不想在作者訓練好的權重再加以訓練,想要從頭開始訓練,選擇darknet53.conv.74。
3.11 訓練模型:
使用pycharm中的Terminal,輸入如下命令:
python train.py --data data/person.data --cfg cfg/yolov3-spp.cfg --weights weights/darknet53.conv.74 --epochs 100 --batch-size 32
其中,–epochs 10指迭代了10次,batch-size 32指每次處理32張圖片。
①如果GPU顯存不夠的,會報出錯誤:
②batch-size 最好在32以上,並且爲2的指數倍,不然會不定時出現nan問題:
③數據集不要包括黑白圖片,請保持RBG通道一致,不然會報錯:
④,有條件的,請開啓多尺度訓練,提高模型精度,具體命令如下:
python train.py --data data/person.data --cfg cfg/yolov3-spp.cfg --weights weights/darknet53.conv.74 --epochs 100 --batch-size 32 --multi-scale
正常運行如下:
(自己筆記本顯存不夠,通過Google Colab訓練)
訓練結束後得到模型best.pt和last.pt:
4. 結果檢測:
在Terminal下輸入以下命令:
python detect.py --names data/person.names --cfg cfg/yolov3-spp.cfg --weights weights/best.pt
結果如下:
結果倒是出乎自己的預期
5. 模型評估與測試
模型仍有待優化,近期不定時更新該博文!