目标检测:yolov3训练自己的数据模型,避免踩坑(包含常见问题集锦)

第一步:

克隆darknet:    git clone https://github.com/pjreddie/darknet.git

第二步:编译

2.1 修改makefile 文件,把GPU,CUDNN,OPENCV等参数设为1,nvcc 设置为自己的路径,保存

2.2 执行编译

make

目标检测:yolov3训练自己的数据模型,避免踩坑(包含常见问题集锦)_第1张图片

第三步:制作VOC数据集

3.1 新建文件夹VOCdevkit,其中其中包含以下文件夹和子文件。Annotations 用来存放xml文件,ImageSet/Main/train.txt 用来保存训练图片的名称,JPEGImages 用来保存训练图片

目标检测:yolov3训练自己的数据模型,避免踩坑(包含常见问题集锦)_第2张图片

3.2标注数据

标注工具推荐使用labelImg,下载地址https://github.com/tzutalin/labelImg 

标注图像生成的xml文件保存到Annotations文件夹中

3.3提取图像的文件名并保存到文本当中

在ImageSets/Main/train.txt 中保存训练图像的名称,可以使用以下脚本实现。将路径改为自己的即可

# -*- coding: UTF-8 -*-
import os
from os import listdir, getcwd
from os.path import join
if __name__ == '__main__':
    source_folder='/home/walter/myNet/head_yolo/darknet/scripts/VOCdevkit/VOC2018/JPEGImages/'
    dest='/home/walter/myNet/head_yolo/darknet/scripts/VOCdevkit/VOC2018/ImageSets/Main/train.txt' 
    #dest2='/home/darknet/scripts/VOCdevkit/VOC2018/ImageSets/Main/val.txt'  
    file_list=os.listdir(source_folder)       
    train_file=open(dest,'w')                 
   # val_file=open(dest2,'a')                  
    for file_obj in file_list:                
        file_path=os.path.join(source_folder,file_obj) 
       
        file_name,file_extend=os.path.splitext(file_obj)
       
        #file_num=int(file_name) 
        train_file.write(file_name+'\n')  
        
        #
    train_file.close()
#val_file.close()

3.3转换数据格式。将标注后的xml文件写到txt文本当中。可以使用yolov3提供的voc_label.py脚本。在yolo/darknet/scripts目录下

import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join

sets=[('2018', 'train')]

classes = ["head"]


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)

