yolo 学习系列(二):训练自己的数据集

yolo 学习系列(二):训练自己的数据集

1、图片预处理

1.1 统一大小
import cv2
import os


fullfilename=[]
filepath = "F:/PycharmProjects/image"   # 不能包含中文路径
filepath1 = "F:/PycharmProjects/resize"
for filename in os.listdir(filepath):
    print(filename)
    print(os.path.join(filepath, filename))
    filelist = os.path.join(filepath, filename)
    fullfilename.append(filelist)
i = 1
for imagename in fullfilename:
    img = cv2.imread(imagename)
    img = cv2.resize(img, (416, 416))   # 该句报错,路径中包含中文
    resizename = str(i)+'.jpg'          # 命名形式为1,2,3... 需重新命名位000001,000002000003...
    isExists = os.path.exists(filepath1)
    if not isExists:
        os.makedirs(filepath1)
        print('mkdir resizename accomploshed')
    savename = filepath1+'/'+resizename
    cv2.imwrite(savename, img)
    print('{} is resized'.format(savename))
    i = i+1
1.2 重命名

图片重命名,并将图片名称写入 train.txt 文本内

# -*- coding:utf8 -*-
#!/usr/bin/python3.6
import os


class BatchRename():
    def __init__(self):
        self.path = 'F:/PycharmProjects/resize'

    def rename(self):
        f = open(r'F:/PycharmProjects/resize/train.txt', 'a')
        filelist = os.listdir(self.path)
        total_num = len(filelist)
        i = 1

        for item in filelist:
            if item.endswith('.jpg'):
                src = os.path.join(os.path.abspath(self.path), item)
                str1 = str(i)
                dst = os.path.join(os.path.abspath(self.path), str1.zfill(6) + '.jpg')
                try:
                    os.rename(src, dst)
                    print('converting %s to %s ...' % (src, dst))

                    # 写入 txt 文本中的名称形式,前面加上绝对路径
                    f.write('/home/chris/darknet/trainData/haishen/VOC2007/JPEGImages/' + str1.zfill(6) + '.jpg' + '\n')
                    i = i + 1
                except:
                    continue
        print('total %d to rename & converted %d jpgs' % (total_num, i))


if __name__ == '__main__':
    demo = BatchRename()
    demo.rename()

2、创建VOC数据集

参考这里
还有这里

2.1 新建文件夹

这里面用到的文件夹是Annotation、ImageSets和JPEGImages。

  • Annotation中主要存放xml文件,每一个xml对应一张图像,并且每个xml中存放的是标记的各个目标的位置和类别信息,命名通常与对应的原始图像一样;
  • ImageSets我们只需要用到Main文件夹,这里面存放的是一些文本文件,通常为train.txt、test.txt等,该文本文件里面的内容是需要用来训练或测试的图像的名字(无后缀无路径);

  • JPEGImages文件夹中放我们已按统一规则命名好的原始图像

--VOC2007
    --Annotations
    --ImageSets
      --Main
      --Layout
      --Segmentation
    --JPEGImages
    --SegmentationClass
    --SegmentationObject
  • 新建文件夹VOC2007(通常命名为这个,也可以用其他命名,但一定是名字+年份,例如MYDATA2016,无论叫什么后面都需要改相关代码匹配这里,本例中以VOC2007为例)
  • 在VOC2007文件夹下新建三个文件夹Annotation、ImageSets和JPEGImages,并把准备好的自己的原始图像放在JPEGImages文件夹下
  • 在ImageSets文件夹中,新建三个空文件夹Layout、Main、Segmentation,然后把写了训练或测试的图像的名字的文本拷到Main文件夹下,按目的命名,我这里所有图像用来训练,故而Main文件夹下只有train.txt文件。上面说的小代码运行后会生成该文件,把它拷进去即可。

2.2 标注图像目标区域——labelImg

2.2.1 labelImg安装

下载labelImg: 直接下载或克隆下载

git clone https://github.com/tzutalin/labelImg.git

解压后放在 home 路径下

sudo apt-get install pyqt4-dev-tools # 安装PyQt4
sudo pip install lxml # 安装lxml,如果报错,可以试试下面语句
sudo apt-get install python-lxml

进入 LabelImg 目录后使用 make 编译(源码安装方式,一定要会)

cd labelImg
make all

测试使用:在 labelImg 目录下使用终端执行

python labelImg.py 
#或./labelImg.py
2.2.2 labelImg使用

快捷键

  • Ctrl + u 加载目录中的所有图像,鼠标点击Open dir同功能
  • Ctrl + r 更改默认注释目标目录(xml文件保存的地址)
  • Ctrl + s 保存
  • Ctrl + d 复制当前标签和矩形框
  • d 下一张图片
  • a 上一张图片
  • Ctrl++ 放大
  • Ctrl– 缩小
  • ↑→↓← 键盘箭头移动选定的矩形框

(1)源码文件夹下,修改data / predefined_classes.txt文件,根据自己的需求修改默认类别,这里改成sea
(2)点击 “Open Dir” 打开至图片文件夹位置,即 JPEGImages 下,点击 Nest Image可切换下张图片
(3)打开 labelImg 使用 Ctrl + r 更改 xml 文件的默认保存目录,修改至上面介绍的Annotation下
接下来右键 Create RectBox 画框,画完后点击 Save 保存,这时在Annotation目录下就会产生一个xml文件了,它的画风是这样的:
文件中记录了图片的大小、通道数、类别、图框位置等信息

