SSD详解 + default box生成过程

在mxnet上面看李沐大神的视频,自己看了SSD的paper里面还是有些一知半解的东西,于是就用篇博客记录下来。文章中的图和部分见解都来自于网络有些错误的图已经修正,如有侵权,联系我删除。

先放一张SSD算法的模型图。SSD采用不用卷积层的feature map进行综合,将VGG16的最后两层全连接改为卷积层并额外增加四个卷积层来达到构造网络。对于这5个不同尺度的卷积层输出的feature map,作者分别采用不同的3x3的卷积核进行卷积,一个输出分类的置信度(condfidence),一个输出回归的localization。下面先放一张作者构造的代码:


作者文中多次提到的default boxes和aspect ratios,那么他们是什么意思呢?其实就是每一个box相对于其对应的 feature map cell 的位置是固定的。 在每一个 feature map cell 中,我们要 predict 得到的 box 与 default box 之间的 offsets,以及每一个 box 中包含物体的 score(每一个类别概率都要计算出)。 
因此,对于一个位置上的 kk 个boxes 中的每一个 box,我们需要计算出 cc 个类,每一个类的 score,还有这个 box 相对于 它的默认 box 的 4 个偏移值(offsets)。于是,在 feature map 中的每一个 feature map cell 上,就需要有 (c+4)×k(c+4)×k 个 filters。对于一张 m×nm×n 大小的 feature map,即会产生 (c+4)×k×m×n(c+4)×k×m×n 个输出结果。

在训练的过程中,SSD与two stage的方法最大的区别是,SSD 训练图像中的 groundtruth 需要赋予到那些固定输出的 boxes 上。在前面也已经提到了,SSD 输出的是事先定义好的,一系列固定大小的 bounding boxes。

最后会得到(38*38*4 + 19*19*6 + 10*10*6 + 5*5*6 + 3*3*4 + 1*1*4)= 8732个default box。

训练中还有一个东西:prior box,是指实际中选择的default box(每一个feature map cell 不是k个default box都取)。也就是说default box是一种概念,prior box则是实际的选取。训练中一张完整的图片送进网络获得各个feature map,对于正样本训练来说,需要先将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个。那个可能分别有10、20个prior box能分别与这2个ground truth box匹配上。训练的损失包含定位损失和回归损失两部分。

重点:

一直很难理解上述的8732个框和每个feature map每个像素生成6个框,paper里面提到:ratio={1,2,3,1/2,1/3},但是看了代码发现其实这5种ratio是作者计算出来的。作者在ssd_pascal.py这个脚本中提到的计算如下,代码在:https://github.com/weiliu89/caffe/blob/ssd/examples/ssd/ssd_pascal_orig.py

#coding:utf-8
import math

min_dim = 300   #######维度
# conv4_3 ==> 38 x 38
# fc7 ==> 19 x 19
# conv6_2 ==> 10 x 10
# conv7_2 ==> 5 x 5
# conv8_2 ==> 3 x 3
# conv9_2 ==> 1 x 1
mbox_source_layers = ['conv4_3', 'fc7', 'conv6_2', 'conv7_2', 'conv8_2', 'conv9_2'] #####prior_box来源层,可以更改。很多改进都是基于此处的调整。
# in percent %
min_ratio = 20 ####这里即是论文中所说的Smin=0.2,Smax=0.9的初始值,经过下面的运算即可得到min_sizes,max_sizes。具体如何计算以及两者代表什么,请关注我的博客SSD详解。这里产生很多改进。
max_ratio = 90
####math.floor()函数表示:求一个最接近它的整数,它的值小于或等于这个浮点数。
step = int(math.floor((max_ratio - min_ratio) / (len(mbox_source_layers) - 2)))####取一个间距步长,即在下面for循环给ratio取值时起一个间距作用。可以用一个具体的数值代替,这里等于17。
min_sizes = []  ###经过以下运算得到min_sizes和max_sizes。
max_sizes = []
for ratio in xrange(min_ratio, max_ratio + 1, step):  ####从min_ratio至max_ratio+1每隔step=17取一个值赋值给ratio。注意xrange函数的作用。
########min_sizes.append()函数即把括号内部每次得到的值依次给了min_sizes。
  min_sizes.append(min_dim * ratio / 100.)
  print(min_sizes)
  max_sizes.append(min_dim * (ratio + step) / 100.)
