SSD(Single Shot MultiBox Detector)笔记

本文转载文章,目的是方便以后查阅,原文地址http://blog.csdn.net/qq_14845119/article/details/53331581,感谢原创的分享!

该方法出自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下载,

[plain]  view plain  copy
  1. wget http://cs.unc.edu/~wliu/projects/ParseNet/VGG_ILSVRC_16_layers_fc_reduced.caffemodel  

2,VOC2007,VOC2012数据集下载,并解压

[plain]  view plain  copy
  1. # Download the data.  
  2. cd $HOME/data  
  3. wget http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar  
  4. wget http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtrainval_06-Nov-2007.tar  
  5. wget http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtest_06-Nov-2007.tar  
  6. # Extract the data.  
  7. tar -xvf VOCtrainval_11-May-2012.tar  
  8. tar -xvf VOCtrainval_06-Nov-2007.tar  
  9. tar -xvf VOCtest_06-Nov-2007.tar  

3,生成LMDB数据

[plain]  view plain  copy
  1. cd $CAFFE_ROOT  
  2. # Create the trainval.txt, test.txt, and test_name_size.txt in data/VOC0712/  
  3. ./data/VOC0712/create_list.sh  
  4. # You can modify the parameters in create_data.sh if needed.  
  5. # It will create lmdb files for trainval and test with encoded original image:  
  6. #   - $HOME/data/VOCdevkit/VOC0712/lmdb/VOC0712_trainval_lmdb  
  7. #   - $HOME/data/VOCdevkit/VOC0712/lmdb/VOC0712_test_lmdb  
  8. # and make soft links at examples/VOC0712/  
  9. ./data/VOC0712/create_data.sh  

这里可能会出现一个错误,AttributeError: 'module' object has noattribute 'LabelMap' error

解决方法,export PYTHON PATH=$CAFFE_ROOT/python:$PYTHONPATH

4,训练,

[plain]  view plain  copy
  1. # It will create model definition files and save snapshot models in:  
  2. #   - $CAFFE_ROOT/models/VGGNet/VOC0712/SSD_300x300/  
  3. # and job file, log file, and the python script in:  
  4. #   - $CAFFE_ROOT/jobs/VGGNet/VOC0712/SSD_300x300/  
  5. # and save temporary evaluation results in:  
  6. #   - $HOME/data/VOCdevkit/results/VOC2007/SSD_300x300/  
  7. # It should reach 72.* mAP at 60k iterations.  
  8. python examples/ssd/ssd_pascal.py  

可能的错误,Check failed: error == cudaSuccess (10 vs. 0)  invalid device ordinal,

解决方法,

[plain]  view plain  copy
  1. vim examples/ssd/ssd_pascal.py  
  2. gg 285  

将,gpus = "0,1,2,3",改为,gpus = "0",因为本人只有一块显卡

到此,就可以静静的等待训练结果了。

建议训练配置,内存8G+,显卡8G+,(本人训练内存占用7.8G,显卡占用6.8G),训练过程大概24个小时(单卡titanx),模型效果和作者提供的模型的效果一样。



训练模型(自己的数据集):

(1)在/home/data/VOCdevkit/目录下

[plain]  view plain  copy
  1. mkdir VOCmy  
  2. cd VOCmy  
  3. mkdir Annotations Imagesets JPEGImages  
  4. cd Imagesets/  
  5. 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转化程序如下:

[plain]  view plain  copy
  1. %%  
  2. %该代码可以做voc2007数据集中的xml文件,  
  3. %txt文件每行格式为:000001.jpg dog 48 240 195 371  
  4. %即每行由图片名、目标类型、包围框坐标组成,空格隔开  
  5. %如果一张图片有多个目标,则格式如下:(比如两个目标)  
  6. % 000001.jpg dog 48 240 195 371  
  7. % 000001.jpg person 8 12 352 498  
  8. % 000002.jpg train 139 200 207 301  
  9. % 000003.jpg sofa 123 155 215 195  
  10. % 000003.jpg chair 239 156 307 205  
  11. %包围框坐标为左上角和右下角  
  12. %%  
  13. clc;  
  14. clear;  
  15. %注意修改下面四个变量  
  16. imgpath='img\';%图像存放文件夹  
  17. txtpath='img\output.txt';%txt文件  
  18. xmlpath_new='Annotations/';%修改后的xml保存文件夹  
  19. foldername='VOC2007';%xml的folder字段名  
  20.   
  21.   
  22. fidin=fopen(txtpath,'r');  
  23. lastname='begin';  
  24.   
  25. while ~feof(fidin)  
  26.      tline=fgetl(fidin);  
  27.      str = regexp(tline, ' ','split');  
  28.      filepath=[imgpath,str{1}];  
  29.      img=imread(filepath);  
  30.      [h,w,d]=size(img);  
  31.       imshow(img);  
  32.       rectangle('Position',[str2double(str{3}),str2double(str{4}),str2double(str{5})-str2double(str{3}),str2double(str{6})-str2double(str{4})],'LineWidth',4,'EdgeColor','r');  
  33.       pause(0.1);  
  34.         
  35.         if strcmp(str{1},lastname)%如果文件名相等,只需增加object  
  36.            object_node=Createnode.createElement('object');  
  37.            Root.appendChild(object_node);  
  38.            node=Createnode.createElement('name');  
  39.            node.appendChild(Createnode.createTextNode(sprintf('%s',str{2})));  
  40.            object_node.appendChild(node);  
  41.             
  42.            node=Createnode.createElement('pose');  
  43.            node.appendChild(Createnode.createTextNode(sprintf('%s','Unspecified')));  
  44.            object_node.appendChild(node);  
  45.             
  46.            node=Createnode.createElement('truncated');  
  47.            node.appendChild(Createnode.createTextNode(sprintf('%s','0')));  
  48.            object_node.appendChild(node);  
  49.   
  50.            node=Createnode.createElement('difficult');  
  51.            node.appendChild(Createnode.createTextNode(sprintf('%s','0')));  
  52.            object_node.appendChild(node);  
  53.             
  54.            bndbox_node=Createnode.createElement('bndbox');  
  55.            object_node.appendChild(bndbox_node);  
  56.   
  57.            node=Createnode.createElement('xmin');  
  58.            node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(str{3}))));  
  59.            bndbox_node.appendChild(node);  
  60.   
  61.            node=Createnode.createElement('ymin');  
  62.            node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(str{4}))));  
  63.            bndbox_node.appendChild(node);  
  64.   
  65.            node=Createnode.createElement('xmax');  
  66.            node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(str{5}))));  
  67.            bndbox_node.appendChild(node);  
  68.   
  69.            node=Createnode.createElement('ymax');  
  70.            node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(str{6}))));  
  71.            bndbox_node.appendChild(node);  
  72.         else %如果文件名不等,则需要新建xml  
  73.            copyfile(filepath, 'JPEGImages');  
  74.             %先保存上一次的xml  
  75.            if exist('Createnode','var')  
  76.               tempname=lastname;  
  77.               tempname=strrep(tempname,'.jpg','.xml');  
  78.               xmlwrite(tempname,Createnode);     
  79.            end  
  80.               
  81.               
  82.             Createnode=com.mathworks.xml.XMLUtils.createDocument('annotation');  
  83.             Root=Createnode.getDocumentElement;%根节点  
  84.             node=Createnode.createElement('folder');  
  85.             node.appendChild(Createnode.createTextNode(sprintf('%s',foldername)));  
  86.             Root.appendChild(node);  
  87.             node=Createnode.createElement('filename');  
  88.             node.appendChild(Createnode.createTextNode(sprintf('%s',str{1})));  
  89.             Root.appendChild(node);  
  90.             source_node=Createnode.createElement('source');  
  91.             Root.appendChild(source_node);  
  92.             node=Createnode.createElement('database');  
  93.             node.appendChild(Createnode.createTextNode(sprintf('The VOC2007 Database')));  
  94.             source_node.appendChild(node);  
  95.             node=Createnode.createElement('annotation');  
  96.             node.appendChild(Createnode.createTextNode(sprintf('PASCAL VOC2007')));  
  97.             source_node.appendChild(node);  
  98.   
  99.            node=Createnode.createElement('image');  
  100.            node.appendChild(Createnode.createTextNode(sprintf('flickr')));  
  101.            source_node.appendChild(node);  
  102.   
  103.            node=Createnode.createElement('flickrid');  
  104.            node.appendChild(Createnode.createTextNode(sprintf('NULL')));  
  105.            source_node.appendChild(node);  
  106.            owner_node=Createnode.createElement('owner');  
  107.            Root.appendChild(owner_node);  
  108.            node=Createnode.createElement('flickrid');  
  109.            node.appendChild(Createnode.createTextNode(sprintf('NULL')));  
  110.            owner_node.appendChild(node);  
  111.   
  112.            node=Createnode.createElement('name');  
  113.            node.appendChild(Createnode.createTextNode(sprintf('watersink')));  
  114.            owner_node.appendChild(node);  
  115.            size_node=Createnode.createElement('size');  
  116.            Root.appendChild(size_node);  
  117.   
  118.           node=Createnode.createElement('width');  
  119.           node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(w))));  
  120.           size_node.appendChild(node);  
  121.   
  122.           node=Createnode.createElement('height');  
  123.           node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(h))));  
  124.           size_node.appendChild(node);  
  125.   
  126.          node=Createnode.createElement('depth');  
  127.          node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(d))));  
  128.          size_node.appendChild(node);  
  129.            
  130.           node=Createnode.createElement('segmented');  
  131.           node.appendChild(Createnode.createTextNode(sprintf('%s','0')));  
  132.           Root.appendChild(node);  
  133.           object_node=Createnode.createElement('object');  
  134.           Root.appendChild(object_node);  
  135.           node=Createnode.createElement('name');  
  136.           node.appendChild(Createnode.createTextNode(sprintf('%s',str{2})));  
  137.           object_node.appendChild(node);  
  138.             
  139.           node=Createnode.createElement('pose');  
  140.           node.appendChild(Createnode.createTextNode(sprintf('%s','Unspecified')));  
  141.           object_node.appendChild(node);  
  142.             
  143.           node=Createnode.createElement('truncated');  
  144.           node.appendChild(Createnode.createTextNode(sprintf('%s','0')));  
  145.           object_node.appendChild(node);  
  146.   
  147.           node=Createnode.createElement('difficult');  
  148.           node.appendChild(Createnode.createTextNode(sprintf('%s','0')));  
  149.           object_node.appendChild(node);  
  150.             
  151.           bndbox_node=Createnode.createElement('bndbox');  
  152.           object_node.appendChild(bndbox_node);  
  153.   
  154.          node=Createnode.createElement('xmin');  
  155.          node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(str{3}))));  
  156.          bndbox_node.appendChild(node);  
  157.   
  158.          node=Createnode.createElement('ymin');  
  159.          node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(str{4}))));  
  160.          bndbox_node.appendChild(node);  
  161.   
  162.         node=Createnode.createElement('xmax');  
  163.         node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(str{5}))));  
  164.         bndbox_node.appendChild(node);  
  165.   
  166.         node=Createnode.createElement('ymax');  
  167.         node.appendChild(Createnode.createTextNode(sprintf('%s',num2str(str{6}))));  
  168.         bndbox_node.appendChild(node);  
  169.          
  170.        lastname=str{1};  
  171.         end  
  172.         %处理最后一行  
  173.         if feof(fidin)  
  174.             tempname=lastname;  
  175.             tempname=strrep(tempname,'.jpg','.xml');  
  176.             xmlwrite(tempname,Createnode);  
  177.         end  
  178. end  
  179. fclose(fidin);  
  180.   
  181. file=dir(pwd);  
  182. for i=1:length(file)  
  183.    if length(file(i).name)>=4 && strcmp(file(i).name(end-3:end),'.xml')  
  184.     fold=fopen(file(i).name,'r');  
  185.     fnew=fopen([xmlpath_new file(i).name],'w');  
  186.     line=1;  
  187.     while ~feof(fold)  
  188.         tline=fgetl(fold);  
  189.         if line==1  
  190.            line=2;  
  191.            continue;  
  192.         end  
  193.         expression = '   ';  
  194.         replace=char(9);  
  195.         newStr=regexprep(tline,expression,replace);  
  196.         fprintf(fnew,'%s\n',newStr);  
  197.     end  
  198.     fprintf('已处理%s\n',file(i).name);  
  199.     fclose(fold);  
  200.     fclose(fnew);  
  201.     delete(file(i).name);  
  202.    end  
  203. end  

