yolov3訓練自己資料集可參考文章
這篇文章將介紹編譯darknet框架開始,到整理資料集,到用yolo網路實現一個內部資料集中號碼簿的定位。
1. DarkNet的編譯
- 當前的環境:亞馬遜GPU例項P2:Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz + K80 + Ubuntu16.04
1.1 環境準備:
sudo -i
#先改軟體源:
[email protected] ****:~# cd /etc/apt/
[email protected]****:/etc/apt# sudo cp sources.list sources.list.bak
[email protected]****:/etc/apt# sudo vi /etc/apt/sources.list
>>>
deb http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse
##測試版源
deb http://mirrors.aliyun.com/ubuntu/ xenial-proposed main restricted universe multiverse
# 原始碼
deb-src http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse
##測試版源
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-proposed main restricted universe multiverse
# Canonical 合作伙伴和附加
deb http://archive.canonical.com/ubuntu/ xenial partner
deb http://extras.ubuntu.com/ubuntu/ xenial main
[email protected]****:/etc/apt# sudo apt-get update
#安裝rz:
apt install lrzsz
#檢視opencv是否可用
pip install --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple
import cv2 #沒有報錯說明本機opencv可以使用
nvcc -V #檢視cuda是否可用
#下載工程並嘗試編譯
git clone https://github.com/pjreddie/darknet
cd darknet
vim Makefile:
... GPU=1
... CUDNN=1
... OPENCV=1
make -j4
#下載多執行緒下載器:
wget https://jaist.dl.sourceforge.net/project/kmphpfm/mwget/0.1/mwget_0.1.0.orig.tar.bz2
tar -xjf mwget_0.1.0.orig.tar.bz2
./build
make -j4
make install
- 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
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
1.2 配置ftp伺服器:
因為在遠端終端環境,有很多測試圖片我們看不到,我們就可以用ftp來從web訪問這些圖片
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
#例項化虛擬使用者,這是FTP驗證首要條件
authorizer = DummyAuthorizer()
#新增使用者許可權和路徑,括號內的引數是(使用者名稱, 密碼, 使用者目錄, 許可權)
authorizer.add_user('user', '12345', '/root', perm='elradfmw')
#新增匿名使用者 只需要路徑
authorizer.add_anonymous('/root')
#初始化ftp控制代碼
handler = FTPHandler
handler.authorizer = authorizer
#監聽ip 和 埠,因為linux裡非root使用者無法使用21埠,所以我使用了2121埠
server = FTPServer(('192.168.1.151', 21), handler)
#開始服務
server.serve_forever()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
如你的伺服器沒有固定ip的話,在安裝lrzsz後,可以使用sz file命令將檔案下載下來。
2.資料準備:
先把資料集傳上去
因為在公司內網,沒有許可權設定埠轉發。所以不能在本地搭建FTP,然後在伺服器上下載。
所以之前安裝rz,現在只需要講資料集壓縮打包為zip,然後拖到xshell視窗中即可上傳。
然後用unzip+檔名解壓。
2.1 DarkNet讀取資料的標準:
所有的.xml標籤需要按照voc的格式來記錄資料,因為後面需要voc_label.py按照這個格式來歸一化資料集。
拿到資料後,要先把所有的資料放到一個資料夾中。一類是.xml的voc格式的標籤檔案,一類是圖片檔案。
之所以要把標籤和資料都放在一個資料夾裡是因為,我們要檢查是否所有的標籤都有對應的資料,或者資料有對應的標籤。
我寫了一個指令碼來處理這件事情,它做的功能是如果有某個檔案沒有對應的標籤或者資料集(我稱它為單身檔案),就將其刪除。最終資料夾內都是情侶檔案:
大家用這個指令碼的時候需要根據自己的資料集路徑稍微修改一下。
'''
清除單身的xml或者jpg
在Windows執行
'''
import os
import os.path
h = 0
a = ''
b = ''
dele = []
pathh = "C:\\Users\\於祥\\Desktop\\pic\\"
#dele.remove(1)
for filenames in os.walk(pathh):
filenames = list(filenames)
filenames = filenames[2]
for filename in filenames:
print(filename)
if h==0:
a = filename
h = 1
elif h==1:
#print(filename)
b = filename
if a[0:a.rfind('.', 1)]==b[0:b.rfind('.', 1)]:
h = 0
#print(filename)
else:
h = 1
dele.append(a)
a = b
else:
print("wa1")
print(dele)
for file in dele:
os.remove(pathh+file)
print("remove"+file+" is OK!")
#再迴圈一次看看有沒有遺漏的單身檔案
for filenames in os.walk(pathh):
filenames = list(filenames)
filenames = filenames[2]
for filename in filenames:
print(filename)
if h==0:
a = filename
h = 1
elif h==1:
#print(filename)
b = filename
if a[0:a.rfind('.', 1)]==b[0:b.rfind('.', 1)]:
h = 0
#print(filename)
else:
h = 1
dele.append(a)
a = b
else:
print("wa1")
print (dele)
- 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
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
現在這些檔案都是情侶檔案了。
然後要把圖片和標籤分開,分別存在Annotations和pic檔案下里
2.2 製作box_train.txt 和 box_test.txt檔案:
然後我們要將每張資料圖片的地址給列出來,放到box_train.txt 和 box_test.txt裡,因為不僅歸一化指令碼要用到box_train.txt來讀取檔案,DarkNet在訓練時也要從中讀取檔案。
# -*- coding: utf-8 -*-
import os
import os.path
pathh = "/root/darknet/hebing2/pic/"
for filenames in os.walk(pathh):
filenames = list(filenames)
filenames = filenames[2]
for filename in filenames:
print(filename)
with open ("box_train.txt",'a') as f:
f.write(pathh+filename+'\n')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
這樣,在工程目錄下就有一個box_train.txt了。我們選一部分剪下進box_test.txt中。
2.3 然後我們需要用voc_label.py歸一化標籤
voc_label.py讀取資料的格式為以下格式:
資料集資料夾名
|—Annotations #存放xml標籤
|—labels #存放DarkNet可以識別的歸一化後的標籤檔案,我後面的指令碼會自動建立
|—pic #存放資料集
因為原始碼中的voc_label.py是針對voc資料集的,無論路徑上還是資料格式都和我們自己的資料有出入,所以我們要自己編寫voc_label.py來處理自己的資料。比如我的供大家參考:
(我這個資料集標準人員在標註的時候有錯誤標籤了應該是號碼簿代號box,他們給標成了號碼簿的號碼,所以我在41-46行加了一步處理,請大家忽略)
# -*- coding: utf-8 -*-
#此指令碼需要執行兩次,在91行train_number執行一次,test_number執行一次
import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join
sets = []
classes = ["box"]
#原樣保留。size為圖片大小
# 將ROI的座標轉換為yolo需要的座標
# size是圖片的w和h
# box裡儲存的是ROI的座標(x,y的最大值和最小值)
# 返回值為ROI中心點相對於圖片大小的比例座標,和ROI的w、h相對於圖片大小的比例
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)
#對於單個xml的處理
def convert_annotation(image_add):
#image_add進來的是帶地址的.jpg
image_add = os.path.split(image_add)[1] #擷取檔名帶字尾
image_add = image_add[0:image_add.find('.',1)] #刪除字尾,現在只有檔名沒有後綴
in_file = open('/root/darknet/hebing2/Annotations/'+ image_add+'.xml')
print('now write to: hebing2/labels/%s.txt'%(image_add))
out_file = open('hebing2/labels/%s.txt'%(image_add), 'w')
tree=ET.parse(in_file)
root = tree.getroot()
#加入我的預處理<name>程式碼:
for obj in root.findall("object"):
#obj.append("number") = obj.find('name').text
obj.find('name').text = "box"
print(obj.find('name').text)
tree.write('/root/darknet/hebing2/Annotations/'+ image_add + '.xml')
size = root.find('size')
# <size>
# <width>500</width>
# <height>333</height>
# <depth>3</depth>
# </size>
w = int(size.find('width').text)
h = int(size.find('height').text)
#在一個XML中每個Object的迭代
for obj in root.iter('object'):
#iter()方法可以遞迴遍歷元素/樹的所有子元素
'''
<object>
<name>car</name>
<pose>Unspecified</pose>
<truncated>1</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>141</xmin>
<ymin>50</ymin>
<xmax>500</xmax>
<ymax>330</ymax>
</bndbox>
</object>
'''
difficult = obj.find('difficult').text
#找到所有的椅子
cls = obj.find('name').text
#如果訓練標籤中的品種不在程式預定品種,或者difficult = 1,跳過此object
if cls not in classes or int(difficult)==1:
continue
#cls_id 只等於1
cls_id = classes.index(cls)
xmlbox = obj.find('bndbox')
#b是每個Object中,一個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')
if not os.path.exists('hebing2/labels/'):
os.makedirs('hebing2/labels/')
image_adds = open("box_train.txt")
for image_add in image_adds:
image_add = image_add.strip()
print (image_add)
convert_annotation(image_add)
'''voc
wd = getcwd()
for year, image_set in sets:
#如果lebal資料夾中不存在年份資料夾則建立
if not os.path.exists('VOCdevkit/VOC%s/labels/'%(year)):
os.makedirs('VOCdevkit/VOC%s/labels/'%(year))
#000034的這種id
image_ids = open('VOCdevkit/VOC%s/ImageSets/Main/%s.txt'%(year, image_set)).read().strip().split()
#2007_test.txt的這種檔案
list_file = open('%s_%s.txt'%(year, image_set), 'w')
for image_id in image_ids:
#/home/yuxiang/Desktop/darknet/VOCdevkit/VOC2007/JPEGImages/000001.jpg
list_file.write('%s/VOCdevkit/VOC%s/JPEGImages/%s.jpg\n'%(wd, year, image_id))
#/VOC%s/ImageSets/Main/train.txt中每一個image_id都要被執行這個方法
convert_annotation(year, image_id)
list_file.close()
'''
- 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
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
如果有個別檔案找不到。或者有除0異常的話,去box_train.txt中dd那一行就行。
現在就已經在label/中打好標籤了
對於除voc外普通資料集,darknet是會在圖片目錄下尋找label.txt。進入label/後使用cp * ../pic 整合
3 配置darknet檔案:
vim cfg/box.data :
… classes = 1
… train = /root/darknet/box_train.txt
… valid = /root/darknet/box_text.txt
… names = data/box.names
… backup = backupvim data/box.name:
… boxcfg/yolov3.cfg:
首先要把net層最頂上兩行的Testing註釋掉,把Training開啟。
後面的Width和height暫時不要懂。因為Yolo行的錨點是根據這個又做了一些聚類算出來的。
裡面3個yolo層上面的conv層的filters都要修改,修改公式為3 * (classes + 5)。例如我的就是18
所有的的yolo層class改為1mkdir ./backup
4.訓練
./darknet detector train cfg/box.data cfg/yolov3.cfg darknet53.conv.74.0
- 1
————————
現在就開始訓練了。在訓練過程中也可以呼叫一下訓練時候階段輸出的權重來測試一下。
使用在/backup中找到每百次的訓練權重,然後使用命令測試一下:
./darknet detect cfg/yolov3.cfg backup/yolov3_801.weights -thresh 0
- 1
時候會讓你輸入圖片地址,當然你也可以在引數上加上圖片地址。
訓練時我們可以再開一個終端,通過nvidia-smi命令來檢視cuda和視訊記憶體的使用情況
5.DarkNet的Python介面:
注意使用Python測試的時候必須要有GPU和安裝好cuda驅動,因為作者提供的Python的介面只有GPU呼叫函式
作者給出的使用的Demo為:
# Stupid python path shit.
# Instead just add darknet.py to somewhere in your python path
# OK actually that might not be a great idea, idk, work in progress
# Use at your own risk. or don't, i don't care
import sys, os
sys.path.append(os.path.join(os.getcwd(),'python/'))
import darknet as dn
import pdb
dn.set_gpu(0)
net = dn.load_net("cfg/yolo-thor.cfg", "/home/pjreddie/backup/yolo-thor_final.weights", 0)
meta = dn.load_meta("cfg/thor.data")
r = dn.detect(net, meta, "data/bedroom.jpg")
print r
# And then down here you could detect a lot more images like:
r = dn.detect(net, meta, "data/eagle.jpg")
print r
r = dn.detect(net, meta, "data/giraffe.jpg")
print r
r = dn.detect(net, meta, "data/horses.jpg")
print r
r = dn.detect(net, meta, "data/person.jpg")
print r
- 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
我們不難看出除了系統模組之外,只調用了darknet.py模組,存放在工程的python目錄裡。
但darknet.py中有一些路徑是軟地址要改一下(在47行中,仿照著註釋的格式):
#lib = CDLL("/home/pjreddie/documents/darknet/libdarknet.so", RTLD_GLOBAL)
lib = CDLL("libdarknet.so", RTLD_GLOBAL)
- 1
- 2
但這個模組是基於Python2編寫,如果直接使用Python2執行的話還會有一些包存在路徑問題,哪我們不如將他稍微修改一下:
在154行,即最後一行中,print需要加上括號
print (r)
- 1
這樣,我們在工程目錄下進行模組匯入就沒有任何問題了。
我們仿照著把配置檔案路徑和要測試的圖片地址改好就可以了。比如我的:
In [10]: dn = set_gpu(0)
...: net = dn.load_net("cfg/yolov3.cfg", "/home/pjreddie/backup/yolov3_400.weights", 0)
...: meta = dn.load_meta("cfg/number.data")
...: r = dn.detect(net, meta, "zhebing/pic/20171105_636454947681503257_P.jpg")
...: print (r)
- 1
- 2
- 3
- 4
- 5
就可以顯示出測試影象啦。
這篇文章將介紹編譯darknet框架開始,到整理資料集,到用yolo網路實現一個內部資料集中號碼簿的定位。