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 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),模型效果和作者提供的模型的效果一样。

 

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

(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,分别为下面左右图,

 

                                 

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

 

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,敬请期待后续更新……

  • 16
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 16
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值