1. 安裝darknet
使用Git克隆源碼
git clone https://github.com/pjreddie/darknet
我們可能需要修改Makefile
cd darknet
gedit Makefile
主要修改前三行,配置使用GPU(CUDA),CUDNN,OPENCV
GPU=1
CUDNN=1
OPENCV=1
之后運行
make -j8
wget https://pjreddie.com/media/files/yolov3.weights
./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg
安裝成功!
2. 準(zhǔn)備數(shù)據(jù)集
使用LabelImg工具對圖片進(jìn)行標(biāo)注,LabelImg安裝和使用方法請自行百度。標(biāo)注完成后得到兩個文件夾Annotations和JPEGImages,分別存放xml格式標(biāo)注內(nèi)容和圖片。
在scripts文件夾下構(gòu)建目錄樹
mkdir -p scripts/VOCdevkit/VOC2007
cd scripts/VOCdevkit/VOC2007
mkdir ImageSets
cd ImageSets
mkdir Layout Main Segmentation
此時scripts文件夾下目錄樹應(yīng)當(dāng)為:

然后將Annotations和JPEGImages文件夾復(fù)制到VOC2007目錄下
值得注意的是,VOC2007的年份2007應(yīng)當(dāng)和xml標(biāo)注文件中的年份相同
接下來把traindata.py和trans.py拷貝到VOC2007目錄下
其中traindata.py內(nèi)容是:
#!/usr/bin/env python3
import os
import shutil
def rename_by_count(path): #按序號命名
count = 1000
filelist = os.listdir(path) # 該文件夾下所有的文件(包括文件夾)
for files in filelist: # 遍歷所有文件
Olddir = os.path.join(path, files) # 原來的文件路徑
if os.path.isdir(Olddir): # 如果是文件夾則跳過
continue
filename = os.path.splitext(files)[0] # 文件名
filetype = os.path.splitext(files)[1] # 文件擴展名
Newdir = os.path.join(path, str(count) + filetype) # 新的文件路徑
os.rename(Olddir, Newdir) # 重命名
count += 1
def listname(path,idtxtpath):
filelist = os.listdir(path) # 該文件夾下所有的文件(包括文件夾)
f = open(idtxtpath, 'w')
for files in filelist: # 遍歷所有文件
Olddir = os.path.join(path, files) # 原來的文件路徑
if os.path.isdir(Olddir): # 如果是文件夾則跳過
continue
filename = os.path.splitext(files)[0] # 文件名
filetype = os.path.splitext(files)[1] # 文件擴展名
#Newdir = os.path.join(path, "1000" + filetype) # 新的文件路徑: path+filename+type
f.write(filename)
f.write('\n')
f.close()
def imgid_list(imgpath, savepath, num):
#rename_by_count(imgpath)
path1 = savepath + "/validateImage"
path2 = savepath + "/trainImage"
if os.path.exists(path1)== False:
os.mkdir(path1)
if os.path.exists(path2) == False:
os.mkdir(path2)
xmlpath1 = savepath + "/validateImageXML"
xmlpath2 = savepath + "/trainImageXML"
if os.path.exists(xmlpath1)== False:
os.mkdir(xmlpath1)
if os.path.exists(xmlpath2) == False:
os.mkdir(xmlpath2)
filelist = os.listdir(imgpath)
count = 0
for files in filelist:
olddir = os.path.join(imgpath, files)
newdir1 = os.path.join(path1, files)
newdir2 = os.path.join(path2, files)
filename = os.path.splitext(files)[0] # 文件名
xmldir = savepath + "/xml"
xmldir1 = savepath + "/validateImageXML"
xmldir2 = savepath + "/trainImageXML"
if count<num:
shutil.copy(olddir, newdir1) #validate
xmlolddir = os.path.join(xmldir, filename + ".xml")
xmlnewdir = os.path.join(xmldir1,filename+".xml")
shutil.copy(xmlolddir,xmlnewdir)
shutil.copy(xmlolddir, newdir1)
else:
shutil.copy(olddir, newdir2)
xmlolddir = os.path.join(xmldir, filename + ".xml")
xmlnewdir = os.path.join(xmldir2, filename + ".xml")
shutil.copy(xmlolddir, xmlnewdir)
shutil.copy(xmlolddir, newdir2)
count=count+1
imgidtxtpath1 = savepath + "/validateImageId.txt"
imgidtxtpath2 = savepath + "/trainImageId.txt"
listname(path1, imgidtxtpath1)
listname(path2, imgidtxtpath2)
#rename_by_count # 給圖片按序號給名字
savepath = os.getcwd()
imgpath = savepath+"/Image"
val_num=1440 #驗證集數(shù)量,可修改
imgid_list(imgpath,savepath,val_num)
trans.py內(nèi)容是:
#!/usr/bin/env python3
import xml.etree.ElementTree as ET
import pickle
import string
import os
import shutil
from os import listdir, getcwd
from os.path import join
sets=[('2007', 'train'), ('2007', 'val')]
classes = ["airplane_gro", "airplane_air", "fix_UAV_gro", "fix_UAV_air", "rotor_UAV_gro", "rotor_UAV_air", "airship_gro", "airship_air"]
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,flag,savepath):
#s = '\xef\xbb\xbf'
#nPos = image_id.index(s)
#if nPos >= 0:
# image_id = image_id[3:]
if flag == 0:
in_file = open(savepath+'/trainImageXML/%s.xml' % (image_id))
labeltxt = savepath+'/trainImageLabelTxt'
if os.path.exists(labeltxt) == False:
os.mkdir(labeltxt);
out_file = open(savepath+'/trainImageLabelTxt/%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)
elif flag == 1:
in_file = open(savepath+'/validateImageXML/%s.xml' % (image_id))
labeltxt = savepath + '/validateImageLabelTxt'
if os.path.exists(labeltxt) == False:
os.mkdir(labeltxt);
out_file = open(savepath+'/validateImageLabelTxt/%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()
for year, image_set in sets:
#savepath = "/home/wurui/CAR/wrz/pillar"
savepath = os.getcwd()
idtxt = savepath + "/validateImageId.txt"
pathtxt = savepath + "/validateImagePath.txt"
image_ids = open(idtxt).read().strip().split()
list_file = open(pathtxt, 'w')
s = '\xef\xbb\xbf'
for image_id in image_ids:
nPos = image_id.find(s)
if nPos >= 0:
image_id = image_id[3:]
list_file.write('%s/validateImage/%s.jpg\n' % (wd, image_id))
print(image_id)
convert_annotation(image_id, 1, savepath)
list_file.close()
idtxt = savepath + "/trainImageId.txt"
pathtxt = savepath + "/trainImagePath.txt"
image_ids = open(idtxt).read().strip().split()
list_file = open(pathtxt, 'w')
s = '\xef\xbb\xbf'
for image_id in image_ids:
nPos = image_id.find(s)
if nPos >= 0:
image_id = image_id[3:]
list_file.write('%s/trainImage/%s.jpg\n'%(wd,image_id))
print(image_id)
convert_annotation(image_id,0,savepath)
list_file.close()
首先拷貝Annotations文件夾為xml,拷貝JPEGImages為Image
修改traindata.py中驗證集數(shù)量val_num,推薦為總數(shù)據(jù)集的30%,修改trans.py中的sets和classes
執(zhí)行
python3 traindata.py
python3 trans.py
將生成的trainImageID.txt和validateImageID.txt拷貝到ImageSets的Main目錄下,并分別重命名為train.txt和val.txt,刪除執(zhí)行traindata.py和trans.py生成的所有文件和文件夾以及xml和Image文件夾
回到scripts文件夾
修改voc_label.py,修改sets和classes的值,并將最后的兩行
os.system("cat 2007_train.txt 2007_val.txt 2012_train.txt 2012_val.txt > train.txt")
os.system("cat 2007_train.txt 2007_val.txt 2007_test.txt 2012_train.txt 2012_val.txt > train.all.txt")
改成
os.system("cat 2007_train.txt 2007_val.txt > train.txt")
# os.system("cat 2007_train.txt 2007_val.txt 2007_test.txt 2012_train.txt 2012_val.txt > train.all.txt")
最后執(zhí)行
python3 voc_label.py
至此,數(shù)據(jù)集準(zhǔn)備完畢
3. 修改配置文件
回到darknet根目錄下
3.1 data/
修改voc.names
修改為需要識別的類名稱,每行一類
3.2 cfg/
修改yolov3.cfg
[net]
# Testing 初始batch參數(shù)要分為兩類,分別為訓(xùn)練集和測試集,不同模式相應(yīng)放開參數(shù),#為注釋符號
#batch=1
#subdivisions=1
# Training
batch=64 一批訓(xùn)練樣本的樣本數(shù)量,每batch個樣本更新一次參數(shù)
subdivisions=8 batch/subdivisions作為一次性送入訓(xùn)練器的樣本數(shù)量
如果內(nèi)存不夠大,將batch分割為subdivisions個子batch
(subdivisions相當(dāng)于分組個數(shù),相除結(jié)果作為一次送入訓(xùn)練器的樣本數(shù)量)
注意:上面這兩個參數(shù)如果電腦內(nèi)存小,則把batch改小一點,batch越大,訓(xùn)練效果越好
Subdivisions越大,可以減輕顯卡壓力(分組數(shù)目越多,每組樣本數(shù)量則會更少,顯卡壓力也會相應(yīng)減少)
width=416
height=416
channels=3
以上三個參數(shù)為輸入圖像的參數(shù)信息 width和height影響網(wǎng)絡(luò)對輸入圖像的分辨率,從而影響precision,只可以設(shè)置成32的倍數(shù)(為什么是32?由于使用了下采樣參數(shù)是32,所以不同的尺寸大小也選擇為32的倍數(shù){320,352…..608},最小320*320,最大608*608,網(wǎng)絡(luò)會自動改變尺寸,并繼續(xù)訓(xùn)練的過程。)
momentum=0.9 DeepLearning1中最優(yōu)化方法中的動量參數(shù),這個值影響著梯度下降到最優(yōu)值得速度 (注:SGD方法的一個缺點是其更新方向完全依賴于當(dāng)前batch計算出的梯度,因而十分不穩(wěn)定。Momentum算法借用了物理中的動量概念,它模擬的是物體運動時的慣性,即更新的時候在一定程度上保留之前更新的方向,同時利用當(dāng)前batch的梯度微調(diào)最終的更新方向。這樣一來,可以在一定程度上增加穩(wěn)定性,從而學(xué)習(xí)地更快,并且還有一定擺脫局部最優(yōu)的能力)
decay=0.0005 權(quán)重衰減正則項,防止過擬合,正則項往往有重要意義
//增加樣本的數(shù)量,改變基礎(chǔ)樣本的狀態(tài),去增加樣本整體的數(shù)量,增加樣本量減少過擬合
angle=0 通過旋轉(zhuǎn)角度來生成更多訓(xùn)練樣本
saturation = 1.5 通過調(diào)整飽和度來生成更多訓(xùn)練樣本
exposure = 1.5 通過調(diào)整曝光量來生成更多訓(xùn)練樣本
hue=.1 通過調(diào)整色調(diào)來生成更多訓(xùn)練樣本
learning_rate=0.001
學(xué)習(xí)率決定著權(quán)值更新的速度,設(shè)置得太大會使結(jié)果超過最優(yōu)值,直接錯過最優(yōu)值,震蕩回去,太小會使下降速度過慢,導(dǎo)致收斂過慢。如果僅靠人為干預(yù)調(diào)整參數(shù),需要不斷修改學(xué)習(xí)率。剛開始訓(xùn)練時可以將學(xué)習(xí)率設(shè)置的高一點,而一定輪數(shù)之后,將其減小。在訓(xùn)練過程中,一般根據(jù)訓(xùn)練輪數(shù)設(shè)置動態(tài)變化的學(xué)習(xí)率。
基本訓(xùn)練守則
剛開始訓(xùn)練時:學(xué)習(xí)率以 0.01 ~ 0.001 為宜。
一定輪數(shù)過后:逐漸減緩。
接近訓(xùn)練結(jié)束:學(xué)習(xí)速率的衰減應(yīng)該在100倍以上。
提供參考資料學(xué)習(xí)率的調(diào)整參考https://blog.csdn.net/qq_33485434/article/details/80452941
burn_in=1000 在迭代次數(shù)小于burn_in時,其學(xué)習(xí)率的更新有一種方式,大于burn_in時,才采用policy的更新方式
max_batches = 500200 訓(xùn)練達(dá)到max_batches后停止學(xué)習(xí),多個batches
policy=steps 這個是學(xué)習(xí)率調(diào)整的策略,有policy:constant, steps, exp, poly, step, sig, RANDOM,constant等方式
調(diào)整學(xué)習(xí)率的policy,有如下policy:constant, steps, exp, poly, step, sig, RANDOM
constant
保持學(xué)習(xí)率為常量,caffe里為fixed
steps
比較好理解,按照steps來改變學(xué)習(xí)率
Steps和scales相互一一對應(yīng)
steps=40000,45000 下面這兩個參數(shù)steps和scale是設(shè)置學(xué)習(xí)率的變化,比如迭代到40000次時,學(xué)習(xí)率衰減十倍。45000次迭代時,學(xué)習(xí)率又會在前一個學(xué)習(xí)率的基礎(chǔ)上衰減十倍。根據(jù)batch_num調(diào)整學(xué)習(xí)率
scales=,.1,.1 學(xué)習(xí)率變化的比例,累計相乘
涉及幾個參數(shù)(以后要學(xué)習(xí)的代碼,具體參數(shù)可以調(diào)節(jié))
exp
gamma=
返回base_lr*gamma^iter,iter為當(dāng)前迭代次數(shù),gamma設(shè)置為0.98
poly
power=4
max_batches=800000
對學(xué)習(xí)率進(jìn)行多項式衰減。圖中power為0.9
sig
學(xué)習(xí)率進(jìn)行sigmod函數(shù)衰減
gamma= 0.05
step=200
根據(jù)電腦配置,主要是顯卡能力調(diào)整參數(shù)
修改每一個[yolo]前面四行的filters和后面三行的classes,注意需要修改3處
[convolutional]
size=1
stride=1
pad=1
filters=255 # 改成3*(classes + 5)
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=80 # 修改為實際需要識別的類數(shù)量
num=9
jitter=.3
ignore_thresh = .7
truth_thresh = 1
random=1
修改voc.data為
classes= 8 # 實際需要識別的類
train = /home/ubuntu/darknet/scripts/2007_train.txt # 指向剛剛生成的文件2007_train.txt
valid = /home/ubuntu/darknet/scripts/2007_val.txt # 指向剛剛生成的文件2007_val.txt
names = data/voc.names
backup = backup
3.3 examples/
修改detector.c
void validate_detector_flip(char *datacfg, char *cfgfile, char *weightfile, char *outfile)
{
int j;
list *options = read_data_cfg(datacfg);
char *valid_images = option_find_str(options, "valid", "data/train.list");
// char *name_list = option_find_str(options, "names", "data/names.list");
char *name_list = option_find_str(options, "names", "data/voc.names");
char *prefix = option_find_str(options, "results", "results");
char **names = get_labels(name_list);
char *mapf = option_find_str(options, "map", 0);
int *map = 0;
if (mapf) map = read_map(mapf);
有幾個函數(shù)中需要修改,建議直接搜索
修改darknet.c
float thresh = find_float_arg(argc, argv, "-thresh", .5);
char *filename = (argc > 4) ? argv[4]: 0;
char *outfile = find_char_arg(argc, argv, "-out", 0);
int fullscreen = find_arg(argc, argv, "-fullscreen");
// test_detector("cfg/coco.data", argv[2], argv[3], filename, thresh, .5, outfile, fullscreen);
test_detector("cfg/voc.data", argv[2], argv[3], filename, thresh, .5, outfile, fullscreen);
} else if (0 == strcmp(argv[1], "cifar")){
run_cifar(argc, argv);
這里也有幾處需要修改
4 訓(xùn)練
執(zhí)行
./darknet detector train cfg/voc.data cfg/yolov3.cfg darknet53.conv.74 -gpus 0,1