SSD

Mobilenet V2在移动设备上比Mobilenet V1更快,但在桌面GPU上稍慢。

Single Shot MultiBox Detector

SSD是一种速度很快,且识别率较高的物体检测模型。其借鉴了YOLO的图像网格划分,并简化了基于Region Proposal方法的faster r-cnn,同时整合faster r-cnn中anchor boxes实现multi-scale的思想。

缺点:

1.需要人工设置 default box 的 min_size,max_size 和 aspect_ratio 值。网络中 default box 的基础大小和形状不能直接通过学习获得,而是需要手工设置。而网络中每一层feature使用的default box大小和形状恰好都不一样,导致调试过程非常依赖经验。

2.虽然采用了 pyramdial feature hierarchy 的思路,但是对小目标的recall依然一般,并没有达到碾压 Faster RCNN 的级别。作者认为,这是由于SSD使用 conv4_3 低级 feature 去检测小目标,而低级特征卷积层数少,存在特征提取不充分的问题。

网路结构

SSD_第1张图片

SSD_第2张图片

SSD网络包括两个部分,前面用VGG16前五层网络,后面用astrous算法将fc6和fc7分别转化成两个conv,然后再加入3个卷积conv,和一个 average pool层,总共11层。其中的6层产生default Box,分别是conv4_3、fc7、conv6_2、conv7_2、conv8_2、pool6(box数量见图中,一般设置4~6),这样通过不同尺度的特征层检测图像中不同大小的目标,从而实现多尺度目标检测 ,一般来说越底层的 layer,保留的图像细节越多,即低层检测小目标,高层检测大目标。实验表明增加对基础网络conv4_3的特征图的检测可以使mAP提升4%。

prior box

每一个feature map cell生成的是k个default box,prior box指实际中选择的default box。其与faster R-CNN中的Anchor类似,把每个Feature map分成m×n个cell,每个cell有默认出k个default box,最后的predict box与default box有4个offset,并为每个predict box计算c个类的值。最后产生了(c+4)*k*m*n个值。

这些输出个数的含义是:采用3×3的卷积核对该层的feature map卷积时卷积核的个数,包含两部分(实际code是分别用不同数量的3*3卷积核对该层feature map进行卷积):数量c*k*m*n是confidence输出,表示每个default box的confidence,也就是类别的概率;数量4*k*m*n是localization输出,表示每个default box回归后的坐标)。

这里与faster R-CNN Anchor最大的不同就是,faster R-CNN是通过改变Anchor的大小来实现scalable的,SSD是改变Feature map大小来实现的。实验表明box的shape数量越多效果越好。

I.生成规则

  • 以feature map上每个点的中点为中心(offset=0.5),生成一些列同心的prior box(然后中心点的坐标会乘以step,相当于从feature map位置映射回原图位置)
  •  
  • 正方形prior box最小边长为,最大边长为:
  •  
  • 在prototxt设置一个aspect ratio,会生成2个长方形,长宽为:
  •  
  •     和   。

              SSD_第3张图片

Feature map对应的prior box的min_size和max_size由以下公式决定,m为feature map数量,即m=6。

prior box宽高根据比例值计算,原论文中比例值为{1,2,3,1/2,1/3},默认框的长宽计算公式:

宽:w      高:h      ar:宽高比      Sk:default box尺寸

第一层feature map中的prior box对应的min_size=S1,max_size=S2;第二层min_size=S2,max_size=S3;其他类推。在原文中,Smin=0.2,Smax=0.9,但是在SSD 300的caffe代码中prior box设置并不能和paper中上述公式对应。实验得出使用瘦高与宽扁默认框相对于只使用正方形默认框有2.9%mAP提升。

II.如下图,以conv4_3 feature map为例,可见网络pipeline分为了3条线路:

SSD_第4张图片

1.经过batch norm+卷积后,生成[1, num_class*num_priorbox, layer_height, layer_width]大小的feature用于softmax分类目标和非目标;

2.经过batch norm+卷积后,生成[1, 4*num_priorbox, layer_height, layer_width]大小的feature用于bounding box regression(即每个点一组[dxmin,dymin,dxmax,dymax])

3.生成了[1, 2, 4*num_priorbox]大小的prior box blob,其中2个channel分别存储prior box的4个点坐标和对应的4个variance

后续通过softmax分类+bounding box regression即可从priox box中预测到目标,每个位置的prior box一般是4~6个,少于Faster RCNN默认的9个anchor,同时prior box设置在不同尺度的feature map上,且大小不同。

variance,这实际上是一种bounding regression中的权重。图中线路2中,网络输出[dxmin,dymin,dxmax,dymax],即对应下面代码中bbox;然后利用如下方法进行针对prior box的位置回归:

decode_bbox->set_xmin(  
    prior_bbox.xmin() + prior_variance[0] * bbox.xmin() * prior_width);  
decode_bbox->set_ymin(  
    prior_bbox.ymin() + prior_variance[1] * bbox.ymin() * prior_height);  
decode_bbox->set_xmax(  
    prior_bbox.xmax() + prior_variance[2] * bbox.xmax() * prior_width);  
decode_bbox->set_ymax(  
    prior_bbox.ymax() + prior_variance[3] * bbox.ymax() * prior_height);  

上述代码可以在SSD box_utils.cpp的void DecodeBBox()函数见到。

III.以conv4_3和fc7为例分析SSD是如何将不同size的feature map组合在一起进行prediction。下图为conv4_3和fc7合并在一起的过程中caffe blob shape变化。

SSD_第5张图片

conv4_3 feature map设置了4个prior box。由于SSD 300共有21个分类,所以conv4_3_norm_mbox_conf的channel值为num_priorbox * num_class = 4 * 21 = 84,而每个prior box都要回归出4个位置变换量,所以conv4_3_norm_mbox_loc的caffe blob channel值为4 * 4 = 16;fc7每个点有6个prior box,其他feature map同理。经过一系列下图所示的caffe blob shape变化后,最后拼接成mbox_conf和mbox_loc。而mbox_conf后接reshape,再进行softmax(为何在softmax前进行reshape,Faster RCNN有提及)。最后这些值输出detection_out_layer,获得检测结果。

Permute   Flatten   Concat

多个feature maps协同工作用到Permute,Flatten和Concat。

1.Permute是SSD中自带的层,作用是交换caffe blob中的数据维度。在正常情况下caffe blob的顺序为:

bottom blob = [batch_num, channel, height, width]

经过Permute后的caffe blob为:

top blob = [batch_num, height, width, channel]

2.Flattlen和Concat层都是caffe自带层,请参照caffe official documentation理解或者见我SSD码源解析部分的博客。

SSD_第6张图片

Hard Negative Mining

目标检测中,正样本是目标,负样本是背景,训练过程相当于一个二分类的过程(有目标和没目标)。绝大多数的default box都是负例样本,这会造成 negative boxes、positive boxes 之间的不均衡,很难收敛。

本文采用Hard Negative Mining策略(使正负样本比例为1:3)来平衡正负样本比例。具体的做法是先将每一个物体位置上对应 predictions(prior boxes)loss 进行排序。 对于候选正样本集:选择最高的几个prior box与正样本集匹配(box索引同时存在于这两个集合里则匹配成功),匹配不成功则删除这个正样本(因为这个正样本不在难例里已经很接近ground truth box了,不需要再训练了);对于候选负样本集:选择最高的几个prior box与候选负样本集匹配,匹配成功则作为负样本。即在开始时随机创建一堆的bounding box候选框,没有与正样本有任何的重叠新的bounding box作为负样本。

举个例子,假设在这8732个prior box里,经过FindMatches后得到候选正样本P个,候选负样本那就有8732-P个。将prior box的prediction loss按照从大到小顺序排列后选择最高的M个prior box。如果这P个候选正样本里有a个box不在这M个prior box里,将这M个box从候选正样本集中踢出去。如果这8732-P个候选负样本集中包含的8732-P有M-a个在这M个prior box,则将这M-a个候选负样本作为负样本。SSD算法中通过这种方式来保证 positives、negatives 的比例。

训练过程

一张图片送进网络获得6个feature map,将default box与ground truth box做匹配,寻找与每一个ground truth box有最大的jaccard overlap的prior box,这样就能保证每一个ground truth box与唯一的一个prior box对应起来。之后又将剩余还没有配对的default box与任意一个ground truth box尝试配对,只要两者之间的jaccard overlap大于阈值,就认为match(SSD 300 阈值为0.5,另外所谓的jaccard overlap就是IoU)。配对到GT的prior box就是positive,没有配对到GT的prior box就是negative。但在一般情况下negative prior boxes数量>>positive prior boxes数量,直接训练会导致网络过于重视负样本,从而导致loss不稳定。所以需要采取Hard Negative Mining。依据confidience score排序prior box,挑选其中confidience高的box进行训练,控制positive:negative=1:3。训练的目的是保证default box的分类confidence的同时将prior box尽可能回归到ground truth box。 一个输出分类用的confidence,每个default box 生成21个类别confidence;一个输出回归用的 localization,每个 default box生成4个坐标值(x, y, w, h)。

