Tensorflow SSD实现与理解
1. 论文解读
1.1 模型特点
多个feature map作为目标检测的输入;通过卷积获得目标检测的得分和位置偏移;默认窗和纵横比,feature map上的每一个cell产生默认bounding box,feature map上产生的box与其周边的box相对位置是固定的,在每个cell上我们预测偏移和得分,针对每个box我们需要计算c个类别的得分,4个位置offset,因此需要(c+4)k个filter,因此feature map = mxnxp的输出为(c+4)kmn。
2.1 训练特点
- 匹配策略
Ground Truth与default box计算IOU,之后设置IOU > 0.5认为匹配。 - 目标函数
包含定位loc loss和类别conf loss:
其中N代表default box与GT匹配的个数,if N =0,loss = 0. loc loss 为smooth L1 loss,类似与FasterRCNN,如下:
其中l为predict box,g为GT box,Xi,jP = {1,0},表示第i个default box与第j个GT box匹配,且类别为P;cx,cy表示default box的center offset,
需要注意的是负样本的选取(论文中Hard negative mining部分),什么是hard negative mining,主要是为了降低假阳性即背景被识别成目标,粘一段百度的回答:对于目标检测中我们会事先标记处ground truth,然后再算法中会生成一系列proposal,这些proposal有跟标记的ground truth重合的也有没重合的,那么重合度(IOU)超过一定阈值(通常0.5)的则认定为是正样本,以下的则是负样本。然后扔进网络中训练。However,这也许会出现一个问题那就是正样本的数量远远小于负样本,这样训练出来的分类器的效果总是有限的,会出现许多false positive,把其中得分较高的这些false positive当做所谓的Hard negative,既然mining出了这些Hard negative,就把这些扔进网络再训练一次,从而加强分类器判别假阳性的能力。
注这部分连接原文:https://blog.youkuaiyun.com/qq_37541097/article/details/80917536
- 尺度和default box纵横比的选择
多个feature map预测搞定尺度问题,不同层每个cell的感受野不同;每一个目标检测层生成default box的方法为:每个cell 产生一个固定大小,固定比例的default box,这里的default box的大小对应的事原图上的大小比例。 - 数据增广
针对每个图片随机采用如下措施:
使用原图
取图像切片使目标的IOU介于:0.1:0.2:0.9
随机切片;
2. 网络结构
vsd文件在github:SSD 网络结构
2.1 特征提取卷积层
VGG16为backbone网络,如下图:
第17层卷积,即block6 为dilated convolution,rate = 6,,kernel= 3*3,kernel_num = 1024.
采用网络结构中的:‘block4’, ‘block7’, ‘block8’, ‘block9’, ‘block10’, 'block11’作为ssd_multi_box的输入,如下图:
2.2 anchor_box产生
- anchor_sizes的产生
paper中有两个超参数:Smin = 0.2;Smax = 0.9,表示所有的目标检测层中,最(前)大的层用来预测原图大小0.2尺度的目标,最小(后)层用来预测原图大小0.9尺度的目标,其他的层介于Smin~Smax之间的目标;因此通过下式计算得到每个层对应的Smin和Smax
这里论文与代码有一点不同,论文中的描述k=6,代码m=5. 这里还有一些向下取整的操作:
得出:S = [0.2,0.37,0.54,0.71,0.88,1.05]
因此得出6个区间片段:[0.1=0.5Smin*, 0.2],[0.2,0.37],[0.37,0.54],[0.54.0.71],[0.71,0.88],[0.88,1.05]
按照比例映射到原图中anchor_sizes: = S300 ,得出anchor_size:
[30, 60], [60,111],[111,162],[162,213],[213,264],[264,315];
第一层这里的min_sizes我考虑应该是作者给的一个 超参数:0.5Sminmin(high,width)*,对第一层单独设置了一下。
作者的原始代码:
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']
min_ratio = 20
max_ratio = 90
step = int(math.floor((max_ratio - min_ratio) / (len(mbox_source_layers) - 2)))
min_sizes = []
max_sizes = []
for ratio in xrange(min_ratio, max_ratio + 1, step):
min_sizes.append(min_dim * ratio / 100.)
max_sizes.append(min_dim * (ratio + step) / 100.)
print(min_sizes)
print(max_sizes)
min_sizes = [min_dim * 10 / 100.] + min_sizes
max_sizes = [min_dim * 20 / 100.] + max_sizes
steps = [8, 16, 32, 64, 100, 300],对于feature map一个格反应到原图的步进。
aspect_ratios = [[2], [2, 3], [2, 3], [2, 3], [2], [2]]
print(min_sizes)
print(max_sizes)
#https://blog.youkuaiyun.com/xunan003/article/details/79186162
在tensorflow的代码中:
anchor_size_bound设置的为smin=0.15,smax=0.9;steps = 0.75/4 = 0.1875;
得出:S = [0.15,0.3375,0.525,0.7125,0.9,1.275]
得出区间片段:(保留2位小数有效数字向下取floor计算)
[0.075 = 0.5Smin*, 0.15],[0.15, 0.03375],…
因此得出:
[anchor_sizes=[(21., 45.),
(45., 99.),
(99., 153.),
(153., 207.),
(207., 261.),
(261., 315.)],
- anchor_ratio
anchor_ratios=[[2, .5],
[2, .5, 3, 1./3],
[2, .5, 3, 1./3],
[2, .5, 3, 1./3],
[2, .5],
[2, .5]],
在每一个特征提取层,生成一个边长为min_size和sqrt(min_sizemax_size)的正方形box;针对每个ratio,生成一个[sqrt(ratio)min_size, 1/sqrt(ratio)min_size]和[1/sqrt(ratio)min_size, sqrt(ratio)min_size]的长方形。
anchor_num
由以上两部分可以得出:
我们都知道SSD默认框从6层卷积层输出的特征图中产生,分别为conv4_3、fc7、conv6_2、conv7_2、conv8_2、conv9_2。这6个特征层产生的特征图的大小分别为3838、1919、1010、55、33、11。每个nn大小的特征图中有nn个中心点,每个中心点产生k个默认框,六层中每层的每个中心点产生的k分别为4、6、6、6、4、4。所以6层中的每层取一个特征图共产生38384+19196+10106+556+334+114=8732个默认框。
3. TensorFlow 实现
TensorFlow的实现代码来自:https://github.com/balancap/SSD-Tensorflow, 小弟作为代码的搬运工,也在我的git上复制了一份,并封装了两个分别调用图片和camera的接口,代码地址:https://github.com/ZhangChuann/go_elife/tree/master/AI_Demo/ssd。
本文代码运行基于TensorFlow 1.8.0, G{U为GTX 970M, 显存2G。
3.1 运行图片检测
source activate tensorflow
cd go_elife/AI_Demo/ssd
python img_demo.py -i demo/dog.jpg
运行效果:
3.2 运行camera检测
python video_demo.py
运行效果: