公开数据集VOC中有20类,有时并不是所有的类都是我们所需要的,可以根据需要从中提取出特定类并重新制作成LMDB数据源。
第一步,从VOC数据集中提取特定的类,在VOC2007,VOC2012数据集下分别运行该代码即可提取出特定的类,注意修改代码中的地址,代码如下(Ubuntu环境):
# -*- coding: utf-8 -*-
# @Function:There are 20 classes in VOC data set. If you need to extract specific classes, you can use this program to extract them.
import os
import shutil
ann_filepath='/home/abc/data/VOCdevkit/VOC2012/Annotations/' #注意修改成自己的地址
img_filepath='/home/abc/data/VOCdevkit/VOC2012/JPEGImages/' #注意修改成自己的地址
img_savepath='/home/abc/data/VOCdevkit/VOC2012/JPEGImages_ssd/' #注意修改成自己的地址
ann_savepath='/home/abc/data/VOCdevkit/VOC2012/Annotations_ssd/' #注意修改成自己的地址
if not os.path.exists(img_savepath):
os.mkdir(img_savepath)
if not os.path.exists(ann_savepath):
os.mkdir(ann_savepath)
names = locals()
classes = ['aeroplane','bicycle','bird', 'boat', 'bottle',
'bus', 'car', 'cat', 'chair', 'cow','diningtable',
'dog', 'horse', 'motorbike', 'pottedplant',
'sheep', 'sofa', 'train', 'tvmonitor', 'person']
for file in os.listdir(ann_filepath):
print(file)
fp = open(ann_filepath + '//' + file)
ann_savefile=ann_savepath+file
fp_w = open(ann_savefile, 'w')
lines = fp.readlines()
ind_start = []
ind_end = []
lines_id_start = lines[:]
lines_id_end = lines[:]
classes1 = '\t\t<name>person</name>\n' #自己需要几类就写几类,我这只需要person这一类
#classes2 = '\t\t<name>motorbike</name>\n'
#classes3 = '\t\t<name>bus</name>\n'
#classes4 = '\t\t<name>car</name>\n'
#classes5 = '\t\t<name>bicycle</name>\n'
#在xml中找到object块,并将其记录下来
while "\t<object>\n" in lines_id_start:
a = lines_id_start.index("\t<object>\n")
ind_start.append(a)
lines_id_start[a] = "delete"
while "\t</object>\n" in lines_id_end:
b = lines_id_end.index("\t</object>\n")
ind_end.append(b)
lines_id_end[b] = "delete"
#names中存放所有的object块
i = 0
for k in range(0, len(ind_start)):
names['block%d' % k] = []
for j in range(0, len(classes)):
if classes[j] in lines[ind_start[i] + 1]:
a = ind_start[i]
for o in range(ind_end[i] - ind_start[i] + 1):
names['block%d' % k].append(lines[a + o])
break
i += 1
#print(names['block%d' % k])
#xml头
string_start = lines[0:ind_start[0]]
#xml尾
string_end = [lines[len(lines) - 1]]
#在给定的类中搜索,若存在则,写入object块信息
a = 0
for k in range(0, len(ind_start)):
if classes1 in names['block%d' % k]:
a += 1
string_start += names['block%d' % k]
#if classes2 in names['block%d' % k]:
a += 1
string_start += names['block%d' % k]
#if classes3 in names['block%d' % k]:
a += 1
string_start += names['block%d' % k]
#if classes4 in names['block%d' % k]:
a += 1
string_start += names['block%d' % k]
#if classes5 in names['block%d' % k]:
a += 1
string_start += names['block%d' % k]
string_start += string_end
for c in range(0, len(string_start)):
fp_w.write(string_start[c])
fp_w.close()
#如果没有我们寻找的模块,则删除此xml,有的话拷贝图片
if a == 0:
os.remove(ann_savepath+file)
else:
name_img = img_filepath + os.path.splitext(file)[0] + ".jpg"
shutil.copy(name_img, img_savepath)
fp.close()
第二步,重新制作VOC格式数据集
在caffe-ssd/data目录下新建一个文件夹命名为VOCperson,进入VOCperson目录下,新建VOC2007和VOC2012文件夹,然后再进入VOC2007和VOC2012文件夹下分别新建Annotations,ImageSets,JPEGImages三个文件夹,并在ImageSets文件夹内新建一个Main文件夹。
最后目录级别为(以VOC2007为例):
- caffe-ssd/data/VOCperson/VOC2007
- -----------------------------------------------/Annotations
- -----------------------------------------------/ImageSets
- --------------------------------------------------------------/Main
- -----------------------------------------------/JPEGImages
再将自己抽取的jpg图片(PEGImages_ssd文件中的图片)对应放入相应文件夹中的JPEGImages文件夹中;将自己抽取的数据集标签(应该和放入JPEGImages文件内的图片名一样且数量一样多)xml文件对应放入相应文件夹中的Annotations文件夹中。
最后,生成分别生成VOC2007和VOC2012文件夹中ImageSets文件夹下Main文件夹中的四个文件。
训练集: train.txt
训练验证集: trainval.txt
测试集: test.txt
验证集: val.txt
这些txt文件内容如下所示,记录的都是图片的索引号:
以VOC2012为例,在VOC2012文件夹下打开终端,并执行如下Python程序,即可生成ImageSets文件夹下Main文件夹下的四个文件。
import os
import random
trainval_percent = 1 #训练验证集占整个数据集的比例,根据需要自己修改
train_percent = 0.7 #训练集占训练验证集的比例,根据需要自己修改
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(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()
将新建好的VOCperson复制到HOME/data文件夹下,和VOCdevkit文件夹在同一目录下。
第三步,将制作好的VOC格式数据集转换为LMDB格式
在caffe-ssd/data目录下,新建一个VOC0712person文件夹,并在caffe-ssd/data目录下找到VOC0712文件夹,在VOC0712文件夹下找到create_data.sh, create_list.sh,labelmap_voc.prototxt三个文件。
create_list.sh: 用于生成训练集,测试集的文件路径txt文件和一个测试集目录名和图片大小的txt(trainval.txt,,test_size_name.txt,test.txt)
create_data.sh: 用于生成lmdb格式的训练数据集和测试集
labelmap_voc.prototxt: 里头是标签的信息
将这三个文件复制到自己新建的VOC0712person文件夹下。
最后,在caffe-ssd/examples文件夹内新建一个VOC0712person文件夹就行(存放生成的LMDB文件)
生成LMDB文件的具体过程:
首先修改create_list.sh(改成自己的目录就行),代码如下(这部分很重要,容易出错,详细介绍):
#!/bin/bash
root_dir=$HOME/data/VOCperson/ #存放数据的根目录
sub_dir=ImageSets/Main #存放ImageSets数据集的索引
bash_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" #dirname "${BASH_SOURCE[0]}"进行提取bash命令中第一个参数(这里是creat_list.sh文件所在的路径),详见https://blog.csdn.net/davidhopper/article/details/78989369
for dataset in trainval test #for循环(在trainval和test中循环各一次)(格式为for...do...done,详见https://www.cnblogs.com/hshanghai/p/3267211.html)
do
dst_file=$bash_dir/$dataset.txt
if [ -f $dst_file ] #-f filename 如果 filename为常规文件,则为真 (格式为if...then...fi),详见https://blog.csdn.net/xungjhj/article/details/73613883
then
rm -f $dst_file #删除该文件(上述if为真的情况下),详见http://man.linuxde.net/rm
fi #搭配if用的(表示if结束)
for name in VOC2007 VOC2012
do
if [[ $dataset == "test" && $name == "VOC2012" ]]
then
continue
fi
echo "Create list for $name $dataset..." #在终端打印出当前执行的结果
dataset_file=$root_dir/$name/$sub_dir/$dataset.txt #要提取的数据集索引文件路径
#以下对图像索引进行处理
img_file=$bash_dir/$dataset"_img.txt"
cp $dataset_file $img_file #将dataset_file复制给img_file,详见http://man.linuxde.net/cp
#sed用法详见https://www.cnblogs.com/lzeffort/p/7237268.html
#-i表示直接修改文件内容;s表示用后面的替换;^表示行首;/g表示搜索到的全部替换(与s联用);此句代码表示用$name\/JPEGImages\/替换掉所有行首
sed -i "s/^/$name\/JPEGImages\//g" $img_file
sed -i "s/$/.jpg/g" $img_file #$表示行尾;此句代码的意思是用.jpg替换掉所有行尾
#以下对图像对应的标签进行处理
label_file=$bash_dir/$dataset"_label.txt"
cp $dataset_file $label_file
sed -i "s/^/$name\/Annotations\//g" $label_file
sed -i "s/$/.xml/g" $label_file
paste -d' ' $img_file $label_file >> $dst_file #paste将文件进行合并;-d<间隔字符>表示用指定的间隔字符取代跳格字符;并写入dst_file中,详见http://www.runoob.com/linux/linux-comm-paste.html
rm -f $label_file
rm -f $img_file
done
# Generate image name and size infomation.
if [ $dataset == "test" ]
then
$bash_dir/../../build/tools/get_image_size $root_dir $dst_file $bash_dir/$dataset"_name_size.txt" #后三者为get_image_size这个函数的输入(详细参见/tools/get_image_size.cpp文件)
fi
# Shuffle trainval file. 随机重置顺序
if [ $dataset == "trainval" ]
then
rand_file=$dst_file.random
cat $dst_file | perl -MList::Util=shuffle -e 'print shuffle(<STDIN>);' > $rand_file
mv $rand_file $dst_file #覆盖,详见http://man.linuxde.net/mv
fi
done
代码本身是先获取VOC2007或VOC2012/ImageSets/Main下的trainval.txt和test.txt文档中的所有内容(文件里的内容都是图片的索引号), 然后利用以下语句(上面代码中摘抄出来的),将所有图片的索引和对应的标签文件索引写入同一个txt文档下。
img_file=$bash_dir/$dataset"_img.txt"
cp $dataset_file $img_file
sed -i "s/^/$name\/JPEGImages\//g" $img_file
sed -i "s/$/.jpg/g" $img_file
label_file=$bash_dir/$dataset"_label.txt"
cp $dataset_file $label_file
sed -i "s/^/$name\/Annotations\//g" $label_file
sed -i "s/$/.xml/g" $label_file
paste -d' ' $img_file $label_file >> $dst_file
结果如下图所示:
即同一行所表示的是一张图像的路径及其对应的标签文件的路径。
在此基础上,进一步获取测试集中图像的索引号和长宽,写入test_name_size.txt文件中,结果如下:
最后再对训练集(生成的trainval.txt文件,其实叫训练验证集更合适)中的每一行进行随机打散重排(增强随机性)。
然后改create_data.sh(改成自己的目录就行),代码如下:
cur_dir=$(cd $( dirname ${BASH_SOURCE[0]} ) && pwd )
root_dir=$cur_dir/../..
cd $root_dir
redo=1
data_root_dir="$HOME/data/VOCperson"
dataset_name="VOC0712person"
mapfile="$root_dir/data/$dataset_name/labelmap_voc.prototxt"
anno_type="detection"
db="lmdb"
min_dim=0
max_dim=0
width=0
height=0
extra_cmd="--encode-type=jpg --encoded"
if [ $redo ]
then
extra_cmd="$extra_cmd --redo"
fi
for subset in test trainval
do
python $root_dir/scripts/create_annoset.py --anno-type=$anno_type --label-map-file=$mapfile --min-dim=$min_dim --max-dim=$max_dim --resize-width=$width --resize-height=$height --check-label $extra_cmd $data_root_dir $root_dir/data/$dataset_name/$subset.txt $data_root_dir/$dataset_name/$db/$dataset_name"_"$subset"_"$db examples/$dataset_name
done
最后修改labelmap_voc.prototxt文件
除了第一个背景标签部分不要改,其他改成自己的标签就行,多的删掉,少了添加进去就行
最后,依次运行create_list.sh和create_data.sh文件,运行完后,会在自己建的examples/VOC0712person目录内生成lmdb文件夹: