本文提出的SSD算法是一种直接预测目标类别和 bounding box 的多目标检测算法。与 faster rcnn相比,该算法没有生成 proposal 的过程,这就极大提升了检测速度。针对不一样大小的目标检测,传统的作法是先将图像转换成不一样大小(图像金字塔),而后分别检测,最后将结果综合起来(NMS)。而SSD算法则利用不一样卷积层的 feature map 进行综合也能达到一样的效果。算法的主网络结构是VGG16,将最后两个全链接层改为卷积层,并随后增长了4个卷积层来构造网络结构。对其中5种不一样的卷积层的输出(feature map)分别用两个不一样的 3×3 的卷积核进行卷积,一个输出分类用的confidence,每一个default box 生成21个类别confidence;一个输出回归用的 localization,每一个 default box 生成4个坐标值(x, y, w, h)。此外,这5个feature map还通过 PriorBox 层生成 prior box(生成的是坐标)。上述5个feature map中每一层的default box的数量是给定的(8732个)。最后将前面三个计算结果分别合并而后传给loss层。算法
文章的核心之一是做者同时采用 lower 和 upper 的 feature map 作检测。如图Fig 1 所示,这里假定有 8×8 和 4×4 两种不一样尺度的 feature map。第一个概念是feature map cell,feature map cell 是指 feature map 中每个小格子,如图中分别有 64 和 16 个 cell。另外有一个概念:default box,是指在feature map的每一个小格(cell)上都有一系列固定大小的box,以下图有4个(下图中的虚线框,仔细看格子的中间有比格子还小的一个box)。假设每一个 feature map cell 有 k 个 default box,那么对于每一个default box都须要预测 c 个类别 score 和 4 个 offset,那么若是一个 feature map的大小是 m×n,也就是有 m*n 个 feature map cell,那么这个 feature map就一共有(c+4)*k * m*n 个输出。这些输出个数的含义是:采用3×3的卷积核对该层的feature map卷积时卷积核的个数,包含两部分:数量 c*k*m*n 是 confidence 输出,表示每一个 default box 的 是某一类别的 confidence;数量4*k*m*n是 localization 输出,表示每一个default box 回归后的坐标)。训练中还有一个东西:prior box,是指实际中选择的 default box(你能够认为 default box 是抽象类,而 prior box 是具体实例)。训练中对于每一个图像样本,须要先将 prior box 与 ground truth box 作匹配,匹配成功说明这个 prior box 所包含的是个目标,但离完整目标的 ground truth box 还有段距离,训练的目的是保证default box 的分类 confidence 的同时将 prior box 尽量回归到 ground truth box。 举个列子:假设一个训练样本中有2个ground truth box,全部的feature map中获取的prior box一共有8732个。那个可能分别有十、20个prior box能分别与这2个ground truth box匹配上。训练的损失包含定位损失和回归损失两部分。网络
做者的实验代表default box的shape数量越多,效果越好。框架
这里用到的 default box 和 Faster RCNN 中的 anchor 很像,在 Faster RCNN 中 anchor 只用在最后一个卷积层,可是在本文中,default box 是应用在多个不一样层的feature map上。ide
那么default box的 scale(大小)和aspect ratio(横纵比)要怎么定呢?假设咱们用 m 个 feature maps 作预测,那么对于每一个featuer map而言其 default box 的 scale 是按如下公式计算的: 函数
$\vee$学习
$S_k=S_{min} + \frac{S_{max} - S_{min}}{m-1}(k-1), k\in{[1, m]}$google
这里smin是0.2,表示最底层的scale是0.2;smax是0.9,表示最高层的scale是0.9。atom
至于aspect ratio,用$a_r$表示为下式:注意这里一共有5种aspect ratio spa
$a_r = \{1, 2, 3, 1/2, 1/3\}$设计
所以每一个default box的宽的计算公式为:
$w_k^a=s_k\sqrt{a_r}$
高的计算公式为:(很容易理解宽和高的乘积是scale的平方)
$h_k^a=s_k/\sqrt{a_r}$
另外当aspect ratio为1时,做者还增长一种scale的default box:
$s_k^{'}=\sqrt{s_{k}s_{k+1}}$
所以,对于每一个feature map cell而言,一共有6种default box。
能够看出这种 default box 在不一样的 feature 层有不一样的 scale,在同一个 feature 层又有不一样的 aspect ratio,所以基本上能够覆盖输入图像中的各类形状和大小的 object!
(训练本身的样本的时候能够在 FindMatch() 以后检查是否能覆盖了全部的 ground truth box,其实是全覆盖了,由于会至少找一个最大匹配)
源代码中的 ssd_pascal.py 设计了上面几个参数值,caffe 源码 prior_box_layer.cpp 中Forward_cpu()实现。
最后会获得(38*38*4 + 19*19*6 + 10*10*6 + 5*5*6 + 3*3*4 + 1*1*4)= 8732 个 prior box。
Fig.2 SSD 框架
将prior box 和 grount truth box 按照 IOU(JaccardOverlap)进行匹配,匹配成功则这个prior box就是 positive example(正样本),若是匹配不上,就是negative example(负样本),显然这样产生的负样本的数量要远远多于正样本。这里默认作了难例挖掘:简单描述起来就是,将全部的匹配不上的 negative prior box 按照前向 loss 进行排序,选择最高的 num_sel 个 prior box 序号集合做为最终的负样本集。这里就能够利用 num_sel 来控制最后正、负样本的比例在 1 : 3 左右。
Fig.3 positive and negtive sample VS ground_truth box
咱们已经在图上画出了prior box,同时也有了ground truth,那么下一步就是将prior box匹配到ground truth上,这是在 src/caffe/utlis/bbox_util.cpp
的 FindMatches 以及子函数MatchBBox函数里完成的。具体的:先从 groudtruth box 出发,为每一个 groudtruth box 找到最匹配的一个 prior box 放入候选正样本集;而后再尝试从剩下的每一个 prior box 出发,寻找与 groundtruth box 知足 IOU>0.5IOU>0.5 的 最大匹配,若是找到了这样的一个匹配结果就放入候选正样本集。这个作的目的是保证每一个 groundtruth box 都至少有一个匹配正样本。
在生成一系列的 prior boxes 以后,会产生不少个符合 ground truth box 的 positive boxes(候选正样本集),但同时,不符合 ground truth boxes 也不少,并且这个 negative boxes(候选负样本集),远多于 positive boxes。这会形成 negative boxes、positive boxes 之间的不均衡。训练时难以收敛。
所以,本文采起,先将每个物体位置上对应 predictions(prior boxes)loss 进行排序。 对于候选正样本集:选择 loss 最高的几个prior box集合与候选正样本集进行匹配(box索引同时存在于这两个集合里则匹配成功),匹配不成功则删除这个正样本(由于这个正样本不在难例里已经很接近ground truth box了,不须要再训练了);对于候选负样本集:选择 loss 最高的几个 prior box 与候选负样本集匹配,匹配成功则做为负样本。这就是一个难例挖掘的过程,举个例子,假设在这8732个prior box里,通过 FindMatches 后获得候选正样本 $P$ 个,候选负样本那就有 $8732-P$ 个。将 prior box 的 prediction loss 按照从大到小顺序排列后选择最高的 $M$ 个prior box。若是这 $P$ 个候选正样本里有 $a$ 个box不在这 $M$ 个prior box里,将这 $a$ 个box从候选正样本集中踢出去。若是这 $8732-P$ 个候选负样本集中包含的$ 8732-P$ 有 $b$ 个在这 $M$ 个prior box,则将这 $b$ 个候选负样本做为最终的负样本。总归一句话就是:选择 loss 值高的难例做为最后的负样本参与 loss 反传计算。SSD算法中经过这种方式来保证 positives、negatives 的比例。实际代码中有三种负样本挖掘方式:
若是选择HARD_EXAMPLE方式(源于论文Training Region-based Object Detectors with Online Hard Example Mining),则默认$M = 64$,因为没法控制正样本数量,这种方式就有点相似于分类、回归按比重不一样交替训练了。
若是选择MAX_NEGATIVE方式,则$M = P*neg\_pos\_ratio$,这里当$neg\_pos\_ratio = 3$的时候,就是论文中的正负样本比例1:3了。
enum MultiBoxLossParameter_MiningType {
MultiBoxLossParameter_MiningType_NONE = 0, MultiBoxLossParameter_MiningType_MAX_NEGATIVE = 1, MultiBoxLossParameter_MiningType_HARD_EXAMPLE = 2 };
本文同时对训练数据作了 data augmentation,数据增广。
每一张训练图像,随机的进行以下几种选择:
采样的 patch 是原始图像大小比例是 [0.3,1.0],aspect ratio 在 0.5 或 2。
当 groundtruth box 的 中心(center)在采样的 patch 中且在采样的 patch中 groundtruth box面积大于0时,咱们保留CropImage。
在这些采样步骤以后,每个采样的 patch 被 resize 到固定的大小,而且以 0.5 的几率随机的 水平翻转(horizontally flipped,翻转不翻转看prototxt,默认不翻转)
这样一个样本被诸多batch_sampler采样器采样后会生成多个候选样本,而后从中随机选一个样本送人网络训练。
SSD的结构在VGG16网络的基础上进行修改,训练时一样为conv1_1,conv1_2,conv2_1,conv2_2,conv3_1,conv3_2,conv3_3,conv4_1,conv4_2,conv4_3,conv5_1,conv5_2,conv5_3(512),fc6通过3*3*1024的卷积(原来VGG16中的fc6是全链接层,这里变成卷积层,下面的fc7层同理),fc7通过1*1*1024的卷积,conv6_1,conv6_2(对应上图的conv8_2),conv7_1,conv7_2,conv,8_1,conv8_2,conv9_1,conv9_2,loss。而后一方面:针对conv4_3(4),fc7(6),conv6_2(6),conv7_2(6),conv8_2(4),conv9_2(4)(括号里数字是每一层选取的default box种类)中的每个再分别采用两个3*3大小的卷积核进行卷积,这两个卷积核是并列的(括号里的数字表明prior box的数量,能够参考Caffe代码,因此上图中SSD结构的倒数第二列的数字8732表示的是全部prior box的数量,是这么来的38*38*4+19*19*6+10*10*6+5*5*6+3*3*4+1*1*4=8732),这两个3*3的卷积核一个是用来作localization的(回归用,若是prior box是6个,那么就有6*4=24个这样的卷积核,卷积后map的大小和卷积前同样,由于pad=1,下同),另外一个是用来作confidence的(分类用,若是prior box是6个,VOC的object类别有20个,那么就有6*(20+1)=126个这样的卷积核)。以下图是conv6_2的localizaiton的3*3卷积核操做,卷积核个数是24(6*4=24,因为pad=1,因此卷积结果的map大小不变,下同):这里的permute层就是交换的做用,好比你卷积后的维度是32×24×19×19,那么通过交换层后就变成32×19×19×24,顺序变了而已。而flatten层的做用就是将32×19×19×24变成32*8664,32是batchsize的大小。另外一方面结合conv4_3(4),fc7(6),conv6_2(6),conv7_2(6),conv8_2(4),conv9_2(4)中的每个和数据层(ground truth boxes)通过priorBox层生成prior box。
通过上述两个操做后,对每一层feature的处理就结束了。对前面所列的5个卷积层输出都执行上述的操做后,就将获得的结果合并:采用Concat,相似googleNet的Inception操做,是通道合并而不是数值相加。
Fig.5 SSD 流程
损失函数方面:和Faster RCNN的基本同样,由分类和回归两部分组成,能够参考Faster RCNN,这里不细讲。总之,回归部分的loss是但愿预测的box和prior box的差距尽量跟ground truth和prior box的差距接近,这样预测的box就能尽可能和ground truth同样。
上面获得的8732个目标框通过Jaccard Overlap筛选剩下几个了;其中不知足的框标记为负数,其他留下的标为正数框。紧随其后:
训练过程当中的 prior boxes 和 ground truth boxes 的匹配,基本思路是:让每个 prior box 回归而且到 ground truth box,这个过程的调控咱们须要损失层的帮助,他会计算真实值和预测值之间的偏差,从而指导学习的走向。
SSD 训练的目标函数(training objective)源自于 MultiBox 的目标函数,可是本文将其拓展,使其能够处理多个目标类别。具体过程是咱们会让每个 prior box 通过Jaccard系数计算和真实框的类似度,阈值只有大于 0.5 的才能够列为候选名单;假设选择出来的是N个匹配度高于百分之五十的框吧,咱们令 i 表示第 i 个默认框,j 表示第 j 个真实框,p表示第p个类。那么$x_{ij}^p$表示 第 i 个 prior box 与 类别 p 的 第 j 个 ground truth box 相匹配的Jaccard系数,若不匹配的话,则$x_{ij}^p=0$。总的目标损失函数(objective loss function)就由 localization loss(loc) 与 confidence loss(conf) 的加权求和: