本文转载文章,目的是方便以后查阅,原文地址http://blog.csdn.net/qq_14845119/article/details/53331581,感谢原创的分享!
该方法出自2016年的一篇ECCV的oral paper,SSD: Single Shot MultiBoxDetector,算是一个革命性的方法了,非常值得学习和研究。
论文解析:
![](https://img-blog.csdn.net/20161127222834417)
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。
位置损失的详细公式如下:
![](https://img-blog.csdn.net/20161127223006873)
置信度损失的公式如下:
![](https://img-blog.csdn.net/20161127223031621)
该方法包括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下的运行效果如下,
![](https://img-blog.csdn.net/20161125105748507)
训练模型(作者VOC2007&VOC2012数据集):
1,VGGNet下载,
- wget http://cs.unc.edu/~wliu/projects/ParseNet/VGG_ILSVRC_16_layers_fc_reduced.caffemodel
2,VOC2007,VOC2012数据集下载,并解压
- # Download the data.
- cd $HOME/data
- wget http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar
- wget http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtrainval_06-Nov-2007.tar
- wget http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtest_06-Nov-2007.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),模型效果和作者提供的模型的效果一样。
![](https://img-blog.csdn.net/20161203135158081)
训练模型(自己的数据集):
(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)%如果文件名相等,只需增加object
- 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);
- else %如果文件名不等,则需要新建xml
- copyfile(filepath, 'JPEGImages');
- %先保存上一次的xml
- if exist('Createnode','var')
- tempname=lastname;
- tempname=strrep(tempname,'.jpg','.xml');
- xmlwrite(tempname,Createnode);
- end
-
-
- Createnode=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==1
- line=2;
- continue;
- end
- expression = ' ';
- replace=char(9);
- newStr=regexprep(tline,expression,replace);
- fprintf(fnew,'%s\n',newStr);
- end
- fprintf('已处理%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:numOfxml
- if 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));
- else
- fprintf(fval,'%s\n',xmlfile(i+2).name(1:end-4));
- end
- else
- fprintf(ftest,'%s\n',xmlfile(i+2).name(1:end-4));
- end
- end
- fclose(ftrainval);
- fclose(ftrain);
- fclose(fval);
- fclose(ftest);
(5)到此所有数据就准备完毕,第(3)(4)的程序下载链接:http://download.csdn.net/detail/qq_14845119/9700102
同时建议,上面的操作最好在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版本的解析库,下载地址,http://download.csdn.net/detail/qq_14845119/9711846
- 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)
- i
- xmlpath=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));
- end
- end
-
- end
最后生成te.txt和text.txt,分别为下面左右图,
![](https://img-blog.csdn.net/20161215084555307)
生成这样的文件,就可以继续按照上面的步骤进行处理了。
windows下SSD的一安装指南:
安装SSD-caffe步骤:
准备资源:
官方SSD-CAFFE:https://github.com/weiliu89/caffe/tree/ssd
官方WINDOWS-CAFFE:https://github.com/BVLC/caffe/tree/windows
boost_1_59_0(regex):http://download.csdn.net/detail/qq_14845119/9693187
安装步骤:
1,首先mv一个WINDOWS-CAFFE的F:\caffe-windows\windows下面的CommonSettings.props.example为CommonSettings.props,并对里面进行修改
CPU版本配置:
![](https://img-blog.csdn.net/20161125103154191)
GPU版本配置:
2,用SSD-CAFFE目录下的src,include,tools,examples替换掉WINDOWS-CAFFE的相应目录,然后进行编译,将会生成15个组件。
![](https://img-blog.csdn.net/20161125103215676)
这个环节是最重要的一个环节,期间可能出现的问题总结如下:
问题1:
解决方法,双击该错误,分别定位到bbox_util.cpp出错的地方,将snprintf改为_snprintf。
![](https://img-blog.csdn.net/20161125103256943)
问题2:
解决方法,右键caffe,libcaffe,test_all,配置属性,C/C++,常规,将警告视为错误改为否。
![](https://img-blog.csdn.net/20161125103318584)
问题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:
![](https://img-blog.csdn.net/20161128090420776)
解决方法,双击定位到错误位置,将kBNLL_THRESHOLD改为50即可。
![](https://img-blog.csdn.net/20161128090534360)
问题6:
test_lrn_layer的错误,本人使用的是cudnn4版本,估计是一些这问题吧
解决方法,修改为原版caffe中的样子,
![](https://img-blog.csdn.net/20161201124721554)
3,SSD测试
![](https://img-blog.csdn.net/20161201124758573)
CPU版本用时(至强E5-2687)
GPU版本用时(大将gtx-750Ti)
稳定后为120ms的样子
提供一个热心网友分享的他自己翻译的SSD资料,http://download.csdn.net/detail/qq_14845119/9698597
由于本人配置这个windows的ssd也是折腾了4天,因此友情提示,坑多,初学者慎入。由于本人疏忽,未能整理的各种错误,Bug,欢迎下面留言。
夫学者,传道授业解惑也,博客亦然。如果不能真正帮助广大热心学习的小伙伴解决问题,那博客写的再好也没意义,因此,本人将会近期整理完成,上传一个全部配置好的可以直接编译完就运行ssd的windows-caffe,敬请期待后续更新……