损失函数由分类和回归两部分组成,回归部分的loss是希望预测的box和prior box的差距尽可能跟ground truth和prior box的差距接近,这样预测的box就能尽量和ground truth一样。得到的8732个目标框经过Jaccard Overlap筛选剩下几个了;其中不满足的框标记为负数,其余留下的标为正数框。紧随其后:

SSD_第7张图片

SSD损失函数分为两部分:

对应默认框的位置loss,localization loss(loc) 

类别置信度loss,confidence loss(conf)

定义 , 表示第i个default box与第j个ground truth box相匹配,类别为p,若不匹配的话,值为0。

目标函数定义为:

,N为匹配default box,If N = 0,loss = 0;

为预测框和ground truth box 的Smooth L1 loss,值通过cross validation设置为1。

1.localization loss定义如下:

SSD_第8张图片

为预测框,为ground truth, 为default box,我们对偏移位置进行回归。

2. 为多类别softmax loss,confidence loss定义如下, 通过交叉验证将设为1 :

 

SSD的网络构图:

SSD_第9张图片

Data augmentation

在原文中的Fig.6提到了在数据增广之后SSD网络对小物体识别能力有很大提升,Sec. 2.2提出的方法是随机采样。数据增广的步骤,即对每一张训练图像,随机的进行如下几种选择:

1.使用原始的图像;

2.采样一个 patch,与目标之间最小的 jaccard overlap 为:0.1,0.3,0.5,0.7 与 0.9;

3.随机的采样一个 patch

采样的patch与原始图像大小比例为[0.1,1],aspect ratio在0.5~2之间。当 ground truth box 的中心在采样的patch中时,保留重叠部分。采样之后,每一个采样的patch被resize到固定大小,并以0.5的概率随机水平翻转(horizontally flipped)。

详细步骤:

 

假设原图输入是一张640*480的图片,由于最后会有resize参数导致输出的图片都会resize到300x300,但是主要看的是增强的效果,SSD中的数据增强的顺序是:

DistortImage:修改图片的brightness,contrast,saturation,hue,reordering channels,并没改变标签bbox;

ExpandImage:放缩图片,达到zoom out的效果,将DistortImage的图片用像素0进行扩展,标签bbox此时肯定会改变,就重新以黑边的左上角为原点计算[0,1]的bbox的左上角和右下角两个点坐标。先做一个比原图大的画布,然后随机找一个放原图的位置将原图镶嵌进去,像天安门上挂了一个画像。这在SSD原文中提到的zoom out缩小16倍(4×4,3.6节)来获得小对象的方法。 而放大图像必然会导致需要填充一些数据,原文(SSD,3.6节)提到是使用mean值填充,在data_transforme.cpp中,使用了如下的代码:

 

if (has_mean_values) {
   transformed_data[top_index] =
       (datum_element - mean_values_[c]) * scale;//c是channel,在prototxt有三个值,分别对应三个波段104,117,123
 } 

BatchSampler:产生随机样本patch的方法,必须要有GT的存在才会生效,比如做的是人的检测但图中没人,那么就不会生成sampled_bboxes。sampled_bboxes的值是随机在[0, 1]上生成的bbox,并且和某个gt_bboxes的IOU在[min, max]之间。由于proto中配的max_sample都是为1,所以每个batch_sampler可能会有1个sampled_bbox,随机取一个sampled bbox并且裁剪图片和标签。标签裁剪也很好理解首先要通过ProjectBBox将原坐标系标签投影到裁剪后图片的新坐标系的坐标,然后再ClipBBox到[0,1]之间。在该cpp的第178行调用了函数GenerateBatchSamples()函数原型定义为:

 

// Generate samples from AnnotatedDatum using the BatchSampler.
// All sampled bboxes which satisfy the constraints defined in BatchSampler
// is stored in sampled_bboxes.
void GenerateBatchSamples(const AnnotatedDatum& anno_datum,
                          const vector& batch_samplers,
                          vector* sampled_bboxes);

然后调用到了函数GenerateSamples/SampleBBox 

其方法是随机生产width和height,但是长宽的生成是相关的;然后在随机生成左上角坐标(之前见到的是先随机生成左上角坐标,然后用固定size去裁剪)

