文章:SSD: Single Shot MultiBox Detector
代码:https://github.com/weiliu89/caffe/tree/ssd
来源: ECCV 2016
目标检测近年来已经取得了很重要的进展,主流的目标算法主要分为两种类型:
不同算法的性能如图所示,可以看到两类方法在准确度和速度上的差异。
本文讲解的是SSD算法,其英文全名是Single Shot MultiBox Detector,Single shot指明了SSD算法属于one-stage方法,MultiBox指明了SSD是多框预测。SSD算法在准确度和速度(除了SSD512)上都比Yolo要好很多。下图2给出了不同算法的基本框架图,对于Faster R-CNN,其先通过CNN得到候选框,然后再进行分类与回归,而Yolo与SSD可以一步到位完成检测。
相比Yolo,SSD采用CNN来直接进行检测,而不是像Yolo那样在全连接层之后做检测。其实采用卷积直接做检测只是SSD相比Yolo的其中一个不同点,另外还有两个重要的改变。
下面我们来详细讲解SSD算法的原理。
SSD和Yolo一样都是采用一个CNN网络来进行检测,但是却采用了多尺度的特征图,其基本架构如图3所示。下面将SSD核心设计理念总结为以下三点:
(1)采用多尺度特征图用于检测
所谓多尺度采用大小不同的特征图,CNN网络一般前面的特征图比较大,后面会逐渐采用stride=2的卷积或者pool来降低特征图大小,这正如图3所示,一个比较大的特征图和一个比较小的特征图,它们都用来做检测。这样做的好处是比较大的特征图来用来检测相对较小的目标,而小的特征图负责检测大目标,如图4所示,8x8的特征图可以划分更多的单元,但是其每个单元的先验框尺度比较小。
(2)采用卷积进行检测
与Yolo最后采用全连接层不同,SSD直接采用卷积对不同的特征图来进行提取检测结果。对于形状为m×n×pm×n×p的特征图,只需要采用3×3×p3×3×p这样比较小的卷积核得到检测值。
(3)设置先验框
在Yolo中,每个单元预测多个边界框,但是其都是相对这个单元本身(正方块),但是真实目标的形状是多变的,Yolo需要在训练过程中自适应目标的形状。而SSD借鉴了Faster R-CNN中anchor的理念,每个单元设置尺度或者长宽比不同的先验框,预测的边界框(bounding boxes)是以这些先验框为基准的,在一定程度上减少训练难度。一般情况下,每个单元会设置多个先验框,其尺度和长宽比存在差异,如图5所示,可以看到每个单元使用了4个不同的先验框,图片中猫和狗分别采用最适合它们形状的先验框来进行训练,后面会详细讲解训练过程中的先验框匹配原则。
SSD的检测值也与Yolo不太一样。对于每个单元的每个先验框,其都输出一套独立的检测值,对应一个边界框,主要分为两个部分。第一部分是各个类别的置信度或者评分,值得注意的是SSD将背景也当做了一个特殊的类别,如果检测目标共有c个类别,SSD其实需要预测c+1个置信度值,其中第一个置信度指的是不含目标或者属于背景的评分。后面当我们说cc个类别置信度时,请记住里面包含背景那个特殊的类别,即真实的检测类别只有c−1个。
在预测过程中,置信度最高的那个类别就是边界框所属的类别,特别地,当第一个置信度值最高时,表示边界框中并不包含目标。第二部分就是边界框的location,包含4个值(cx,cy,w,h),分别表示边界框的中心坐标以及宽高。但是真实预测值其实只是边界框相对于先验框的转换值(paper里面说是offset,但是觉得transformation更合适,参见R-CNN)。先验框位置用d=(dcx,dcy,dw,dh)表示,其对应边界框用b=(bcx,bcy,bw,bh)b表示,那么边界框的预测值ll其实是b相对于d的转换值:
习惯上,我们称上面这个过程为边界框的编码(encode),预测时,你需要反向这个过程,即进行解码(decode),从预测值l中得到边界框的真实位置b:
然而,在SSD的Caffe源码实现中还有trick,那就是设置variance超参数来调整检测值,通过bool参数variance_encoded_in_target来控制两种模式,当其为True时,表示variance被包含在预测值中,就是上面那种情况。但是如果是Fasle(大部分采用这种方式,训练更容易?),就需要手动设置超参数variance,用来对ll的4个值进行放缩,此时边界框需要这样解码:
综上所述,对于一个大小m×n的特征图,共有mn个单元,每个单元设置的先验框数目记为k,那么每个单元共需要(c+4)k个预测值,所有的单元共需要(c+4)kmn个预测值,由于SSD采用卷积做检测,所以就需要(c+4)k个卷积核完成这个特征图的检测过程。
SSD采用VGG16作为基础模型,然后在VGG16的基础上新增了卷积层来获得更多的特征图以用于检测。
SSD的网络结构如下图所示。上面是SSD模型,下面是Yolo模型,可以明显看到SSD利用了多尺度的特征图做检测。模型的输入图片大小是300×300(还可以是512×512,其与前者网络结构没有差别,只是最后新增一个卷积层,本文不再讨论)。
采用VGG16做基础模型,首先VGG16是在ILSVRC CLS-LOC数据集预训练。然后借鉴了DeepLab-LargeFOV,分别将VGG16的全连接层fc6和fc7转换成3×3卷积层conv6和1×1卷积层conv7,同时将池化层pool5由原来的2×2−s2变成3×3−s1(猜想是不想reduce特征图大小),为了配合这种变化,采用了一种Atrous Algorithm,其实就是conv6采用扩展卷积或带孔卷积(Dilation Conv),其在不增加参数与模型复杂度的条件下指数级扩大卷积的视野,其使用扩张率(dilation rate)参数,来表示扩张的大小,如下图所示,(a)是普通的3×3卷积,其视野就是3×3,(b)是扩张率为2,此时视野变成7×7,©扩张率为4时,视野扩大为15×15,但是视野的特征更稀疏了。Conv6采用3×3大小但dilation rate=6的扩展卷积。
作者采用了atrous algorithm 的技术,这里所谓的 atrous algorithm,我查阅了资料,就是 hole filling algorithm。
在 DeepLab 的主页上:http://liangchiehchen.com/projects/DeepLab.html,有一张如下的图:
博客 1:http://www.cnblogs.com/jianyingzhou/p/5386222.html
最早用的就是 deeplab 的文章了,Semantic Image Segmentation with Deep
Convolutional Nets and Fully Connected CRFS 这篇文章和 fcn 不同的是,在最后产生 score
map 时,不是进行upsampling,而是采用了 hole algorithm,就是在 pool4 和 pool 5层,步长由 2 变成
1,必然输出的 score map 变大了,但是 receptive field 也变小了,为了不降低 receptive
field,怎么做呢?利用 hole algorithm,将卷积 weights 膨胀扩大,即原来卷积核是 3x3,膨胀后,可能变成 7x7
了,这样 receptive field 变大了,而 score map 也很大,即输出变成 dense 的了。这么做的好处是,输出的 score map 变大了,即是 dense 的输出了,而且 receptive field
不会变小,而且可以变大。这对做分割、检测等工作非常重要。
博客 2:http://blog.csdn.net/tangwei2014/article/details/50453334
既想利用已经训练好的模型进行 fine-tuning,又想改变网络结构得到更加 dense 的 score map.
这个解决办法就是采用 Hole 算法。如下图 (a) (b) 所示,在以往的卷积或者 pooling 中,一个 filter
中相邻的权重作用在 feature map 上的位置都是物理上连续的。如下图 © 所示,为了保证感受野不发生变化,某一层的 stride
由 2 变为 1 以后,后面的层需要采用 hole 算法,具体来讲就是将连续的连接关系是根据 hole size 大小变成 skip
连接的(图 © 为了显示方便直接画在本层上了)。不要被 © 中的 padding 为 2 吓着了,其实 2 个 padding
不会同时和一个 filter 相连。pool4 的 stride 由 2 变为 1,则紧接着的 conv5_1, conv5_2 和 conv5_3 中 hole size 为
2。接着 pool5 由 2 变为 1 , 则后面的 fc6 中 hole size 为 4。
本文还将 fully convolutional reduced (atrous) VGGNet 中的所有的 dropout layers、fc8 layer 移除掉了。
本文在 fine-tuning 预训练的 VGG model 时,初始 learning rate 为 10−3,momentum 为 0.9,weight decay 为 0.0005,batch size 为 32,learning rate decay 的策略随数据集的不同而变化。
其中VGG16中的Conv4_3层将作为用于检测的第一个特征图。conv4_3层特征图大小是38×38,但是该层比较靠前,其norm较大,所以在其后面增加了一个L2 Normalization层(参见ParseNet),以保证和后面的检测层差异不是很大,这个和Batch Normalization层不太一样,其仅仅是对每个像素点在channle维度做归一化,而Batch Normalization层是在[batch_size, width, height]三个维度上做归一化。归一化后一般设置一个可训练的放缩变量gamma,使用TF可以这样简单实现:
# l2norm (not bacth norm, spatial normalization)
def l2norm(x, scale, trainable=True, scope="L2Normalization"):
n_channels = x.get_shape().as_list()[-1]
l2_norm = tf.nn.l2_normalize(x, [3], epsilon=1e-12)
with tf.variable_scope(scope):
gamma = tf.get_variable("gamma", shape=[n_channels, ], dtype=tf.float32,
initializer=tf.constant_initializer(scale),
trainable=trainable)
return l2_norm * gamma
从后面新增的卷积层中提取Conv7,Conv8_2,Conv9_2,Conv10_2,Conv11_2作为检测所用的特征图,加上Conv4_3层,共提取了6个特征图,其大小分别是(38,38),(19,19),(10,10),(5,5),(3,3),(1,1),但是不同特征图设置的先验框数目不同(同一个特征图上每个单元设置的先验框是相同的,这里的数目指的是一个单元的先验框数目)。先验框的设置,包括尺度(或者说大小)和长宽比两个方面。对于先验框的尺度,其遵守一个线性递增规则:随着特征图大小降低,先验框尺度线性增加:
其中m指的特征图个数,但却是5,因为第一层(Conv4_3层)是单独设置的,sk表示先验框大小相对于图片的比例,而smin和smax表示比例的最小值与最大值,paper里面取0.2和0.9。对于第一个特征图,其先验框的尺度比例一般设置为smin/2=0.1,那么尺度为300×0.1=30。对于后面的特征图,先验框尺度按照上面公式线性增加,但是先将尺度比例先扩大100倍,此时增长步长为,这样各个特征图的sk为20,37,54,71,88,将这些比例除以100,然后再乘以图片大小,可以得到各个特征图的尺度为60,111,162,213,264,这种计算方式是参考SSD的Caffe源码。
综上,可以得到各个特征图的先验框尺度30,60,111,162,213,264。对于长宽比,一般选取ar∈{1,2,3,1/2,1/3},对于特定的长宽比,按如下公式计算先验框的宽度与高度(后面的sk均指的是先验框实际尺度,而不是尺度比例):
默认情况下,每个特征图会有一个ar =1,且尺度为 sk的先验框,除此之外,还会设置一个尺度为
的先验框,这样每个特征图都设置了两个长宽比为1但大小不同的正方形先验框。注意最后一个特征图需要参考一个虚拟 sm+1=300×105/100=315来计算 s′m。因此,每个特征图一共有 6 个先验框 {1,2,3,1/2,1/3,1′},但是在实现时,Conv4_3,Conv10_2和Conv11_2层仅使用4个先验框,它们不使用长宽比为 3,1/3,的先验框。每个单元的先验框的中心点分布在各个单元的中心,即
得到了特征图之后,需要对特征图进行卷积得到检测结果,下图给出了一个 5×5大小的特征图的检测过程。其中Priorbox是得到先验框,前面已经介绍了生成规则。检测值包含两个部分:类别置信度和边界框位置,各采用一次 3×3卷积来进行完成。令 n_k 为该特征图所采用的先验框数目,那么类别置信度需要的卷积核数量为 nk×c ,而边界框位置需要的卷积核数量为 nk×4 。由于每个先验框都会预测一个边界框,所以SSD300一共可以预测 38×38×4+19×19×6+10×10×6+5×5×6+3×3×4+1×1×4=8732 个边界框,这是一个相当庞大的数字,所以说SSD本质上是密集采样。
(1)先验框匹配
在训练过程中,首先要确定训练图片中的ground truth(真实目标)与哪个先验框来进行匹配,与之匹配的先验框所对应的边界框将负责预测它。在Yolo中,ground truth的中心落在哪个单元格,该单元格中与其IOU最大的边界框负责预测它。
但是在SSD中却完全不一样,SSD的先验框与ground truth的匹配原则主要有两点。
首先,对于图片中每个ground truth,找到与其IOU最大的先验框,该先验框与其匹配,这样,可以保证每个ground truth一定与某个先验框匹配。通常称与ground truth匹配的先验框为正样本,反之,若一个先验框没有与任何ground truth进行匹配,那么该先验框只能与背景匹配,就是负样本。一个图片中ground truth是非常少的, 而先验框却很多,如果仅按第一个原则匹配,很多先验框会是负样本,正负样本极其不平衡,所以需要第二个原则。
第二个原则是:对于剩余的未匹配先验框,若某个ground truth的IOU大于某个阈值(一般是0.5),那么该先验框也与这个ground truth进行匹配。这意味着某个ground truth可能与多个先验框匹配,这是可以的。但是反过来却不可以,因为一个先验框只能匹配一个ground truth,如果多个ground truth与某个先验框IOU大于阈值,那么先验框只与IOU最大的那个先验框进行匹配。
第二个原则一定在第一个原则之后进行,仔细考虑一下这种情况,如果某个ground truth所对应最大IOU小于阈值,并且所匹配的先验框却与另外一个ground truth的IOU大于阈值,那么该先验框应该匹配谁,答案应该是前者,首先要确保某个ground truth一定有一个先验框与之匹配。但是,这种情况我觉得基本上是不存在的。由于先验框很多,某个ground truth的最大IOU肯定大于阈值,所以可能只实施第二个原则既可以了,这里的TensorFlow版本就是只实施了第二个原则,但是这里的Pytorch两个原则都实施了。下图为一个匹配示意图,其中绿色的GT是ground truth,红色为先验框,FP表示负样本,TP表示正样本。
尽管一个ground truth可以与多个先验框匹配,但是ground truth相对先验框还是太少了,所以负样本相对正样本会很多。为了保证正负样本尽量平衡,SSD采用了hard negative mining,就是对负样本进行抽样,抽样时按照置信度误差(预测背景的置信度越小,误差越大)进行降序排列,选取误差的较大的top-k作为训练的负样本,以保证正负样本比例接近1:3。
(2)损失函数
训练样本确定了,然后就是损失函数了。损失函数定义为位置误差(locatization loss, loc)与置信度误差(confidence loss, conf)的加权和:
其中N是先验框的正样本数量。
并且ground truth的类别为p。c为类别置信度预测值。L为先验框的所对应边界框的位置预测值,而g是ground truth的位置参数。对于位置误差,其采用Smooth L1 loss,定义如下
采用数据扩增(Data Augmentation)可以提升SSD的性能,主要采用的技术有水平翻转(horizontal flip),随机裁剪加颜色扭曲(random crop & color distortion),随机采集块域(Randomly sample a patch)(获取小目标训练样本),如下图所示:
其它的训练细节如学习速率的选择详见论文,这里不再赘述。
预测过程比较简单,对于每个预测框,首先根据类别置信度确定其类别(置信度最大者)与置信度值,并过滤掉属于背景的预测框。然后根据置信度阈值(如0.5)过滤掉阈值较低的预测框。对于留下的预测框进行解码,根据先验框得到其真实的位置参数(解码后一般还需要做clip,防止预测框位置超出图片)。解码之后,一般需要根据置信度进行降序排列,然后仅保留top-k(如400)个预测框。最后就是进行NMS算法,过滤掉那些重叠度较大的预测框。最后剩余的预测框就是检测结果了。
1 SSD结合了YOLO中的回归思想和Faster-RCNN中的Anchor机制,使用全图各个位置的多尺度区域特征进行回归,既保持了YOLO速度快的特性,也保证了窗口预测的跟Faster-RCNN一样比较精准。
2 SSD的核心是在特征图上采用卷积核来预测一系列Default Bounding Boxes的类别、坐标偏移。为了提高检测准确率,SSD在不同尺度的特征图上进行预测。
一、模型结构
1 多尺度特征图(Mult-scale Feature Map For Detection)
在图像Base Network基础上,将Fc6,Fc7变为了Conv6,Conv7两个卷积层,添加了一些卷积层(Conv8,Conv9,Conv10,Conv11),这些层的大小逐渐减小,可以进行多尺度预测。
2 卷积预测器(Convolutional Predictors For Detection)
每个新添加的卷积层和之前的部分卷积层,使用一系列的卷积核进行预测。对于一个大小为m * n大小,p通道的卷积层,使用3 * 3的p通道卷积核作为基础预测元素进行预测,在某个位置上预测出一个值,该值可以是某一类别的得分,也可以是相对于Default Bounding Boxes的偏移量,并且在图像的每个位置都将产生一个值。
3 默认框和比例(Default Boxes And Aspect Ratio)
在特征图的每个位置预测K个Box。对于每个Box,预测C个类别得分,以及相对于Default Bounding Box的4个偏移值,这样需要(C+4) * k个预测器,在m*n的特征图上将产生(C+4) * k * m * n个预测值。这里,Default Bounding Box类似于Faster-RCNN中Anchors,如下图所示。
二、模型训练
1 监督学习的训练关键是人工标注的label。对于包含Default Box(在Faster R-CNN中叫做Anchor)的网络模型(如:YOLO,Faster R-CNN, MultiBox)关键点就是如何把 标注信息(Ground True Box,Ground True Category)映射到(Default Box上)。
2 给定输入图像以及每个物体的Ground Truth,首先找到每个Ground True Box对应的Default Box中IOU最大的作为正样本。然后,在剩下的Default Box中找到那些与任意一个Ground Truth Box的IOU大于0.5的Default Box作为正样本。其他的作为负样本(每个Default Box要么是正样本Box要么是负样本Box)。如上图中,两个Default Box与猫匹配,一个与狗匹配。在训练过程中,采用Hard Negative Mining 的策略(根据Confidence Loss对所有的Box进行排序,使正负例的比例保持在1:3) 来平衡正负样本的比率。
3 损失函数
与Faster-RCNN中的RPN是一样的,不过RPN是预测Box里面有Object或者没有,没有分类,SSD直接用的Softmax分类。Location的损失,还是一样,都是用Predict box和Default Box/Anchor的差 与Ground Truth Box和Default Box/Anchor的差进行对比,求损失。
首先整体看一下SSD在VOC2007,VOC2012及COCO数据集上的性能。相比之下,SSD512的性能会更好一些。加*的表示使用了image expansion data augmentation(通过zoom out来创造小的训练样本)技巧来提升SSD在小目标上的检测效果,所以性能会有所提升。
检测结果展示:
关于算法基于各种框架的实现,链接在参考文献之中,欢迎交流学习~
您的支持,是我不断创作的最大动力~
欢迎点赞,关注,留言交流~
深度学习,乐此不疲~