yolo 学习系列(二):训练自己的数据集
网络结构:yolov2-tiny-voc(以voc数据集为基础的yolov2的轻量型网络tiny)
数据集:VOC2007和VOC2012
采用该网络的原因:tiny的训练时间远远小于完全版本的yolov2,有助于达到目标同时节约时间。
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,000002,000003...
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文本。
- train.txt 是用来训练的图片文件的文件名列表
- val.txt是用来验证的图片文件的文件名列表
- trianval.txt是用来训练和验证的图片文件的文件名列表
- test.txt 是用来测试的图片文件的文件名列表
train.txt + val.txt = trianval.txt
trianval.txt + test.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
// YOLOv3版本中 filter=3×(classes + coords + 1)=3×(1+4+1)=18
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 darknet19_448.conv.23
训练过程中会根据迭代次数保存训练的权重模型,然后就可以拿来测试了,测试的命令:
./darknet detector test cfg/voc.data cfg/yolov2-tiny-voc.cfg results/yolov2-tiny-voc_900.weights data/000100.jpg
4、Windows下训练自己的数据集
4.1 准备数据集
同linux下的操作过程相同,即创建 VOCdevkit 文件夹
将该文件夹放在 F:\yolo-install\darknet-master\build\darknet\x64\data\voc路径下,并切换到该路径下,运行以下命令生成txt文件。
python voc_label.py
注意:Windows下VS25015编译安装好的 F:\yolo-install\darknet-master\build\darknet\x64\ 的路径相当于Ubuntu下 home/chris/darknet-master的路径, 因此在Windows下要切换到 F:\yolo-install\darknet-master\build\darknet\x64\ 内输入命令
训练:darknet.exe detector train data/voc.data cfg/yolov2-tiny-voc.cfg
测试:darknet.exe detector test data/voc.data cfg/yolov2-tiny-voc.cfg backup/yolov2-tiny-voc_6000.weights data/person.jpg
4.2 数据集标签与路径生成
修改 F:\yolo-install\darknet-master\build\darknet\x64\data 路径下的voc.data和voc.names文件
- voc.data设置训练集路径和权重保存位置
- voc.names设置名称
修改后的 voc.data 内容为
classes= 1
train = data/voc/2007_train.txt
valid = data/voc/2007_val.txt
#difficult = data/difficult_2007_test.txt
names = data/voc.names
backup = backup/
4.3 修改网络配置文件并开始训练
网络配置文件的修改内容与 linux 下的操作相同
打开cmd窗口 切换至 F:\yolo-install\darknet-master\build\darknet\x64\ 路径 运行一下命令即可训练
darknet.exe detector train data/voc.data cfg/yolov2-tiny-voc.cfg