void SampleBBox(const Sampler& sampler, NormalizedBBox* sampled_bbox) {
  // Get random scale.
  CHECK_GE(sampler.max_scale(), sampler.min_scale());//scale是prototxt中参数
  CHECK_GT(sampler.min_scale(), 0.);//检查这些参数符合逻辑
  CHECK_LE(sampler.max_scale(), 1.);
  float scale;
  caffe_rng_uniform(1, sampler.min_scale(), sampler.max_scale(), &scale);
//自定义的生成随机数的方法,在区间内生成float数,类似于random.random()

  // Get random aspect ratio.
  CHECK_GE(sampler.max_aspect_ratio(), sampler.min_aspect_ratio());
  CHECK_GT(sampler.min_aspect_ratio(), 0.);
  CHECK_LT(sampler.max_aspect_ratio(), FLT_MAX);//同样检查超参
  float aspect_ratio;
  caffe_rng_uniform(1, sampler.min_aspect_ratio(), sampler.max_aspect_ratio(),
      &aspect_ratio);//随机数

  aspect_ratio = std::max(aspect_ratio, std::pow(scale, 2.));//做了些调整
  aspect_ratio = std::min(aspect_ratio, 1 / std::pow(scale, 2.));

  // Figure out bbox dimension.
  float bbox_width = scale * sqrt(aspect_ratio);//长宽
  float bbox_height = scale / sqrt(aspect_ratio);

  // Figure out top left coordinates.
  float w_off, h_off;
  caffe_rng_uniform(1, 0.f, 1 - bbox_width, &w_off);//左上坐标
  caffe_rng_uniform(1, 0.f, 1 - bbox_height, &h_off);

  sampled_bbox->set_xmin(w_off);
  sampled_bbox->set_ymin(h_off);
  sampled_bbox->set_xmax(w_off + bbox_width);
  sampled_bbox->set_ymax(h_off + bbox_height);
}

将随机产生的框(超参数给出迭代次数为max_trials: 50,会产生一些有效的符合要求的框boxes,不一定是50)存入一个容器中,然后再这这个容器中随机选择一个来裁剪图片和标注数据。

// Generate sampled bboxes from expand_datum.
      vector sampled_bboxes;
      GenerateBatchSamples(*expand_datum, batch_samplers_, &sampled_bboxes);
      if (sampled_bboxes.size() > 0) {
        // Randomly pick a sampled bbox and crop the expand_datum.
        int rand_idx = caffe_rng_rand() % sampled_bboxes.size();
        sampled_datum = new AnnotatedDatum();
        this->data_transformer_->CropImage(*expand_datum,
                                           sampled_bboxes[rand_idx],
                                           sampled_datum);

Resize:放缩到300x300,最后将图片放缩到300x300,标签框也是线性放缩坐标而已。

Crop:原本data_transformer还会crop的,这个参数是配在prototxt中,默认是原图 所以就和没crop一样。如果要crop的话标签也是会和之前BatchSampler那样处理。

SSD_第10张图片

 

用到了这几个数据的处理关系如下: 

 

SSD_第11张图片

多种transform方法组合在一起的时候,必然会带来排列组合的问题,源码中使用了一些组合方式,而不是生成一堆图像。(有人在blog中提到不同类别样本的数目问题,不均衡肯定是不好的,带来一些训练偏好)

上面并没有提及的是图片的长和宽,除了ExpandImage中会得到固定的size的裁剪图片,其他的地方没有特别强调图片长宽不同,特别是sampled随机裁剪图片时,长和宽是用同样的随机数得到的,如果要被裁剪的图片长宽不同,那最终就不能保证aspect ratio(宽/长)了。 

annotateDataLayer层重新实现了DataLayerSetUp()函数,该函数是BaseDataLayer的一个虚函数,里面的。而BasePrefetchingDataLayer类的LayerSerup()调用了BaseDataLayer::LayerSetUp(bottom, top);函数将数据存入了prefetch_中,而所获取的数据被重新的reshape()了。

 // Reshape top[0] and prefetch_data according to the batch_size.
  for (int i = 0; i < this->PREFETCH_COUNT; ++i) {//AnnotatedDataLayer::DataLayerSetUp() ---line60
    this->prefetch_[i].data_.Reshape(top_shape);//其中,data_经过多层继承,最终是Blob的一个对象
  }

在这个层(annotateDataLayer)被调用的时候,它已经将输入数据resize了。

http://210.28.132.67/weixs/project/CNNTricks/CNNTricks.html,其中section 1讲了data augmentation。

其实Matching strategy,Hard negative mining,Data augmentation,都是为了加快网络收敛而设计的。尤其是Data augmentation,翻来覆去的randomly crop,保证每一个prior box都获得充分训练而已。

 

你可能感兴趣的:(SSD)