def convert_annotation(year, image_id):
    in_file = open('VOCdevkit/VOC%s/Annotations/%s.xml'%(year, image_id))
    out_file = open('VOCdevkit/VOC%s/labels/%s.txt'%(year, 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:
    if not os.path.exists('VOCdevkit/VOC%s/labels/'%(year)):
        os.makedirs('VOCdevkit/VOC%s/labels/'%(year))
    image_ids = open('VOCdevkit/VOC%s/ImageSets/Main/%s.txt'%(year, image_set)).read().strip().split()
    list_file = open('%s_%s.txt'%(year, image_set), 'w')
    for image_id in image_ids:
        list_file.write('%s/VOCdevkit/VOC%s/JPEGImages/%s.jpg\n'%(wd, year, image_id))
        convert_annotation(year, image_id)
    list_file.close()

#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")

set可以自己设置名称,class也修改为自己训练的类别。运行脚本将会在darknet/script/目录下生成文本2018_train.txt文本,其中保存的是训练图形的路径

目标检测:yolov3训练自己的数据模型,避免踩坑(包含常见问题集锦)_第3张图片

同时在darknet/scripts/VOCdevkit/VOC2018目录下会生成labels 文件夹,并生成xml转换后的txt文本

目标检测:yolov3训练自己的数据模型,避免踩坑(包含常见问题集锦)_第4张图片

文本格式   ,x,y是图片的中心坐标/图片的宽,高   width height 是目标大小相对于整个图片的比例,文本内容如下:

3.4 在darknet/data目录下新建head.names

3.5在darknet/cfg 目录下新建head.data,内容如下

目标检测:yolov3训练自己的数据模型,避免踩坑(包含常见问题集锦)_第5张图片

classes是检测的类别数,train是使用voc_label.py脚本生成的2018_train.txt,2018_train.txt存储的是所有训练集的路径可以是绝对路径也可以是相对路径,修改为自己的即可,names是要检测的物体的名称文件,backup是用来保存训练以后生成的权重文件

3.6在darknet/cfg目录下复制yolov3.cfg文件,重命名为yolov3-head.cfg,并做以下修改:

[net]
# Testing
#batch=1               #把测试的注释掉
#subdivisions=1        #把测试的注释掉
# Training
batch=64
subdivisions=16
width=416
height=416
channels=3
momentum=0.9
decay=0.0005
angle=0
saturation = 1.5
exposure = 1.5
hue=.1

learning_rate=0.001
burn_in=1000
max_batches = 2000
policy=steps
steps=40000,45000
scales=.1,.1



[convolutional]
batch_normalize=1
filters=32
size=3
stride=1
pad=1
activation=leaky

# Downsample

[convolutional]
batch_normalize=1
filters=64
size=3
stride=2
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=32
size=1
stride=1
pad=1
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 = .5
truth_thresh = 1
random=1

修改yolo-head.cfg文件 改变如下行,在此文件的最开始的几行

 batch = 64 
 subdivisions=16

 batch值太大可能超过GPU的显存造成无法训练,会在训练过程中报显存耗尽的错误,我的GPU显存是16GB ,增大subdivision可以减少显存的使用,合适的调节这两个值训练精度更高的模型,batch越大越容易收敛,batch越小收敛速度变慢,也可能过拟合,但可能精度更高。

修改classes=N  其中N修改为你需要识别的物体的种类总数,注意一共有三个 [yolo]层的class位置需要修改

[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

根据公式filters=(classes + 5)x3将每一个[yolo] 层前面的第一个[convolutional]层的 [filters=255] 修改为你的公式结果,一共有三个[convolutional]层位置需要修改

[convolutional]
size=1
stride=1
pad=1
filters=18
activation=linear


3.7下载imagenet上预先训练的权重,保存到darknet目录下

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

第4步:开始训练

./darknet detector train cfg/head.data cfg/yolov3-head.cfg scripts/darknet53.conv.74 -gpus 0,1
 
#注意文件路径

训练过程如下:

目标检测:yolov3训练自己的数据模型,避免踩坑(包含常见问题集锦)_第6张图片

训练历程与部分参数解释:

每迭代100次会存储一个模型,avg loss是一个错误率的指标,越低越好,刚开始会很大,下降也很快,最终降到0.***,每个类一般迭代2000次,所以实训项目需要4000次。使用GTX 950m,大约每100次迭代耗时70分钟。

        Region xx: cfg文件中yolo-layer的索引;
        Avg IOU:当前迭代中,预测的box与标注的box的平均交并比,越大越好,期望数值为1;
        Class: 标注物体的分类准确率,越大越好,期望数值为1;
        obj: 越大越好,期望数值为1;
        No obj: 越小越好;
        .5R: 以IOU=0.5为阈值时候的recall; recall = 检出的正样本/实际的正样本
        0.75R: 以IOU=0.75为阈值时候的recall;

        count:正样本数目。


训练完成

目标检测:yolov3训练自己的数据模型,避免踩坑(包含常见问题集锦)_第7张图片

测试模型

 ./darknet detector test cfg/df_head.data cfg/yolov3-head.cfg backup/yolov3-head_final.weights data/head_01.jpg

注意:在测试的时候需要把yolo-head.cfg中的test注释去掉,把train注释掉,即设置:

batch=1
subdivisions=1

目标检测:yolov3训练自己的数据模型,避免踩坑(包含常见问题集锦)_第8张图片

训练模型时常见问题:

1.make时出现错误,多数是cuda的路径不对,当安装多个cuda版本时,注意自己软链接指向的cuda版本。

2.cuda:out of memory

   原因是显存不够

   解决方法:1.查看GPU使用状况,是否有中断的网络训练进程没有关闭。2.适当调小batch  3.关闭random=0

3.can't open file:train.txt 或者 can't load image

   原因:训练集路径不对,检测script下的trai.txt文件,如果是用window系统编辑的文件会出现换行符号和Linux不兼容问题,请用windows下的notepad打开train.txt,并在设置里显示所有符号。2.检查配置文件cfg下的**.data文件,train路径是否正确

classes= 1
train  = /home/walter/myNet/yolo_seris/defect/darknet/scripts/2018_train.txt
names = data/defect.names
backup = backup

4.Couldn't open file:/home/**/VOC2018/labels/120.bmp 等其他t图片格式文件

这个问题曾经困扰了楼主两天,如果按照提示把所有图片复制到labels文件夹中,则训练时所有的结果为nan,count=0。最终发现是yolov3的源码中只包含了jpg,JPG,JPEG格式文件,因此当训练图片为bmp,png等格式时会出现label错误!因此我们要么转换图片格式,要么修改源代码。如下图所示。

    find_replace(path, "images", "mask", labelpath);
    find_replace(labelpath, "JPEGImages", "mask", labelpath);
    find_replace(labelpath, ".jpg", ".txt", labelpath);
    find_replace(labelpath, ".JPG", ".txt", labelpath);
    find_replace(labelpath, ".JPEG", ".txt", labelpath);

其中修改源代码的方式是打开src/data.c文件,在所有的find_replace(*****)下面增加你需要的图片格式即可。

5.训练是大部分为nan,count=0

      原因是所在的batch里未找到目标,可以适当加大batch,如batch=128。偶尔出现nan并非错误

6. 训练时全部为nan,则数据有问题。

    参考问题4,查看labels文件夹下是否有图片,以及JPEGImage中的图片后缀。

7.若要验证数据集,并绘制PR曲线和mAP

  请参照faster-rcnn中的绘制代码,拷贝下来即可。

 

 

 

你可能感兴趣的:(目标检测)