制作PASCAL VOC2007格式的数据集
本博客转自https://blog.****.net/hitzijiyingcai/article/details/81636455,感谢大佬的雕琢和分享。
一、前言
在利用诸如Faster R-CNN等深度学习网络进行目标检测的时候一定需要训练自己的数据集,一般有两种方法:
按照VOC2007的格式修改自己的数据格式
根据自己的数据格式修改源码
一般推荐第一种方法,因为第一种方法比较简单而且不容易出错,在制作为VOC2007格式的数据集之前,这里可以下载原始VOC2007数据集:VOC2007数据集,来看看这个数据集到底是什么样的:
解压VOC2007数据集后可以看到VOC2007文件夹下有以下5个文件夹:
Annotations文件夹
该文件下存放的是xml格式的标签文件,每个xml文件都对应于JPEGImages文件夹的一张图片。
JPEGImages文件夹
改文件夹下存放的是数据集图片,包括训练和测试图片。
ImageSets文件夹
该文件夹下存放了三个文件,分别是Layout、Main、Segmentation。在这里我们只用存放图像数据的Main文件,其他两个暂且不管。
SegmentationClass文件和SegmentationObject文件。
这两个文件都是与图像分割相关,跟本文无关,暂且不管。
1、Annotations
Annotations文件夹中存放的是xml格式的标签文件,每一个xml文件都对应于JPEGImages文件夹中的一张图片。xml文件的解析如下:
<annotation>
<folder>VOC2007</folder>
<filename>2007_000392.jpg</filename> //文件名
<source> //图像来源(不重要)
<database>The VOC2007 Database</database>
<annotation>PASCAL VOC2007</annotation>
<image>flickr</image>
</source>
<size> //图像尺寸(长宽以及通道数)
<width>500</width>
<height>332</height>
<depth>3</depth>
</size>
<segmented>1</segmented> //是否用于分割(在图像物体识别中01无所谓)
<object> //检测到的物体
<name>horse</name> //物体类别
<pose>Right</pose> //拍摄角度
<truncated>0</truncated> //是否被截断(0表示完整)
<difficult>0</difficult> //目标是否难以识别(0表示容易识别)
<bndbox> //bounding-box(包含左下角和右上角xy坐标)
<xmin>100</xmin>
<ymin>96</ymin>
<xmax>355</xmax>
<ymax>324</ymax>
</bndbox>
</object>
<object> //检测到多个物体
<name>person</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>198</xmin>
<ymin>58</ymin>
<xmax>286</xmax>
<ymax>197</ymax>
</bndbox>
</object>
</annotation>
2、JPEGImages
JPEGImages 内部存放了PASCAL VOC所提供的所有的图片信息,包括了训练图片和测试图片
这些图像的像素尺寸大小不一,但是横向图的尺寸大约在500*375左右,纵向图的尺寸大约在375*500左右,基本不会偏差超过100。(在之后的训练中,第一步就是将这些图片都resize到300*300或是500*500,所有原始图片不能离这个标准过远。
3、 ImageSets
ImageSets存放的是每一种类型的challenge对应的图像数据。
Layout下存放的是具有人体部位的数据(人的head、hand、feet等等,这也是VOC challenge的一部分)
Main下存放的是图像物体识别的数据,总共分为20类。
Segmentation下存放的是可用于分割的数据。
在这里主要考察Main文件夹。
Main文件夹下包含了每个分类的train.txt、val.txt和trainval.txt。
这些txt中的内容都差不多如下:
000005 -1
000007 -1
000009 1
000016 -1
000019 -1
前面的表示图像的name,后面的1代表正样本,-1代表负样本。
_train中存放的是训练使用的数据
_val中存放的是验证结果使用的数据
_trainval将上面两个进行了合并
需要保证的是train和val两者没有交集,也就是训练数据和验证数据不能有重复,在选取训练数据的时候 ,也应该是随机产生的。
二、制作PASCAL VOC形式的数据集
制作自己的VOC2007格式数据集其实不需要上述那么多内容,我们只要做三个部分即可:Annotations文件夹、JPEGImages文件夹、ImageSets文件夹下的Main文件。
step1:JPEGImages
参照原始VOC2007数据集的文件层次创建上述四个文件夹,也就是创建一个VOCdevkit文件夹,下面再创建Annotations、JPEGImages、ImageSets三个文件夹,最后在ImageSets文件夹下再创建一个Main文件夹。
创建好所有文件夹后,我们将自己的数据集图片都放到JPEGImages文件夹下。按照习惯,我们将图片的名字修改为000001.jpg这种格式的(参照原始数据集图片命名规则),统一命名方法,可以参考如下代码:
import os
path = "E:\\image"
filelist = os.listdir(path) #该文件夹下所有的文件(包括文件夹)
count=0
for file in filelist:
print(file)
for file in filelist: #遍历所有文件
Olddir=os.path.join(path,file) #原来的文件路径
if os.path.isdir(Olddir): #如果是文件夹则跳过
continue
filename=os.path.splitext(file)[0] #文件名
filetype=os.path.splitext(file)[1] #文件扩展名
Newdir=os.path.join(path,str(count).zfill(6)+filetype) #用字符串函数zfill 以0补全所需位数
os.rename(Olddir,Newdir)#重命名
强调两点:第一点是图片的格式,图片需是JPEG或者JPG格式,其他格式需要转换一下。第二点是图片的长宽比,图片长宽比不能太大或太小,这个参考原始VOC2007数据集图片即可。
step2:Annotations
LabelImg
制作Annotations文件夹下所需要存放的xml文件需要借助图片标注工具LabelImg,在github上有源码:https://github.com/tzutalin/labelImg。关于它的使用,本人采用的是在Linux系统下的操作,Windows系统下的操作在这里附上两个链接需要的自行点开https://blog.****.net/zhyj3038/article/details/54923781/,https://blog.****.net/jesse_mx/article/details/53606897,而在Linux系统操作如下:
由于Ubuntu系统自带python,这款软件在Ubuntu环境下的安装是最方便的。软件要求python版本在2.6以上,同时需要PyQt和lxml的支持。
sudo apt-get install pyqt4-dev-tools #安装PyQt4
sudo pip install lxml #安装lxml,如果报错,可以试试下面语句
sudo apt-get install python-lxml
接下来位下载github上的安装包,下载地址为https://github.com/tzutalin/labelImg,也可通过以下代码直接获取:
git clone https://github.com/tzutalin/labelImg.git
下载后进入下载目录,copy到home目录下解压,解压后进入目录
cd labelImg
pyrcc4 -o resources.py resources.qrc
python labelImg.py
即可打开界面:
此软件的使用方法
修改默认的XML文件保存位置,使用快捷键“Ctrl+R”,改为自定义位置,这里的路径一定不能包含中文,否则无法保存。
源码文件夹中使用notepad++打开data/predefined_classes.txt,修改默认类别,比如改成person、car、motorcycle三个类别。
“Open Dir”打开图片文件夹,选择第一张图片开始进行标注,使用“Create RectBox”或者“Ctrl+N”开始画框,单击结束画框,再双击选择类别。完成一张图片后点击“Save”保存,此时XML文件已经保存到本地了。点击“Next Image”转到下一张图片。
标注过程中可随时返回进行修改,后保存的文件会覆盖之前的。
完成标注后打开XML文件,发现确实和PASCAL VOC所用格式一样。
说明:每标注完一张图片后进行保存,保存的xml文件名要与对应图片名一致,大家可以参考原始VOC2007数据集中JPEGImages文件夹下图片的命名和Annotations文件夹中的xml文件命名规则。
以上步骤生成的是txt文件,接下来要做的是将此label转换成VOC数据格式
首先建立一个VOC2007文件夹,在其下面建立JPEGImages,Annotations,label文件夹,将前面生成的所有txt文件转放到label文件夹下,并将所有的图片转移到JPEGImages文件夹下。
建立一个**.py文件,完成txt到xml转换的脚本,放到和label文件夹同一目录下,执行脚本Python **.py,生成xml,此py文件代码如下:
import os
import sys
import cv2
from itertools import islice
from xml.dom.minidom import Document
labels = 'label'
imgpath = 'JPEGImages/'
xmlpath_new = 'Annotations/'
foldername = 'VOC2007'
def insertObject(doc, datas):
obj = doc.createElement('object')
name = doc.createElement('name')
name.appendChild(doc.createTextNode(datas[0]))
obj.appendChild(name)
pose = doc.createElement('pose')
pose.appendChild(doc.createTextNode('Unspecified'))
obj.appendChild(pose)
truncated = doc.createElement('truncated')
truncated.appendChild(doc.createTextNode(str(0)))
obj.appendChild(truncated)
difficult = doc.createElement('difficult')
difficult.appendChild(doc.createTextNode(str(0)))
obj.appendChild(difficult)
bndbox = doc.createElement('bndbox')
xmin = doc.createElement('xmin')
xmin.appendChild(doc.createTextNode(str(datas[1])))
bndbox.appendChild(xmin)
ymin = doc.createElement('ymin')
ymin.appendChild(doc.createTextNode(str(datas[2])))
bndbox.appendChild(ymin)
xmax = doc.createElement('xmax')
xmax.appendChild(doc.createTextNode(str(datas[3])))
bndbox.appendChild(xmax)
ymax = doc.createElement('ymax')
if '\r' == str(datas[4])[-1] or '\n' == str(datas[4])[-1]:
data = str(datas[4])[0:-1]
else:
data = str(datas[4])
ymax.appendChild(doc.createTextNode(data))
bndbox.appendChild(ymax)
obj.appendChild(bndbox)
return obj
def create():
for walk in os.walk(labels):
for each in walk[2]:
fidin = open(walk[0] + '/' + each, 'r')
objIndex = 0
for data in islice(fidin, 1, None):
objIndex += 1
data = data.strip('\n')
datas = data.split(' ')
if 5 != len(datas):
print 'bounding box information error'
continue
pictureName = each.replace('.txt', '.jpg')
imageFile = imgpath + pictureName
img = cv2.imread(imageFile)
imgSize = img.shape
if 1 == objIndex:
xmlName = each.replace('.txt', '.xml')
f = open(xmlpath_new + xmlName, "w")
doc = Document()
annotation = doc.createElement('annotation')
doc.appendChild(annotation)
folder = doc.createElement('folder')
folder.appendChild(doc.createTextNode(foldername))
annotation.appendChild(folder)
filename = doc.createElement('filename')
filename.appendChild(doc.createTextNode(pictureName))
annotation.appendChild(filename)
source = doc.createElement('source')
database = doc.createElement('database')
database.appendChild(doc.createTextNode('My Database'))
source.appendChild(database)
source_annotation = doc.createElement('annotation')
source_annotation.appendChild(doc.createTextNode(foldername))
source.appendChild(source_annotation)
image = doc.createElement('image')
image.appendChild(doc.createTextNode('flickr'))
source.appendChild(image)
flickrid = doc.createElement('flickrid')
flickrid.appendChild(doc.createTextNode('NULL'))
source.appendChild(flickrid)
annotation.appendChild(source)
owner = doc.createElement('owner')
flickrid = doc.createElement('flickrid')
flickrid.appendChild(doc.createTextNode('NULL'))
owner.appendChild(flickrid)
name = doc.createElement('name')
name.appendChild(doc.createTextNode('idaneel'))
owner.appendChild(name)
annotation.appendChild(owner)
size = doc.createElement('size')
width = doc.createElement('width')
width.appendChild(doc.createTextNode(str(imgSize[1])))
size.appendChild(width)
height = doc.createElement('height')
height.appendChild(doc.createTextNode(str(imgSize[0])))
size.appendChild(height)
depth = doc.createElement('depth')
depth.appendChild(doc.createTextNode(str(imgSize[2])))
size.appendChild(depth)
annotation.appendChild(size)
segmented = doc.createElement('segmented')
segmented.appendChild(doc.createTextNode(str(0)))
annotation.appendChild(segmented)
annotation.appendChild(insertObject(doc, datas))
else:
annotation.appendChild(insertObject(doc, datas))
try:
f.write(doc.toprettyxml(indent=' '))
f.close()
fidin.close()
except:
pass
if __name__ == '__main__':
create()
step3:ImageSets
接下来为制作ImageSets文件夹下Main文件夹中的4个文件(test.txt、train.txt、trainval.txt、val.txt)。
首先说明一下这四个文件到底是干什么用的:
test.txt:测试集
train.txt:训练集
val.txt:验证集
trainval.txt:训练和验证集
在原始VOC2007数据集中,trainval大约占整个数据集的50%,test大约为整个数据集的50%;train大约是trainval的50%,val大约为trainval的50%。所以我们可参考以下代码来生成这4个txt文件:
import os
import random
trainval_percent = 0.5
train_percent = 0.5
xmlfilepath = '/home/hqd/桌面/VOC2010/Annotations'
txtsavepath = '/home/hqd/桌面/VOC2010/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(txtsavepath+'/trainval.txt', 'w')
ftest = open(txtsavepath+'/test.txt', 'w')
ftrain = open(txtsavepath+'/train.txt', 'w')
fval = open(txtsavepath+'/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()
注意:上述代码中涉及到的路径要写全,另外各个数据集所占比例根据实际数据集的大小调整比例。
至此,我们自己的VOC2007格式数据集就全部制作完成了。