min_sizes = [min_dim * 10 / 100.] + min_sizes
max_sizes = [min_dim * 20 / 100.] + max_sizes
steps = [8, 16, 32, 64, 100, 300]  ###这一步要仔细理解,即计算卷积层产生的prior_box距离原图的步长,先验框中心点的坐标会乘以step,相当于从feature map位置映射回原图位置,比如conv4_3输出特征图大小为38*38,而输入的图片为300*300,所以38*8约等于300,所以映射步长为8。这是针对300*300的训练图片。
aspect_ratios = [[2], [2, 3], [2, 3], [2, 3], [2], [2]]

print(min_sizes)
print(max_sizes)

我们可以得到:建议大家跑一下这段代码就能明白。

/usr/bin/python /Users/bin/Desktop/data/xuelang/ssd.py
[60.0]
[60.0, 111.0]
[60.0, 111.0, 162.0]
[60.0, 111.0, 162.0, 213.0]
[60.0, 111.0, 162.0, 213.0, 264.0]
[30.0, 60.0, 111.0, 162.0, 213.0, 264.0]
[60.0, 111.0, 162.0, 213.0, 264.0, 315.0]

Process finished with exit code 0

首先根据以上代码,六个特征层共产生6组min_sizes和max_sizes。另外min_dim=300,ratio取20到90即min_ratio=20,max_ratio=90。

        然后根据代码的计算公式,我们还需要step,注意是step不是steps,两者的作用不一样,在代码中有博主的注释。这里计算后step=(max_ratio-min_ratio)/(len(mbox_source_layers)-2)=(90-20)/(6-2)=17。要说这个step的作用,其实就是取一个间隔,全文看完你就应该明白了。其实这里用了一个复杂的公式说白了就是显得代码高大上一点。

        然后就开始计算min_sizes和max_sizes了,首先定义数组min_sizes[]和max_sizes[]用来存放计算结果,没有初始化说明默认为0,。然后计算conv4_3产生的min_sizes和max_sizes。根据代码中的公式计算:min_sizes=[min_dim*10/100]+min_sizes和max_sizes=[min_dim*20/100]+max_sizes得到结果为min_sizes=[300*10/100]+0=30,而max_sizes=[300*20/100]+0=60。这样conv4_3的计算公式被计算分别为30和60。这里为什么要先计算下面两行产生conv4_3的结果而不是使用上面两行公式产生博主也没有搞明白,欢迎指教。

        然后根据公式min_sizes.append(min_dim*ratio/100)和公式max_sizes.append(min_dim*(ratio+step)/100)来计算剩下5层的min_size和max_sizes。这里需要用到ratio和step,我们前面讲了step=17,根据代码for ratio in xrange(min_ratio, max_ratio+1, step)(这句的意思我们在代码中有注释,即在min_ratio和max_ratio之间即20-90之间以step=17为间隔产生一组数据赋值给ratio),最终ratio=[20,37,54,71,88]。所以对于剩余5层所产生的min_sizes和max_sizes分别为:

fc7:min_sizes=min_dim*ratio/100=300*20/100=60,max_sizes=min_dim*(ratio+step)/100=300*(20+17)/100=111;

conv6_2:min_sizes=min_dim*ratio/100=300*37/100=111,max_sizes=min_dim*(ratio+step)/100=300*(37+17)/100=162;

conv7_2:min_sizes=min_dim*ratio/100=300*54/100=162,max_sizes=min_dim*(ratio+step)/100=300*(54+17)/100=213;

conv8_2:min_sizes=min_dim*ratio/100=300*71/100=213,max_sizes=min_dim*(ratio+step)/100=300*(71+17)/100=264;

conv9_2:min_sizes=min_dim*ratio/100=300*88/100=213,max_sizes=min_dim*(ratio+step)/100=300*(88+17)/100=315;