转化前后比对效果如下,左面为原始图像,中间为转化后的,右面为原始VOC的,

转化完毕,将其都copy到Annotations目录下

(4)生成Imagesets/Main/,下的train.txt,train_val.txt,val.txt,test.txt,matlab程序如下,

[plain]  view plain  copy
  1. %%  
  2. %该代码根据已生成的xml,制作VOC2007数据集中的trainval.txt;train.txt;test.txt和val.txt  
  3. %trainval占总数据集的50%,test占总数据集的50%;train占trainval的50%,val占trainval的50%;  
  4. %上面所占百分比可根据自己的数据集修改,如果数据集比较少,test和val可少一些  
  5. %  
  6. %注意修改下面四个值  
  7. %%  
  8. xmlfilepath='E:\Annotations';  
  9. txtsavepath='E:\ImageSets\Main\';  
  10. trainval_percent=0.5;%trainval占整个数据集的百分比,剩下部分就是test所占百分比  
  11. train_percent=0.5;%train占trainval的百分比,剩下部分就是val所占百分比  
  12.   
  13. xmlfile=dir(xmlfilepath);  
  14. numOfxml=length(xmlfile)-2;%减去.和.. ?总的数据集大小  
  15.   
  16.   
  17. trainval=sort(randperm(numOfxml,floor(numOfxml*trainval_percent)));  
  18. test=sort(setdiff(1:numOfxml,trainval));  
  19.   
  20.   
  21. trainvalsize=length(trainval);%trainval的大小  
  22. train=sort(trainval(randperm(trainvalsize,floor(trainvalsize*train_percent))));  
  23. val=sort(setdiff(trainval,train));  
  24.   
  25.   
  26. ftrainval=fopen([txtsavepath 'trainval.txt'],'w');  
  27. ftest=fopen([txtsavepath 'test.txt'],'w');  
  28. ftrain=fopen([txtsavepath 'train.txt'],'w');  
  29. fval=fopen([txtsavepath 'val.txt'],'w');  
  30.   
  31.   
  32. for i=1:numOfxml  
  33.       if ismember(i,trainval)  
  34.             fprintf(ftrainval,'%s\n',xmlfile(i+2).name(1:end-4));  
  35.             if ismember(i,train)  
  36.                 fprintf(ftrain,'%s\n',xmlfile(i+2).name(1:end-4));  
  37.             else  
  38.                 fprintf(fval,'%s\n',xmlfile(i+2).name(1:end-4));  
  39.             end  
  40.       else  
  41.              fprintf(ftest,'%s\n',xmlfile(i+2).name(1:end-4));  
  42.       end  
  43. end  
  44. fclose(ftrainval);  
  45. fclose(ftrain);  
  46. fclose(fval);  
  47. 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

