yolov3安卓实现_从零实现YOLOv3
一、安裝Darknet
從官網(wǎng)配置darknet環(huán)境,這一步也可以看yolo作者的網(wǎng)站來進(jìn)行,
git clone https://github.com/pjreddie/darknet.git
cd darknet
make
也可以用其他基于原作者實現(xiàn)的地址(有詳細(xì)一些的文檔),然后cd到根文件下,首次使用以及修改過.c文件、.cfg文件后都需要make一下。
在darknet中,有一個Makefile,這個文件定義了網(wǎng)絡(luò),如果需要使用opencv或者GPU、CUDNN都需要編輯Makefile,設(shè)置等于1;默認(rèn)都是0的。注意,修改過Makefile以后必須重新make才能生效!
不好下的,戳這里百度網(wǎng)盤,密碼:7zuk。
現(xiàn)在,框架有了,可以嘗試一下檢測圖片,如果要檢測圖片,必須下載一個預(yù)訓(xùn)練權(quán)重,200+mb,可以直接用命令
wget https://pjreddie.com/media/files/yolov3.weights
或者[戳百度網(wǎng)盤]() 注意下載下來的權(quán)重文件要放到darknet根目錄下。
然后使用下面的命令嘗試是否可以正常運行(單張圖片檢測)
./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg
這個命令分解開來就是:
,,,
能看到下面這樣一個過程,構(gòu)建網(wǎng)絡(luò),然后測試data/dog.jpg,輸出置信度。
layer filters size input output
0 conv 32 3 x 3 / 1 416 x 416 x 3 -> 416 x 416 x 32 0.299 BFLOPs
1 conv 64 3 x 3 / 2 416 x 416 x 32 -> 208 x 208 x 64 1.595 BFLOPs
.......
105 conv 255 1 x 1 / 1 52 x 52 x 256 -> 52 x 52 x 255 0.353 BFLOPs
106 detection
truth_thresh: Using default '1.000000'
Loading weights from yolov3.weights...Done!
data/dog.jpg: Predicted in 0.029329 seconds.
dog: 99%
truck: 93%
bicycle: 99%
如果設(shè)置使用了opencv會彈出來下面的圖,如果沒有,這張圖會保存在darknet根目錄下,可以自己打開看。
作者添加了類似上面的圖片的供檢測的圖片,可以在目錄中找到,data/eagle.jpg,data/dog.jpg,data/person.jpg, 和data/horses.jpg
對于多圖片檢測(一個一個的檢測),輸入:
./darknet detect cfg/yolov3.cfg yolov3.weights
然后等構(gòu)建完網(wǎng)絡(luò)和載入權(quán)重后,會要求你輸入路徑,輸入后回車,檢測完成可以直接輸入下一個。
至此,yolo已經(jīng)可以正常運行了。
二、制作自己的數(shù)據(jù)集
這一部分分為兩種情況,第一是自己標(biāo)注數(shù)據(jù)集,可以使用labelimg來進(jìn)行標(biāo)記;第二就是將其他數(shù)據(jù)集格式轉(zhuǎn)換成yolo可以使用的數(shù)據(jù)集。
2.1 使用labelimg標(biāo)注數(shù)據(jù)集
labelimg的下載安裝戳這里
界面長這樣
可以支持yolo格式,選一下就好了。
在標(biāo)注之前,比較省事的是把自己要標(biāo)注的圖片名字改成編號,比較好方便使用,因為生成的txt標(biāo)注文件是和圖片同名的,后面不好改。
然后設(shè)置目錄,現(xiàn)在是中文了,直接鼠標(biāo)拉框就行了。
可以在右側(cè)設(shè)置默認(rèn)的標(biāo)簽,這樣就不用每拉一個框就要點選了。
標(biāo)記的txt文件中,是yolo格式的,分別是類別(0是設(shè)置的第一個類別)和坐標(biāo)。
2.2 轉(zhuǎn)換其他數(shù)據(jù)集
yolo支持VOC格式的數(shù)據(jù)集,所使用的標(biāo)記文件都是單獨的一個txt格式的文件,存放一張圖片的標(biāo)記信息,所以思路就是:
如果所有標(biāo)記文件都在一個文件中,那就先提取出來單獨的xml文件,然后將這些文件轉(zhuǎn)換為txt格式;
如果標(biāo)記文件是單獨的xml文件,只需要將xml文件轉(zhuǎn)換為txt文件就可以了;
上次實驗室使用的是DETRAC數(shù)據(jù)集,下載地址,包括三個壓縮文件
DETRAC-train-data
DETRAC-test-data
DETRAC-Train-Annotations-XML
第一步
提取出voc格式的xml文件
import xml.etree.ElementTree as ET
from xml.dom.minidom import Document
import os
import cv2
import time
def ConvertVOCXml(file_path="",file_name=""):
tree = ET.parse(file_name)
root = tree.getroot()
# print(root.tag)
num=0 #計數(shù)
#讀xml操作
frame_lists=[]
output_file_name=""
for child in root:
if(child.tag=="frame"):
# 創(chuàng)建dom文檔
doc = Document()
# 創(chuàng)建根節(jié)點
annotation = doc.createElement('annotation')
# 根節(jié)點插入dom樹
doc.appendChild(annotation)
#print(child.tag, child.attrib["num"])
pic_id= child.attrib["num"].zfill(5)
#print(pic_id)
output_file_name=root.attrib["name"]+"__img"+pic_id+".xml"
# print(output_file_name)
folder = doc.createElement("folder")
folder.appendChild(doc.createTextNode("VOC2007"))
annotation.appendChild(folder)
filename = doc.createElement("filename")
pic_name="img"+pic_id+".jpg"
filename.appendChild(doc.createTextNode(pic_name))
annotation.appendChild(filename)
sizeimage = doc.createElement("size")
imagewidth = doc.createElement("width")
imageheight = doc.createElement("height")
imagedepth = doc.createElement("depth")
imagewidth.appendChild(doc.createTextNode("960"))
imageheight.appendChild(doc.createTextNode("540"))
imagedepth.appendChild(doc.createTextNode("3"))
sizeimage.appendChild(imagedepth)
sizeimage.appendChild(imagewidth)
sizeimage.appendChild(imageheight)
annotation.appendChild(sizeimage)
target_list=child.getchildren()[0] #獲取target_list
#print(target_list.tag)
object=None
for target in target_list:
if(target.tag=="target"):
#print(target.tag)
object = doc.createElement('object')
bndbox = doc.createElement("bndbox")
for target_child in target:
if(target_child.tag=="box"):
xmin = doc.createElement("xmin")
ymin = doc.createElement("ymin")
xmax = doc.createElement("xmax")
ymax = doc.createElement("ymax")
xmin_value=int(float(target_child.attrib["left"]))
ymin_value=int(float(target_child.attrib["top"]))
box_width_value=int(float(target_child.attrib["width"]))
box_height_value=int(float(target_child.attrib["height"]))
xmin.appendChild(doc.createTextNode(str(xmin_value)))
ymin.appendChild(doc.createTextNode(str(ymin_value)))
if(xmin_value+box_width_value>960):
xmax.appendChild(doc.createTextNode(str(960)))
else:
xmax.appendChild(doc.createTextNode(str(xmin_value+box_width_value)))
if(ymin_value+box_height_value>540):
ymax.appendChild(doc.createTextNode(str(540)))
else:
ymax.appendChild(doc.createTextNode(str(ymin_value+box_height_value)))
if(target_child.tag=="attribute"):
name = doc.createElement('name')
pose=doc.createElement('pose')
truncated=doc.createElement('truncated')
difficult=doc.createElement('difficult')
name.appendChild(doc.createTextNode("car"))
pose.appendChild(doc.createTextNode("Left")) #隨意指定
truncated.appendChild(doc.createTextNode("0")) #隨意指定
difficult.appendChild(doc.createTextNode("0")) #隨意指定
object.appendChild(name)
object.appendChild(pose)
object.appendChild(truncated)
object.appendChild(difficult)
bndbox.appendChild(xmin)
bndbox.appendChild(ymin)
bndbox.appendChild(xmax)
bndbox.appendChild(ymax)
object.appendChild(bndbox)
annotation.appendChild(object)
file_path_out=os.path.join(file_path,output_file_name)
f = open(file_path_out, 'w')
f.write(doc.toprettyxml(indent=' ' * 4))
f.close()
num=num+1
return num
def bboxes_draw_on_img(img, bbox, color=[255, 0, 0], thickness=2):
# Draw bounding box...
print(bbox)
p1 = (int(float(bbox["xmin"])), int(float(bbox["ymin"])))
p2 = (int(float(bbox["xmax"])), int(float(bbox["ymax"])))
cv2.rectangle(img, p1, p2, color, thickness)
def visualization_image(image_name,xml_file_name):
tree = ET.parse(xml_file_name)
root = tree.getroot()
object_lists=[]
for child in root:
if(child.tag=="folder"):
print(child.tag, child.text)
elif (child.tag == "filename"):
print(child.tag, child.text)
elif (child.tag == "size"): #解析size
for size_child in child:
if(size_child.tag=="width"):
print(size_child.tag,size_child.text)
elif (size_child.tag == "height"):
print(size_child.tag, size_child.text)
elif (size_child.tag == "depth"):
print(size_child.tag, size_child.text)
elif (child.tag == "object"): #解析object
singleObject={}
for object_child in child:
if (object_child.tag == "name"):
# print(object_child.tag,object_child.text)
singleObject["name"] = object_child.text
elif (object_child.tag == "bndbox"):
for bndbox_child in object_child:
if (bndbox_child.tag == "xmin"):
singleObject["xmin"] = bndbox_child.text
# print(bndbox_child.tag, bndbox_child.text)
elif (bndbox_child.tag == "ymin"):
# print(bndbox_child.tag, bndbox_child.text)
singleObject["ymin"] = bndbox_child.text
elif (bndbox_child.tag == "xmax"):
singleObject["xmax"] = bndbox_child.text
elif (bndbox_child.tag == "ymax"):
singleObject["ymax"] = bndbox_child.text
object_length=len(singleObject)
if(object_length>0):
object_lists.append(singleObject)
img = cv2.imread(image_name)
for object_coordinate in object_lists:
bboxes_draw_on_img(img,object_coordinate)
cv2.imshow("capture", img)
cv2.waitKey (0)
cv2.destroyAllWindows()
if ( __name__ == "__main__"):
#print("main")
basePath="DETRAC-Train-Annotations-XML"
totalxml=os.listdir(basePath)
total_num=0
flag=False
print("正在轉(zhuǎn)換")
saveBasePath="xml_test"
if os.path.exists(saveBasePath)==False: #判斷文件夾是否存在
os.makedirs(saveBasePath)
#ConvertVOCXml(file_path="samplexml",file_name="000009.xml")
# Start time
start = time.time()
log=open("xml_statistical.txt","w") #分析日志,進(jìn)行排錯
for xml in totalxml:
file_name=os.path.join(basePath,xml)
print(file_name)
num=ConvertVOCXml(file_path=saveBasePath,file_name=file_name)
print(num)
total_num=total_num+num
log.write(file_name+" "+str(num)+"\n")
# End time
end = time.time()
seconds=end-start
print( "Time taken : {0} seconds".format(seconds))
print(total_num)
log.write(str(total_num)+"\n")
visualization_image("Insight-MVT_Annotation_Train/MVI_40212/img00396.jpg","xml_test/MVI_40212__img00396.xml")
第二部
把圖片跟標(biāo)記放到一起
import os
import random
import shutil
#xml路徑的地址
XmlPath=r'xml_test'
#原圖片的地址
pictureBasePath=r"Insight-MVT_Annotation_Train"
#保存圖片的地址
saveBasePath=r"picture_test"
total_xml = os.listdir(XmlPath)
num=len(total_xml)
list=range(num)
if os.path.exists(saveBasePath)==False: #判斷文件夾是否存在
os.makedirs(saveBasePath)
for xml in total_xml:
xml_temp=xml.split("__")
folder=xml_temp[0]
filename=xml_temp[1].split(".")[0]+".jpg"
# print(folder)
# print(filename)
temp_pictureBasePath=os.path.join(pictureBasePath,folder)
filePath=os.path.join(temp_pictureBasePath,filename)
# print(filePath)
newfile=xml.split(".")[0]+".jpg"
newfile_path=os.path.join(saveBasePath,newfile)
print(newfile_path)
shutil.copyfile(filePath, newfile_path)
print("xml file total number",num)
第三部
需要將前兩步提取出來的文件放在VOC的文件中,固定格式
VOC2007文件下:
其中,ImageSets的目錄結(jié)構(gòu)為:
到此,可以使用下面的代碼,生成對應(yīng)的txt文件(產(chǎn)生test、train、trainval等文件)
import os
import random
import time
xmlfilepath=r'./VOC2007/Annotations'
saveBasePath=r"./"
trainval_percent=0.8
train_percent=0.85
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)
print("train and val size",tv)
print("traub suze",tr)
ftrainval = open(os.path.join(saveBasePath,'VOC2007/ImageSets/Main/trainval.txt'), 'w')
ftest = open(os.path.join(saveBasePath,'VOC2007/ImageSets/Main/test.txt'), 'w')
ftrain = open(os.path.join(saveBasePath,'VOC2007/ImageSets/Main/train.txt'), 'w')
fval = open(os.path.join(saveBasePath,'VOC2007/ImageSets/Main/val.txt'), 'w')
# Start time
start = time.time()
for i in list:
name=total_xml[i][:-4]+'\n'
if i in trainval:
ftrainval.write(name)
if i in train:
ftrain.write(name)
else:
fval.write(name)
else:
ftest.write(name)
# End time
end = time.time()
seconds=end-start
print( "Time taken : {0} seconds".format(seconds))
ftrainval.close()
ftrain.close()
fval.close()
ftest .close()
此時,VOC中的train.txt里都是各個圖片的名稱,并沒有路徑,可以使用darknet/scripts/voc_label.py來生成訓(xùn)練使用的txt文件,前面創(chuàng)建VOC文件的時候,需要指定一個年份,VOC2007,所以在訓(xùn)練的時候需要修改一下需要使用的名稱,以及類別,如下圖:
sets根據(jù)自己的需要改,比如我這次就使用2007,train,test等等。classes改成自己需要的類別,比如這次使用的數(shù)據(jù)集,只需要有一個car就可以。
三、訓(xùn)練
復(fù)制原來的cfg文件,比如我打算用的是yolov3.cfg,就備份一下,然后修改cfg文件,
在net下面,如果想要訓(xùn)練,需要把#Testing下面的 batch和subdivision給注釋掉,把train下面的取消注釋。
除此之外,還有每一個【YOLO】層的classes改成自己訓(xùn)練的類數(shù),classes=1;
每一個【YOLO】層前還有一個filters也需要改掉,filters=(類數(shù)+5)*3,自己計算一下,比如這次只有一個car類,需要修改filters=18;
這樣的classes和filters一共有三處;
然后去cfg文件夾下復(fù)制一個voc.data出來進(jìn)行修改,
classes是類別數(shù)量;
train是訓(xùn)練集文件,就是上面那個生成的是全部訓(xùn)練數(shù)據(jù)集路徑加名稱的txt文件;
valid同上,驗證集文件路徑;
name是接下來要創(chuàng)建的txt文件的路徑,文件中只需要有類的名字就行,但是注意順序要對應(yīng);
backup是每1000次訓(xùn)練,會生成對應(yīng)的weights權(quán)重文件,備份的地方,需要自己去創(chuàng)建好然后寫到這里;
接下來創(chuàng)建一個.names的文件
一行一個類別,注意對應(yīng)之前標(biāo)記的順序,比如car是0,dog是1,就要按順序?qū)?#xff0c;下面是coco數(shù)據(jù)集的.names文件
然后就是下載預(yù)訓(xùn)練權(quán)重,這個跟前面用來測試的那個預(yù)訓(xùn)練權(quán)重不一樣,注意下載
wget https://pjreddie.com/media/files/darknet53.conv.74
跟之前一樣放在根目錄下面;
使用命令開始訓(xùn)練
./darknet detector train data/voc.data cfg/yolov3.cfg darknet53.conv.74 -i 3
因為我使用的是學(xué)校的服務(wù)器,沒辦法使用多gpu同時訓(xùn)練,所以用-i指定編號3GPU訓(xùn)練,權(quán)重文件放在了根目錄,所以不需要有特殊路徑,.data和.cfg文件都使用自己剛剛創(chuàng)建修改好的文件,注意自己的路徑;
下面這個命令是使用多GPU進(jìn)行訓(xùn)練的命令
./darknet detector train data/voc.data cfg/yolov3.cfg darknet53.conv.74 -gpus 0,1
還有就是如果想要生成loss圖像和iou圖像,則需要在訓(xùn)練的時候使用命令保存log文件,先創(chuàng)建一個文件夾,比如saveLog,然后使用命令
./darknet detector train data/voc.data cfg/yolov3.cfg darknet53.conv.74 -i 3 2>1 |tee saveLog/train.log
四、可視化訓(xùn)練
1、先提取log文件中的信息
#!/usr/bin/python
#coding=utf-8
#該文件用于提取訓(xùn)練log,去除不可解析的log后使log文件格式化,生成新的log文件供可視化工具繪圖
import inspect
import os
import random
import sys
def extract_log(log_file, new_log_file, key_word):
with open(log_file, 'r') as f:
with open(new_log_file, 'w') as train_log:
for line in f:
#去除多GPU的同步log;去除除零錯誤的log
if ('Syncing' in line) or ('nan' in line):
continue
if key_word in line:
train_log.write(line)
f.close()
train_log.close()
extract_log('./2048/train_log2.txt', './2048/log_loss2.txt', 'images')
extract_log('./2048/train_log2.txt', 'log_iou2.txt', 'IOU')
2、可視化loss
#!/usr/bin/python
#coding=utf-8
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
#根據(jù)自己的log_loss.txt中的行數(shù)修改lines, 修改訓(xùn)練時的迭代起始次數(shù)(start_ite)和結(jié)束次數(shù)(end_ite)。
lines = 4500
start_ite = 6000 #log_loss.txt里面的最小迭代次數(shù)
end_ite = 15000 #log_loss.txt里面的最大迭代次數(shù)
step = 10 #跳行數(shù),決定畫圖的稠密程度
igore = 0 #當(dāng)開始的loss較大時,你需要忽略前igore次迭代,注意這里是迭代次數(shù)
y_ticks = [0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4]#縱坐標(biāo)的值,可以自己設(shè)置。
data_path = '2048/log_loss2.txt' #log_loss的路徑。
result_path = './2048/avg_loss' #保存結(jié)果的路徑。
####-----------------只需要改上面的,下面的可以不改動
names = ['loss', 'avg', 'rate', 'seconds', 'images']
result = pd.read_csv(data_path, skiprows=[x for x in range(lines) if (x
False, names=names)
result.head()
for name in names:
result[name] = result[name].str.split(' ').str.get(1)
result.head()
result.tail()
for name in names:
result[name] = pd.to_numeric(result[name])
result.dtypes
print(result['avg'].values)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
###-----------設(shè)置橫坐標(biāo)的值。
x_num = len(result['avg'].values)
tmp = (end_ite-start_ite - igore)/(x_num*1.0)
x = []
for i in range(x_num):
x.append(i*tmp + start_ite + igore)
#print(x)
print('total = %d\n' %x_num)
print('start = %d, end = %d\n' %(x[0], x[-1]))
###----------
ax.plot(x, result['avg'].values, label='avg_loss')
#ax.plot(result['loss'].values, label='loss')
plt.yticks(y_ticks)#如果不想自己設(shè)置縱坐標(biāo),可以注釋掉。
plt.grid()
ax.legend(loc = 'best')
ax.set_title('The loss curves')
ax.set_xlabel('batches')
fig.savefig(result_path)
#fig.savefig('loss')
3、可視化iou
#!/usr/bin/python
#coding=utf-8
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
#根據(jù)log_iou修改行數(shù)
lines = 1736397
step = 5000
start_ite = 0
end_ite = 50200
igore = 1000
data_path = './my_coco3/log_iou.txt' #log_loss的路徑。
result_path = './my_coco3/Region Avg IOU' #保存結(jié)果的路徑。
names = ['Region Avg IOU', 'Class', 'Obj', 'No Obj', '.5_Recall', '.7_Recall', 'count']
#result = pd.read_csv('log_iou.txt', skiprows=[x for x in range(lines) if (x%10==0 or x%10==9)]\
result = pd.read_csv(data_path, skiprows=[x for x in range(lines) if (x
, error_bad_lines=False, names=names)
result.head()
for name in names:
result[name] = result[name].str.split(': ').str.get(1)
result.head()
result.tail()
for name in names:
result[name] = pd.to_numeric(result[name])
result.dtypes
####--------------
x_num = len(result['Region Avg IOU'].values)
tmp = (end_ite-start_ite - igore)/(x_num*1.0)
x = []
for i in range(x_num):
x.append(i*tmp + start_ite + igore)
#print(x)
print('total = %d\n' %x_num)
print('start = %d, end = %d\n' %(x[0], x[-1]))
####-------------
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.plot(x, result['Region Avg IOU'].values, label='Region Avg IOU')
#ax.plot(result['Avg Recall'].values, label='Avg Recall')
plt.grid()
ax.legend(loc='best')
ax.set_title('The Region Avg IOU curves')
ax.set_xlabel('batches')
fig.savefig(result_path)
五、訓(xùn)練參數(shù)解釋
六、F&Q
關(guān)于什么時候停止訓(xùn)練?
一般在訓(xùn)練過程中,可以看到下圖,當(dāng)avg_loss在0.07的時候就可以停止了,或者當(dāng)很一段時間avg_loss不再變化了,也可以停止訓(xùn)練。
總結(jié)
以上是生活随笔為你收集整理的yolov3安卓实现_从零实现YOLOv3的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iar烧录程序步骤_STM8入门以及程序
- 下一篇: bootstrape常用标签_boots