<?xml version="1.0" ?>
<annotation>
    <folder>JPEGImages</folder>
    <filename>00000</filename>
    <path>/home/kinglch/VOC2007/JPEGImages/00000.jpg</path>
    <source>
        <database>Unknown</database>
    </source>
    <size>
        <width>704</width>
        <height>576</height>
        <depth>3</depth>
    </size>
    <segmented>0</segmented>
    <object>
        <name>person</name>
        <pose>Unspecified</pose>
        <truncated>0</truncated>
        <difficult>0</difficult>
        <bndbox>
            <xmin>73</xmin>
            <ymin>139</ymin>
            <xmax>142</xmax>
            <ymax>247</ymax>
        </bndbox>
    </object>
    <object>
        <name>person</name>
        <pose>Unspecified</pose>
        <truncated>0</truncated>
        <difficult>0</difficult>
        <bndbox>
            <xmin>180</xmin>
            <ymin>65</ymin>
            <xmax>209</xmax>
            <ymax>151</ymax>
        </bndbox>
    </object>

</annotation>

最后,生成训练的txt文件,运行以下Python脚本,在 Main 目录下产生 trainval.txt、train.txt、test.txt、val.txt文本

import os
import random

trainval_percent = 0.66
train_percent = 0.95
xmlfilepath = 'Annotations'
txtsavepath = 'ImageSets\Main'
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)

ftrainval = open('ImageSets/Main/trainval.txt', 'w')
ftest = open('ImageSets/Main/test.txt', 'w')
ftrain = open('ImageSets/Main/train.txt', 'w')
fval = open('ImageSets/Main/val.txt', 'w')

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)

ftrainval.close()
ftrain.close()
fval.close()
ftest .close()

2.3 训练前的准备

前提:yolo已经安装好
在darknet-master/scripts文件夹中新建文件夹 VOCdevkit
然后将整个 VOC2007 文件夹都拷到 VOCdevkit 文件夹下

2.3.1 voc_label.py生成训练文件

首先需要修改 voc_label.py 中的代码,这里主要修改数据集名,以及类别信息,我的是VOC2007,并且所有样本用来训练,并且只检测sea,故只有一类目标,修改如下:

import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join
# 注释掉默认的
# sets=[('2012', 'train'), ('2012', 'val'), ('2007', 'train'), ('2007', 'val'), ('2007', 'test')]
# classes = ["aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"]

sets=[ ('2007', 'train'), ('2007', 'val'), ('2007', 'test')]
classes = [ "sea"]


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(year, image_id):
    in_file = open('VOCdevkit/VOC%s/Annotations/%s.xml'%(year, image_id))  #(如果使用的不是VOC而是自设置数据集名字,则这里需要修改)
    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()

修改好后在该目录下运行以下命令:‘’

python voc_label.py

之后在文件夹scripts\VOCdevkit\VOC2007下生成了文件夹 lable,该文件夹下的画风是这样的
这里写图片描述
同时在 scripts\ 下应该也生成了 train_2007.txt 这个文件,里面包含了所有训练样本的绝对路径。

2.3.2 修改配置文件

修改源文件前最好进行备份或注释掉默认语句,不要在原语句上修改。

做好了上述准备,就可以根据不同的网络设置(cfg文件)来训练了。在文件夹cfg中有很多cfg文件,应该跟caffe中的prototxt文件是一个意思。这里以tiny-yolo-voc.cfg为例,该网络是yolo-voc的简版,相对速度会快些。主要修改参数如下

.  
.  
.  
[convolutional]  
size=1  
stride=1  
pad=1  
filters=30  //修改最后一层卷积层核参数个数,计算公式是依旧自己数据的类别数filter=num×(classes + coords + 1)=5×(1+4+1)=30  
activation=linear  

[region]  
anchors = 1.08,1.19,  3.42,4.41,  6.63,11.38,  9.42,5.11,  16.62,10.52  
bias_match=1  
classes=1  //类别数,本例为1类  
coords=4  
num=5  
softmax=1  
jitter=.2  
rescore=1  

object_scale=5  
noobject_scale=1  
class_scale=1  
coord_scale=1  

absolute=1  
thresh = .6  
random=1 

修改好了cfg文件之后,就需要修改两个文件。
(1)data文件下的voc.names
打开voc.names文件可以看到有20类的名称,根据自己的实际应用修改,此处为sea
(2)cfg文件夹中的voc.data

    classes= 1  //类别数  
    train  = /home/chris/darknet/scripts/2007_train.txt  //训练样本的绝对路径文件
    //valid  = /home/pjreddie/data/voc/2007_test.txt  //测试样本的绝对路径  文件
    names = data/voc.names  //上一步修改的voc.names文件  
    backup = /home/xiao_run/darknet-master/results/  //训练后生成的权重存放位置

3、开始训练

上面完成了就可以命令训练了,可以在官网上找到一些预训练的模型作为参数初始值,也可以直接训练,训练命令为

./darknet detector train ./cfg/voc.data cfg/yolov2-tiny-voc.cfg 

如果用官网的预训练模型darknet.conv.weights做初始化,则训练命令为

./darknet detector train ./cfg/voc.data .cfg/yolov2-tiny-voc.cfg darknet.conv.weights  

训练过程中会根据迭代次数保存训练的权重模型,然后就可以拿来测试了,测试的命令:

./darknet detector test cfg/voc.data cfg/yolov2-tiny-voc.cfg results/yolov2-tiny-voc_6000.weights data/images.jpg

猜你喜欢

转载自blog.csdn.net/la_fe_/article/details/81564420