Single Shot MultiBox Detector)不得不说的那些事"/>
SSD(Single Shot MultiBox Detector)不得不说的那些事
该方法出自2016年的一篇ECCV的oral paper,SSD: Single Shot MultiBoxDetector,算是一个革命性的方法了,非常值得学习和研究。
论文解析:
SSD的特殊之处主要体现在以下3点:
(1)多尺度的特征图检测(Multi-scale),如SSD同时使用了上图所示的8*8的特征图和4*4特征图。
(2)相比于YOLO,作者使用的是卷积层来代替了YOLO的全连接层做预测。(如下图所示)
(3)SSD使用了默认的边界框+(1,2/1,3/1,1/2,1/3)6个框来做检测(aspect ratios)
训练过程提出了Smooth L1 loss+softmax loss,将位置定位的准确度值和得分置信度融合起来,从而使得对目标物的检测和识别都表现出state-of-the-art的效果。
整体损失函数公式如下,第一项为置信度的损失,第二项为位置的损失,N为匹配的默认边框的数目,a为平衡因子,交叉验证的时候取值为1。
位置损失的详细公式如下:
置信度损失的公式如下:
该方法包括SSD300和SSD512,2个尺度的训练模型,SSD300的速度更快,SSD512的检测效果更好。相比与其他方法,优势在于SSD的mAP高于YOLO,faster RCNN,速度虽然弱于YOLO,但是完全满足实时应用。不足之处在于对小物体的检测效果不好。VOC2007上的测试效果如下:
作者github提供的是Linux 的版本,对caffe的源码做了很大的改动,加进了很多的层,像NormalizeLayer,PermuteLayer,FlattenLayer,PriorBoxLayer,ConcatLayer,ReshapeLayer,DetectionOutputLayer等。所以linux的童鞋最好直接下载作者的caffe进行编译。这里就不在赘述。linux下的运行效果如下,
训练模型(作者VOC2007&VOC2012数据集):
1,VGGNet下载,
wget .caffemodel
2,VOC2007,VOC2012数据集下载,并解压
# Download the data.
cd $HOME/data
wget .tar
wget .tar
wget .tar
# Extract the data.
tar -xvf VOCtrainval_11-May-2012.tar
tar -xvf VOCtrainval_06-Nov-2007.tar
tar -xvf VOCtest_06-Nov-2007.tar
3,生成LMDB数据
cd $CAFFE_ROOT
# Create the trainval.txt, test.txt, and test_name_size.txt in data/VOC0712/
./data/VOC0712/create_list.sh
# You can modify the parameters in create_data.sh if needed.
# It will create lmdb files for trainval and test with encoded original image:
# - $HOME/data/VOCdevkit/VOC0712/lmdb/VOC0712_trainval_lmdb
# - $HOME/data/VOCdevkit/VOC0712/lmdb/VOC0712_test_lmdb
# and make soft links at examples/VOC0712/
./data/VOC0712/create_data.sh
这里可能会出现一个错误,AttributeError: 'module' object has noattribute 'LabelMap' error,
解决方法,export PYTHON PATH=$CAFFE_ROOT/python:$PYTHONPATH
4,训练,
# It will create model definition files and save snapshot models in:
# - $CAFFE_ROOT/models/VGGNet/VOC0712/SSD_300x300/
# and job file, log file, and the python script in:
# - $CAFFE_ROOT/jobs/VGGNet/VOC0712/SSD_300x300/
# and save temporary evaluation results in:
# - $HOME/data/VOCdevkit/results/VOC2007/SSD_300x300/
# It should reach 72.* mAP at 60k iterations.
python examples/ssd/ssd_pascal.py
可能的错误,Check failed: error == cudaSuccess (10 vs. 0) invalid device ordinal,
解决方法,
vim examples/ssd/ssd_pascal.py
gg 285
将,gpus = "0,1,2,3",改为,gpus = "0",因为本人只有一块显卡
到此,就可以静静的等待训练结果了。
建议训练配置,内存8G+,显卡8G+,(本人训练内存占用7.8G,显卡占用6.8G),训练过程大概24个小时(单卡titanx),模型效果和作者提供的模型的效果一样。
训练模型(自己的数据集):
(1)在/home/data/VOCdevkit/目录下
mkdir VOCmy
cd VOCmy
mkdir Annotations Imagesets JPEGImages
cd Imagesets/
mkdir Main
其中,
Annotations 为图片对应的xml信息文件,里面主要存放图像中objects的位置类别等信息
Imagesets中存放Layout,Main,Segmentation三个文件夹选项,这里我们主要使用Main 文件夹,里面用于存放train.txt,train_val.txt,val.txt,test.txt
JPEGImages中存放我们自己的图片
(2)将训练用到的图片都copy到JPEGImages目录下
(3)制作每个图片对应的xml文件,这里提供一个可以将txt信息转化为xml信息的程序,
txt中的信息格式如下:
000001.jpg dog48 240 195 371
000001.jpgperson 8 12 352 498
000003.jpg sofa123 155 215 195
000003.jpg chair239 156 307 205
000002.jpg train139 200 207 301
matlab转化程序如下:
%%
%该代码可以做voc2007数据集中的xml文件,
%txt文件每行格式为:000001.jpg dog 48 240 195 371
%即每行由图片名、目标类型、包围框坐标组成,空格隔开
%如果一张图片有多个目标,则格式如下:(比如两个目标)
% 000001.jpg dog 48 240 195 371
% 000001.jpg person 8 12 352 498
% 000002.jpg train 139 200 207 301
% 000003.jpg sofa 123 155 215 195
% 000003.jpg chair 239 156 307 205
%包围框坐标为左上角和右下角
%%
clc;
clear;
%注意修改下面四个变量
imgpath='img\';%图像存放文件夹
txtpath='img\output.txt';%txt文件
xmlpath_new='Annotations/';%修改后的xml保存文件夹
foldername='VOC2007';%xml的folder字段名fidin=fopen(txtpath,'r');
lastname='begin';while ~feof(fidin)tline=fgetl(fidin);str = regexp(tline, ' ','split');filepath=[imgpath,str{1}];img=imread(filepath);[h,w,d]=size(img);imshow(img);rectangle('Position',[str2double(str{3}),str2double(str{4}),str2double(str{5})-str2double(str{3}),str2double(str{6})-str2double(str{4})],'LineWidth',4,'EdgeColor','r');pause(0.1);if strcmp(str{1},lastname)%如果文件名相等,只需增加objectobject_node=Createnode.createElement('object');Root.appendChild(object_node);node=Createnode.createElement('name');node.appendChild(Createnode.createTextNode(sprintf('%s',str{2})));object_node.appendChild(node);node=Createnode.createElement('pose');node.appendChild(Createnode.createTextNode(sprintf('%s','Unspecified')));object_node.appendChild(node);node=Createnode.createElement('truncated');node.appendChild(Createnode.createTextNode(sprintf('%s','0')));object_node.appendChild(node);node=Createnode.createElement('difficult');node.appendChild(Createnode.createTextNode(sprintf('%s','0')));object_node.appendChild(node);bndbox_node=Createnode.createElement('bndbox');object_node.appendChild(bndbox_node);node=Createnode.createElement('xmin');node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(str{3}))));bndbox_node.appendChild(node);node=Createnode.createElement('ymin');node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(str{4}))));bndbox_node.appendChild(node);node=Createnode.createElement('xmax');node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(str{5}))));bndbox_node.appendChild(node);node=Createnode.createElement('ymax');node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(str{6}))));bndbox_node.appendChild(node);else %如果文件名不等,则需要新建xmlcopyfile(filepath, 'JPEGImages');%先保存上一次的xmlif exist('Createnode','var')tempname=lastname;tempname=strrep(tempname,'.jpg','.xml');xmlwrite(tempname,Createnode); endCreatenode=com.mathworks.xml.XMLUtils.createDocument('annotation');Root=Createnode.getDocumentElement;%根节点node=Createnode.createElement('folder');node.appendChild(Createnode.createTextNode(sprintf('%s',foldername)));Root.appendChild(node);node=Createnode.createElement('filename');node.appendChild(Createnode.createTextNode(sprintf('%s',str{1})));Root.appendChild(node);source_node=Createnode.createElement('source');Root.appendChild(source_node);node=Createnode.createElement('database');node.appendChild(Createnode.createTextNode(sprintf('The VOC2007 Database')));source_node.appendChild(node);node=Createnode.createElement('annotation');node.appendChild(Createnode.createTextNode(sprintf('PASCAL VOC2007')));source_node.appendChild(node);node=Createnode.createElement('image');node.appendChild(Createnode.createTextNode(sprintf('flickr')));source_node.appendChild(node);node=Createnode.createElement('flickrid');node.appendChild(Createnode.createTextNode(sprintf('NULL')));source_node.appendChild(node);owner_node=Createnode.createElement('owner');Root.appendChild(owner_node);node=Createnode.createElement('flickrid');node.appendChild(Createnode.createTextNode(sprintf('NULL')));owner_node.appendChild(node);node=Createnode.createElement('name');node.appendChild(Createnode.createTextNode(sprintf('watersink')));owner_node.appendChild(node);size_node=Createnode.createElement('size');Root.appendChild(size_node);node=Createnode.createElement('width');node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(w))));size_node.appendChild(node);node=Createnode.createElement('height');node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(h))));size_node.appendChild(node);node=Createnode.createElement('depth');node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(d))));size_node.appendChild(node);node=Createnode.createElement('segmented');node.appendChild(Createnode.createTextNode(sprintf('%s','0')));Root.appendChild(node);object_node=Createnode.createElement('object');Root.appendChild(object_node);node=Createnode.createElement('name');node.appendChild(Createnode.createTextNode(sprintf('%s',str{2})));object_node.appendChild(node);node=Createnode.createElement('pose');node.appendChild(Createnode.createTextNode(sprintf('%s','Unspecified')));object_node.appendChild(node);node=Createnode.createElement('truncated');node.appendChild(Createnode.createTextNode(sprintf('%s','0')));object_node.appendChild(node);node=Createnode.createElement('difficult');node.appendChild(Createnode.createTextNode(sprintf('%s','0')));object_node.appendChild(node);bndbox_node=Createnode.createElement('bndbox');object_node.appendChild(bndbox_node);node=Createnode.createElement('xmin');node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(str{3}))));bndbox_node.appendChild(node);node=Createnode.createElement('ymin');node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(str{4}))));bndbox_node.appendChild(node);node=Createnode.createElement('xmax');node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(str{5}))));bndbox_node.appendChild(node);node=Createnode.createElement('ymax');node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(str{6}))));bndbox_node.appendChild(node);lastname=str{1};end%处理最后一行if feof(fidin)tempname=lastname;tempname=strrep(tempname,'.jpg','.xml');xmlwrite(tempname,Createnode);end
end
fclose(fidin);file=dir(pwd);
for i=1:length(file)if length(file(i).name)>=4 && strcmp(file(i).name(end-3:end),'.xml')fold=fopen(file(i).name,'r');fnew=fopen([xmlpath_new file(i).name],'w');line=1;while ~feof(fold)tline=fgetl(fold);if line==1line=2;continue;endexpression = ' ';replace=char(9);newStr=regexprep(tline,expression,replace);fprintf(fnew,'%s\n',newStr);endfprintf('已处理%s\n',file(i).name);fclose(fold);fclose(fnew);delete(file(i).name);end
end
转化前后比对效果如下,左面为原始图像,中间为转化后的,右面为原始VOC的,
转化完毕,将其都copy到Annotations目录下
(4)生成Imagesets/Main/,下的train.txt,train_val.txt,val.txt,test.txt,matlab程序如下,
%%
%该代码根据已生成的xml,制作VOC2007数据集中的trainval.txt;train.txt;test.txt和val.txt
%trainval占总数据集的50%,test占总数据集的50%;train占trainval的50%,val占trainval的50%;
%上面所占百分比可根据自己的数据集修改,如果数据集比较少,test和val可少一些
%
%注意修改下面四个值
%%
xmlfilepath='E:\Annotations';
txtsavepath='E:\ImageSets\Main\';
trainval_percent=0.5;%trainval占整个数据集的百分比,剩下部分就是test所占百分比
train_percent=0.5;%train占trainval的百分比,剩下部分就是val所占百分比xmlfile=dir(xmlfilepath);
numOfxml=length(xmlfile)-2;%减去.和.. ?总的数据集大小trainval=sort(randperm(numOfxml,floor(numOfxml*trainval_percent)));
test=sort(setdiff(1:numOfxml,trainval));trainvalsize=length(trainval);%trainval的大小
train=sort(trainval(randperm(trainvalsize,floor(trainvalsize*train_percent))));
val=sort(setdiff(trainval,train));ftrainval=fopen([txtsavepath 'trainval.txt'],'w');
ftest=fopen([txtsavepath 'test.txt'],'w');
ftrain=fopen([txtsavepath 'train.txt'],'w');
fval=fopen([txtsavepath 'val.txt'],'w');for i=1:numOfxmlif ismember(i,trainval)fprintf(ftrainval,'%s\n',xmlfile(i+2).name(1:end-4));if ismember(i,train)fprintf(ftrain,'%s\n',xmlfile(i+2).name(1:end-4));elsefprintf(fval,'%s\n',xmlfile(i+2).name(1:end-4));endelsefprintf(ftest,'%s\n',xmlfile(i+2).name(1:end-4));end
end
fclose(ftrainval);
fclose(ftrain);
fclose(fval);
fclose(ftest);
(5)到此所有数据就准备完毕,第(3)(4)的程序下载链接:
同时建议,上面的操作最好在Linux下完成,如果在windows下完成,还需要涉及一下格式的转化。因为DOS的编辑器和Linux对文末eneter的处理规则不一样,这里送上2个锦囊,帮助众童鞋解除困扰,
去除txt中所有的^M指令: :%s/^M//g#
vim 中替换指令: :%s/sour/dst/g (将sour替换为dst)
如此这般,create_list.sh就可以生成正确的 trainval.txt,test.txt,test_name_size.txt
然后执行,create_data.sh就可以生成正确的test_lmdb和trainval_lmdb,并在caffe-root/examples下面生成相应的symbolic link,
然后,修改,caffe-root/examples/ssd/pascal.py
57行:训练数据路径
59行:测试数据路径
197-203行:save_dir,snapshot_dir,job_dir,output_result_dir路径
216-220行:name_size_file,label_map_file路径
223行:类别数目(1+类别数)
315行:测试图片数目
上面的这些修改完毕,就可以执行 python examples/ssd/ssd_pascal.py进行训练,小伙伴们又可以静静的等待了。
(6)如果你没有自己的图片,或者不想花时间处理了,可以直接使用VOC里面的图片,下面的程序可以实现解析VOC XML,从中提取出你需要的类别的图片。当然需要xml_io_tools这个matlab版本的解析库,下载地址,
trainval=importdata('/home/data/VOCdevkit/VOC2007/ImageSets/Main/test.txt','0',6000);
tv=fopen('te.txt','w');
tval=fopen('test.txt','w');
for i=1:size(trainval,1)ixmlpath=strcat('/home/data/VOCdevkit/VOC2007/Annotationsori/',strcat(trainval{i},'.xml'));info=xml_read(xmlpath);for j=1:size(info.object,1)%下面加上需要的类别,例如,车,人等if (strcmp(info.object(j).name,'car')==1||...strcmp(info.object(j).name,'person')==1)fprintf(tv, '%s %s %g %g %g %g\n', info.filename,info.object(j).name,...info.object(j).bndbox.xmin,info.object(j).bndbox.ymin,info.object(j).bndbox.xmax,info.object(j).bndbox.ymax);fprintf(tval,'%s\n',info.filename(1:length(info.filename)-4));endendend
最后生成te.txt和text.txt,分别为下面左右图,
生成这样的文件,就可以继续按照上面的步骤进行处理了。
windows下SSD的一安装指南:
安装SSD-caffe步骤:
准备资源:
官方SSD-CAFFE:
官方WINDOWS-CAFFE:
boost_1_59_0(regex):
安装步骤:
1,首先mv一个WINDOWS-CAFFE的F:\caffe-windows\windows下面的CommonSettings.props.example为CommonSettings.props,并对里面进行修改
CPU版本配置:
GPU版本配置:
2,用SSD-CAFFE目录下的src,include,tools,examples替换掉WINDOWS-CAFFE的相应目录,然后进行编译,将会生成15个组件。
这个环节是最重要的一个环节,期间可能出现的问题总结如下:
问题1:
解决方法,双击该错误,分别定位到bbox_util.cpp出错的地方,将snprintf改为_snprintf。
问题2:
解决方法,右键caffe,libcaffe,test_all,配置属性,C/C++,常规,将警告视为错误改为否。
问题3:
解决方法,将下载好的boost里面的libboost_regex-vc120-mt-1_59.lib,libboost_regex-vc120-mt-gd-1_59.lib复制到F:\NugetPackages\boost_chrono-vc120.1.59.0.0\lib\native\address-model-64\lib目录下VS即可检测到。
问题4:
在用SSD-CAFFE替换WINDOWS-CAFFE的过程中,遇到#if defined(_MSC_VER)类似这样的代码要全部保留下来(可以用compare软件修改)。例如,src/caffe/util/db_lmdb.cpp
问题5:
解决方法,双击定位到错误位置,将kBNLL_THRESHOLD改为50即可。
问题6:
test_lrn_layer的错误,本人使用的是cudnn4版本,估计是一些这问题吧
解决方法,修改为原版caffe中的样子,
3,SSD测试
CPU版本用时(至强E5-2687)
GPU版本用时(大将gtx-750Ti)
稳定后为120ms的样子
提供一个热心网友分享的他自己翻译的SSD资料,
由于本人配置这个windows的ssd也是折腾了4天,因此友情提示,坑多,初学者慎入。由于本人疏忽,未能整理的各种错误,Bug,欢迎下面留言。
夫学者,传道授业解惑也,博客亦然。如果不能真正帮助广大热心学习的小伙伴解决问题,那博客写的再好也没意义,因此,本人将会近期整理完成,上传一个全部配置好的可以直接编译完就运行ssd的windows-caffe,敬请期待后续更新……
更多推荐
SSD(Single Shot MultiBox Detector)不得不说的那些事
发布评论