一、安装Darknet

从官网配置darknet环境,这一步也可以看yolo作者的网站来进行,

git clone https://github.com/pjreddie/darknet.git
cd darknet
make

也可以用其他基于原作者实现的地址(有详细一些的文档),然后cd到根文件下,首次使用以及修改过.c文件、.cfg文件后都需要make一下。

在darknet中,有一个Makefile,这个文件定义了网络,如果需要使用opencv或者GPU、CUDNN都需要编辑Makefile,设置等于1;默认都是0的。注意,修改过Makefile以后必须重新make才能生效!

截屏2020-03-30 下午5.32.51.png

不好下的,戳这里百度网盘,密码:7zuk。

现在,框架有了,可以尝试一下检测图片,如果要检测图片,必须下载一个预训练权重,200+mb,可以直接用命令

wget https://pjreddie.com/media/files/yolov3.weights

或者[戳百度网盘]() 注意下载下来的权重文件要放到darknet根目录下。

然后使用下面的命令尝试是否可以正常运行(单张图片检测)

./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg

这个命令分解开来就是:
<detect>,<cfg文件的路径>,<所需要使用的权重文件的路径>,<图片的路径>
能看到下面这样一个过程,构建网络,然后测试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%

如果设置使用了opencv会弹出来下面的图,如果没有,这张图会保存在darknet根目录下,可以自己打开看。
弹出来的图

作者添加了类似上面的图片的供检测的图片,可以在目录中找到,data/eagle.jpg,data/dog.jpg,data/person.jpg, 和data/horses.jpg

对于多图片检测(一个一个的检测),输入:

./darknet detect cfg/yolov3.cfg yolov3.weights

然后等构建完网络和载入权重后,会要求你输入路径,输入后回车,检测完成可以直接输入下一个。

至此,yolo已经可以正常运行了。

二、制作自己的数据集

这一部分分为两种情况,第一是自己标注数据集,可以使用labelimg来进行标记;第二就是将其他数据集格式转换成yolo可以使用的数据集。

2.1 使用labelimg标注数据集

labelimg的下载安装戳这里
界面长这样

截屏2020-03-30 下午6.34.30.png
可以支持yolo格式,选一下就好了。

在标注之前,比较省事的是把自己要标注的图片名字改成编号,比较好方便使用,因为生成的txt标注文件是和图片同名的,后面不好改。

然后设置目录,现在是中文了,直接鼠标拉框就行了。

image.png
可以在右侧设置默认的标签,这样就不用每拉一个框就要点选了。

标记的txt文件中,是yolo格式的,分别是类别(0是设置的第一个类别)和坐标。

2.2 转换其他数据集

yolo支持VOC格式的数据集,所使用的标记文件都是单独的一个txt格式的文件,存放一张图片的标记信息,所以思路就是:

如果所有标记文件都在一个文件中,那就先提取出来单独的xml文件,然后将这些文件转换为txt格式;
如果标记文件是单独的xml文件,只需要将xml文件转换为txt文件就可以了;

上次实验室使用的是DETRAC数据集,下载地址,包括三个压缩文件

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 #计数
   #读xml操作
 
   frame_lists=[]
   output_file_name=""
   for child in root:
 
      if(child.tag=="frame"):
          # 创建dom文档
         doc = Document()
         # 创建根节点
         annotation = doc.createElement('annotation')
         # 根节点插入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("正在转换")
   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") #分析日志,进行排错
   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")
第二部

把图片跟标记放到一起

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文件下:
image.png
其中,ImageSets的目录结构为:
image.png