[plain]  view plain  copy
  1. trainval=importdata('/home/data/VOCdevkit/VOC2007/ImageSets/Main/test.txt','0',6000);  
  2. tv=fopen('te.txt','w');  
  3. tval=fopen('test.txt','w');  
  4. for i=1:size(trainval,1)  
  5.     i  
  6.     xmlpath=strcat('/home/data/VOCdevkit/VOC2007/Annotationsori/',strcat(trainval{i},'.xml'));  
  7.     info=xml_read(xmlpath);  
  8.     for j=1:size(info.object,1)%下面加上需要的类别,例如,车,人等  
  9.         if (strcmp(info.object(j).name,'car')==1||...  
  10.             strcmp(info.object(j).name,'person')==1)  
  11.             fprintf(tv, '%s %s %g %g %g %g\n', info.filename,info.object(j).name,...  
  12.                 info.object(j).bndbox.xmin,info.object(j).bndbox.ymin,info.object(j).bndbox.xmax,info.object(j).bndbox.ymax);  
  13.             fprintf(tval,'%s\n',info.filename(1:length(info.filename)-4));  
  14.         end  
  15.     end  
  16.   
  17. end  

最后生成te.txt和text.txt,分别为下面左右图,

                                 

生成这样的文件,就可以继续按照上面的步骤进行处理了。


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版本配置:


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资料,http://download.csdn.net/detail/qq_14845119/9698597


由于本人配置这个windows的ssd也是折腾了4天,因此友情提示,坑多,初学者慎入。由于本人疏忽,未能整理的各种错误,Bug,欢迎下面留言。

夫学者,传道授业解惑也,博客亦然。如果不能真正帮助广大热心学习的小伙伴解决问题,那博客写的再好也没意义,因此,本人将会近期整理完成,上传一个全部配置好的可以直接编译完就运行ssd的windows-caffe,敬请期待后续更新……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值