RCSN
1. 制作自己的数据集
本次训练的目标是为了能够检测出一类不同的物体,即香砂六君丸的药品,如图所示:
该种物体采集27张(可加大),命名方式以数字形式进行命名
1.1 准备数据集
然后切换到darkent根目录,依次建立如下几个文件夹:
cd scripts
mkdir -p VOCdevkit && cd VOCdevkit
mkdir -p VOC2019 && cd VOC2019
mkdir -p Annotations && mkdir -p ImageSets && mkdir -p JPEGImages && mkdir -p labels
1.2 打乱顺序并重命名
将JPEGImages文件夹下的图像文件打乱顺序并重新命名.方法是执行如下的python脚本文件:
# -*- coding:utf-8 -*-
import os
import random
class ImageRename():
def __init__(self):
self.path = '/home/jetbot/darknet-master/scripts/VOCdevkit/VOC2019/JPEGImages'#图片所在文件夹
def rename(self):
filelist = os.listdir(self.path)
random.shuffle(filelist)
total_num = len(filelist)
i = 0
for item in filelist:
if item.endswith('.jpg'):
src = os.path.join(os.path.abspath(self.path), item)
dst = os.path.join(os.path.abspath(self.path), '0000' + format(str(i), '0>3s') + '.jpg')
os.rename(src, dst)
print ('converting %s to %s ...' % (src, dst))
i = i + 1
print ('total %d to rename & converted %d jpgs' % (total_num, i))
if __name__ == '__main__':
newname = ImageRename()
newname.rename()
执行命令之后:
1.3 数据标注(labelImg)
该步骤用于对每张图片生成描述文件,我们需要认为指定图片里哪一块区域是我们要检测的物体.每张图片都要以一个xml文件进行描述.数据标注的工具很多,在这里我们选择labelImg.首先安装,在这里该插件对python版本有要求,python2和python3的安装方式不同,选择任意一种即可,区别在于最后执行过程.
若为python3,安装过程如下:
sudo apt-get install pyqt5-dev-tools
sudo pip3 install lxml
cd darknet && mkdir -p software && cd software
git clone https://github.com/tzutalin/labelImg.git
cd labelImg
make all(可不用)
若为python2,安装过程如下:
sudo apt-get install pyqt4-dev-tools
sudo pip install lxml
cd darknet && mkdir -p software && cd software
git clone https://github.com/tzutalin/labelImg.git
cd labelImg
make all(可不用)
至此,安装已完成,下面可以运行如下命令打开该软件进行标注了:
若为python3安装,则打开软件的方式为:
python3 labelImg.py #打开labelImg
若为python2安装,则打开软件的方式为
python labelImg.py #打开labelImg
软件界面如下:
然后点击左侧的"Open Dir"按键,选择我们存放数据图片的文件夹JPEGImages,如下:
然后点击"Open",之后会出现第一张图片,对于一张图片的标注,一般分如下几个步骤:
1)点击左侧的"Create \n RectBox"按键,然后找准我们要识别的物体,从左上角到右下角拖一个矩形,
2) 在弹出的窗口种创建标签,如下图所示,我们需要选择"watermenlon",如果不出现,要手动敲一个,添加进去,并选择,如果图片中有多个待识别的物体,则再此拉矩形框,并选择类别.
3) 选择完成后,点击保存或者键盘按"ctrl+s",则会在JPEGImages文件夹下生成对应00001.jpg的xml文件00001.xml.
4)按键盘"n"来进入下一张图片.
按照上述步骤依次进行,直到标注完毕.
注: 若在标注过程中,有些图片比较模糊,我们可以不进行标注,直接进入下一张图片.
标注全部完成后,我们会在JPEGImages文件夹中看到jpg文件和xml文件共存,在这这里还需要删除我们之前没有标注的图片,它们不具备任何作用.所以就需要我们进行排查和清除,方法有两个,对于样本较少的,建议第一种:
打开文件夹,将文件夹水平方向缩小为只能包含两列,即左侧为jpg图片,右侧为xml文件,若某一行出现有两个jpg图片,则删除左边那个,不断向下拖动滑动条,直到尾断即完成了排查和删除.
import os
import os.path
h = 0
a = ''
b = ''
dele = []
pathh = "/home/jetbot/darknet-master/scripts/VOCdevkit/VOC2019/JPEGImages"
#dele.remove(1)
for filenames in os.walk(pathh):
filenames = list(filenames)
filenames = filenames[2]
for filename in filenames:
print(filename)
if h==0:
a = filename
h = 1
elif h==1:
#print(filename)
b = filename
if a[0:a.rfind('.', 1)]==b[0:b.rfind('.', 1)]:
h = 0
#print(filename)
else:
h = 1
dele.append(a)
a = b
else:
print("wa1")
print(dele)
for file in dele:
os.remove(pathh+file)
print("remove"+file+" is OK!")
#再循环一次看看有没有遗漏的单身文件
for filenames in os.walk(pathh):
filenames = list(filenames)
filenames = filenames[2]
for filename in filenames:
print(filename)
if h==0:
a = filename
h = 1
elif h==1:
#print(filename)
b = filename
if a[0:a.rfind('.', 1)]==b[0:b.rfind('.', 1)]:
h = 0
#print(filename)
else:
h = 1
dele.append(a)
a = b
else:
print("wa1")
print (dele)
至此数据标注工作完毕,我们还需要一个小工作,就是将xml文件全部从JPEGImages移出到Annotations文件夹下,方法为:
cd JPEGImages mv *.xml ../Annotations/
至此,JPEGImages为全部的图像数据,Annotations中为对应的xml描述文件.
1.4 指定训练集和测试集
我们做好的数据集要一部分作为训练集来训练模型,需要另一部分作为测试集来帮助我们验证模型的可靠性.因此首先要将所有的图像文件随机分配为训练集和测试集.
首先切换到ImageSets目录中,新建Main目录,然后在Main目录中新建两个文本文档train.txt和val.txt.分别用于存放训练集的文件名列表和测试集的文件名列表.
cd ImageSets mkdir -p Main && cd Main touch train.txt test.txt val.txt
然后执行如下脚本文件来生成训练集和测试集,注意该文件中的20为我选择的训练集的个数.400默认为测试集
#coding:utf-8
import os
from os import listdir, getcwd
from os.path import join
if __name__ == '__main__':
source_folder='/home/rcsn/lrc/darknet/scripts/VOCdevkit/VOC2019/JPEGImages/' # 修改为自己的路径
dest='/home/rcsn/lrc/darknet/scripts/VOCdevkit/VOC2019/ImageSets/Main/train.txt' # 修改为自己的路径
dest2='/home/rcsn/lrc/darknet/scripts/VOCdevkit/VOC2019/ImageSets/Main/test.txt' # 修改为自己的路径
file_list=os.listdir(source_folder)
train_file=open(dest,'a')
val_file=open(dest2,'a')
count = 0
for file_obj in file_list:
count += 1
file_name,file_extend=os.path.splitext(file_obj)
if(count<20): # 可以修改这个数字,这个数字用来控制训练集合验证集的分割情况
train_file.write(file_name+'\n')
else :
val_file.write(file_name+'\n')
train_file.close()
val_file.close()
运行完成后可以在train.txt和val.txt文件如下所示:
1.5 指定训练集和测试集的实际路径和标签文件
修改script文件夹根目录下的voc_label.py文件,需要修改几处:
1)sets=[ (‘2019’, ‘train’), (‘2019’, ‘val’), ('2019', 'test')] ,2019为我们设置的文件夹标识 ,"train"和"val"指代训练集和测试集
2)classes = ["pill"] ,修改为我们要识别的四类物体名称
如下:
import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join
sets=[ ('2019', 'train'), ('2019', 'val'), ('2019', 'test')]
classes = ["pill"]
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 2019_train.txt 2019_test.txt > train.txt")
os.system("cat 2019_train.txt 2019_val.txt 2019_test.txt > train.all.txt")
修改完成后,执行如下命令,即可在labels文件夹下生成标签文件.如下:
在scripts根目录下,也会生成2019_train.txt和2019_test.txt文件,与之前Main中的train.txt和var.txt文件不同,其内容全部为训练图片或测试图片的实际路径.
1.6 增加一些必要文件
1.6.1 增加data目录下的pill.names
1.6.2 增加 cfg目录下的pill_demo.data文件(可从voc.data复制)
主要修改的点为:
1)classes 因为,我们是1类,所以,classes = 1;
2) 修改训练集:train = /home/rcsn/lrc/darknet/scripts/2019_train.txt
3) 修改测试集valid =/home/rcsn/lrc/darknet/scripts/2019_test.txt
4) 修改标签名:names = /home/rcsn/lrc/darknet/data/pill_demo.names
5) 修改训练过程中生成的过程结果存放地址backup = /home/rcsn/lrc/darknet/backup/
修改后,如下:
1.6.3 增加 cfg目录下的yolov4-tiny-rcsn.cfg文件(从yolov4-tiny-custom.cfg复制)
修改地方主要有如下几个:
- 有两处需要修改classes和filters,从下往上有两处,类似如下字样处:
将classes 修改为1,因为我们只有1类;将卷积层数修改为18 ,计算方式为3*(类别数+5).
我们任务是测试,则需要将该文件上方Testing下两行的batch=1,subdivisions=1全部注释掉,将# Training下两行的batch=64,subdivisions=16全部取消注释.
至此,训练前的准备工作已全部完成了.
2.训练
(需要下载yolov4-tiny.conv.29)
./darknet detector train cfg/pill_demo.data cfg/yolov4-tiny-rcsn.cfg yolov4-tiny.conv.29 | tee pill_train_log.txt
保存log时会生成两个文件,一个保存的是网络加载信息和checkout点保存信息,另一个保存的是训练信息。
训练耗时较长,查看log当loss较小,且不再发生变化时,可按"ctrl+c"终止训练.我训练到了90000次就停止了,在这个过程中在backup文件夹下会保存对应迭代次数的中间结果,前1000次内每100次保存一个,超过1000次,每1000保存一次,依次.在这个过程中,我么可以随时拿中间结果进行测试.
3.测试(图片)
./darknet detect cfg/yolov4-tiny-rcsn.cfg yolov4-tiny-rcsn_final.weights data/pill1.jpg
视频测试 csi摄像头
./darknet detector demo cfg/pill_demo.data cfg/yolov4-tiny-rcsn.cfg yolov4-tiny-rcsn_final.weights "nvarguscamerasrc ! video/x-raw(memory:NVMM), width=1280, height=720, format=NV12, framerate=30/1 ! nvvidconv flip-method=0 ! video/x-raw, width=1280, height=720, format=BGRx ! videoconvert ! video/x-raw, format=BGR ! appsink"