到此,可以使用下面的代码,生成对应的txt文件(产生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来生成训练使用的txt文件,前面创建VOC文件的时候,需要指定一个年份,VOC2007,所以在训练的时候需要修改一下需要使用的名称,以及类别,如下图:
截屏2020-03-31 上午1.03.07.png

sets根据自己的需要改,比如我这次就使用2007,train,test等等。classes改成自己需要的类别,比如这次使用的数据集,只需要有一个car就可以。

三、训练

复制原来的cfg文件,比如我打算用的是yolov3.cfg,就备份一下,然后修改cfg文件,
截屏2020-03-31 上午2.01.47.png

在net下面,如果想要训练,需要把#Testing下面的 batch和subdivision给注释掉,把train下面的取消注释。

除此之外,还有每一个【YOLO】层的classes改成自己训练的类数,classes=1;

每一个【YOLO】层前还有一个filters也需要改掉,filters=(类数+5)*3,自己计算一下,比如这次只有一个car类,需要修改filters=18;

这样的classes和filters一共有三处;

然后去cfg文件夹下复制一个voc.data出来进行修改,
截屏2020-03-31 上午2.16.59.png

  • classes是类别数量;
  • train是训练集文件,就是上面那个生成的是全部训练数据集路径加名称的txt文件;
  • valid同上,验证集文件路径;
  • name是接下来要创建的txt文件的路径,文件中只需要有类的名字就行,但是注意顺序要对应;
  • backup是每1000次训练,会生成对应的weights权重文件,备份的地方,需要自己去创建好然后写到这里;

接下来创建一个.names的文件
一行一个类别,注意对应之前标记的顺序,比如car是0,dog是1,就要按顺序写,下面是coco数据集的.names文件
截屏2020-03-31 上午2.21.56.png

然后就是下载预训练权重,这个跟前面用来测试的那个预训练权重不一样,注意下载

wget https://pjreddie.com/media/files/darknet53.conv.74

跟之前一样放在根目录下面;

使用命令开始训练

./darknet detector train data/voc.data cfg/yolov3.cfg darknet53.conv.74 -i 3
因为我使用的是学校的服务器,没办法使用多gpu同时训练,所以用-i指定编号3GPU训练,权重文件放在了根目录,所以不需要有特殊路径,.data和.cfg文件都使用自己刚刚创建修改好的文件,注意自己的路径;

下面这个命令是使用多GPU进行训练的命令

./darknet detector train data/voc.data cfg/yolov3.cfg darknet53.conv.74 -gpus 0,1
还有就是如果想要生成loss图像和iou图像,则需要在训练的时候使用命令保存log文件,先创建一个文件夹,比如saveLog,然后使用命令
./darknet detector train data/voc.data cfg/yolov3.cfg darknet53.conv.74 -i 3 2>1 |tee saveLog/train.log

四、可视化训练

此部分系搬运[[kepcum]](https://blog.csdn.net/qq_3361...

1、先提取log文件中的信息
#!/usr/bin/python
#coding=utf-8
#该文件用于提取训练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
 
 
#根据自己的log_loss.txt中的行数修改lines, 修改训练时的迭代起始次数(start_ite)和结束次数(end_ite)。
lines = 4500
start_ite = 6000 #log_loss.txt里面的最小迭代次数
end_ite = 15000 #log_loss.txt里面的最大迭代次数
step = 10 #跳行数,决定画图的稠密程度
igore = 0 #当开始的loss较大时,你需要忽略前igore次迭代,注意这里是迭代次数
 
 
y_ticks = [0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4]#纵坐标的值,可以自己设置。
data_path =  '2048/log_loss2.txt' #log_loss的路径。
result_path = './2048/avg_loss' #保存结果的路径。
 
####-----------------只需要改上面的,下面的可以不改动
names = ['loss', 'avg', 'rate', 'seconds', 'images']
result = pd.read_csv(data_path, skiprows=[x for x in range(lines) if (x<lines*1.0/((end_ite - start_ite)*1.0)*igore or x%step!=9)], 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
print(result['avg'].values)
 
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
 
 
###-----------设置横坐标的值。
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)#如果不想自己设置纵坐标,可以注释掉。
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
 
#根据log_iou修改行数
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' #保存结果的路径。
 
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<lines*1.0/((end_ite - start_ite)*1.0)*igore or x%step!=0)]\
, 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)

五、训练参数解释

六、F&Q

关于什么时候停止训练?
一般在训练过程中,可以看到下图,当avg_loss在0.07的时候就可以停止了,或者当很一段时间avg_loss不再变化了,也可以停止训练。
image.png


RHXCYA
0 声望1 粉丝