文中代码显示,给出的长宽比为

aspect_ratios = [[2], [2, 3], [2, 3], [2, 3], [2], [2]] 

这里并不是paper中所给出的ar={1,2,3,1/2,1/3},这个比例是计算出来的。

        首先我们要知道,我们在前面也讲了,每层的特征图的每个中心点分别会产生4、6、6、6、4、4个默认框,但我们要知道为什么是这几个默认框,这里就和aspect_ratios有关系了。

        在SSD中6层卷积层的每个特征图的每个中心点会产生2个不同大小的正方形默认框,另外每设置一个aspect_ratio则会增加两个长方形默认框,而文中代码对于6层的aspect_ratio个数分别为1、2、2、2、1、1,所以这也就是为什么会产生4、6、6、6、4、4个默认框了。例如conv4_3默认生成两个不同大小的正方形默认框,另外又有一个aspect_ratio=2产生了两个长方形默认框,所以总共有4个。再如fc7,默认生成两个正方形默认框,另外又有aspect_ratio=[2,3],所以又生成了4个不同的长方形默认框,共有6个不同大小的默认框。

        接着我们再讲这些产生的默认框的大小计算。这里参考paper中的计算公式,我们可以知道,对于产生的正方形的默认框,一大一小共两个,其边长计算公式为:小边长=min_size,而大边长=sqrt(min_size*max_size)。对于产生的长方形默认框,我们需要计算它的高(height)和宽(width),其中,height=1/sqrt(aspect_ratio)*min_size,width=sqrt(aspect_ratio)*min_size,对其高和宽翻转后得到另一个面积相同但宽高相互置换的长方形。如图所示:

根据以上分析,我们可以计算6层中每个特征图的每个中心点所产生的默认框的大小,分别如下:

conv4_3:小正方形边长=min_size=30,大正方形边长=sqrt(min_size*max_size)=sprt(30*60)=42.42;

                长方形的宽=sqrt(aspect_ratio)*min_size=sqrt(2)*30,高=1/sqrt(aspect_ratio)*min_size=30/sqrt(2),宽高比刚好为2:1;

                    将以上宽高旋转90度产生另一个长方形,宽高比变为1:2。

fc7:小正方形边长=min_size=60,大正方形边长=sqrt(min_size*max_size)=sprt(60*111)=81.6;

                   第1组长方形的宽=sqrt(aspect_ratio)*min_size=sqrt(2)*60,高=1/sqrt(aspect_ratio)*min_size=60/sqrt(2),宽高比刚好为2:1;

                              将以上宽高旋转90度产生另一个长方形,宽高比变为1:2。

                   第2组长方形的宽=sqrt(aspect_ratio)*min_size=sqrt(3)*60,高=1/sqrt(aspect_ratio)*min_size=60/sqrt(3),宽高比刚好为3:1;

                              将以上宽高旋转90度产生另一个长方形,宽高比变为1:3。

conv6_2:小正方形边长=min_size=111,大正方形边长=sqrt(min_size*max_size)=sprt(111*162);

                   第1组长方形的宽=sqrt(aspect_ratio)*min_size=sqrt(2)*111,高=1/sqrt(aspect_ratio)*min_size=111/sqrt(2),宽高比刚好为2:1;

                              将以上宽高旋转90度产生另一个长方形,宽高比变为1:2。

                   第2组长方形的宽=sqrt(aspect_ratio)*min_size=sqrt(3)*111,高=1/sqrt(aspect_ratio)*min_size=111/sqrt(3),宽高比刚好为3:1;

                              将以上宽高旋转90度产生另一个长方形,宽高比变为1:3。

        conv7_2、conv8_2、conv9_2我们这里就不再计算了,相信大家看完以上应该明白了如何计算,具体实现的步骤请大家参考脚本prior_box_layer.cpp。这就是我们先验框的计算方式。

部分内容应用来自:https://blog.csdn.net/xunan003/article/details/79186162

https://www.cnblogs.com/xuanyuyt/p/7447111.html

猜你喜欢

转载自blog.csdn.net/gbyy42299/article/details/81235891