论文名称 | SSD:Single Shot MultiBox Detector |
pytorch code | A PyTorch Implementation of Single Shot MultiBox Detector |
作者及单位 | liu wei & google |
SSDnet是2016年提出的一种单目标检测网络,属于one stage 的目标检测,个人认为SSDnet与yolov3属于相爱相杀,由于在yolov3 之前提出,发现在yolov3中有很多地方与SSDnet相似的地方,本解读系列主要是从代码的角度出发进行ssdnet的深层解析,且与论文结合加深理解。
基础篇 网络架构的简单介绍
paper中的backbone 是采用vgg16的网络来提取基础特征,以论文中首选的SSDnet-300为例,下图的虚线框是vgg16网络中的卷积层结构,剔除了conv5_3后续的所有层,并单独提出vgg16中的conv4_3卷积层,作为后续得到38*38尺寸feature map的待卷积层。
图1.ssdnet 网络结构图
先放一段代码,进行简单说明,便于深度理解,ssd.py 126-146行。
126 def vgg(cfg, i, batch_norm=False):
127 layers = []
128 in_channels = i
129 for v in cfg:
130 if v == 'M':
131 layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
132 elif v == 'C':
133 layers += [nn.MaxPool2d(kernel_size=2, stride=2, ceil_mode=True)]
134 else:
135 conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
136 if batch_norm:
137 layers += [conv2d, nn.BatchNorm2d(v), nn.ReLU(inplace=True)]
138 else:
139 layers += [conv2d, nn.ReLU(inplace=True)]
140 in_channels = v
141 pool5 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
142 conv6 = nn.Conv2d(512, 1024, kernel_size=3, padding=6, dilation=6)
143 conv7 = nn.Conv2d(1024, 1024, kernel_size=1)
144 layers += [pool5, conv6,
145 nn.ReLU(inplace=True), conv7, nn.ReLU(inplace=True)]
146 return layers
vgg的实参给定的为:
[64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'C', 512, 512, 512, 'M',512,512,512] |
这在ssd.py代码的183-185行中有说明。
下图是VGG 原论文中table1 vgg16的结构图,为便于核对,我也搬过来了,这里的‘C’是池化中的天花板模式,300/4=75,由于75/2=37.5,向上约为38*38。在conv5_3后面增加了pool5,conv6,conv7.这里的conv6、conv7分别替代了原vgg16中的fc6、fc7.
图2,vgg16 网络参数
上面应该理清了vgg16的基础层,现在来说额外增加层,是跟在conv7后面的,参照图1看到,跟在conv7后续的conv8_2、conv9_2,conv10_2,就是额外增加层,代码来了! 来自ssd.py 第149-163行。
149 def add_extras(cfg, i, batch_norm=False):
150 # Extra layers added to VGG for feature scaling
151 layers = []
152 in_channels = i
153 flag = False
154 for k, v in enumerate(cfg):
155 if in_channels != 'S':
156 if v == 'S':
157 layers += [nn.Conv2d(in_channels, cfg[k + 1],
158 kernel_size=(1, 3)[flag], stride=2, padding=1)]
159 else:
160 layers += [nn.Conv2d(in_channels, v, kernel_size=(1, 3)[flag])]
161 flag = not flag
162 in_channels = v
163 return layers
还是继续将cfg 参数摆出来,这里来自第192-193行。
[256, 'S', 512, 128, 'S', 256, 128, 256, 128, 256] |
补充语句,i=1024,是conv7卷积层的层数。我想在这里稍微解释一下代码的运行规律,v遍历cfg参数的所有参数,当k=0,v=256时, 执行第157行代码,此时flag=False,进行卷积操作,conv:1*1*256, 当k=1,v='S'时,此时flag更新为True,in_channels=256,执行157-158行代码,,进行卷积操作,conv:3*3*cfg[2]==>conv:3*3*512,且stride=2. 上一部分完成后得到了conv8_2,后续操作一样近似,分别得到了conv9_2、conv10_2、conv11_2.将所有操作层依旧放在add_extras 的layers中,供后续forward调用。
上述就弄清楚了额外增加层,现在就得说道比较最后的多尺度目标框层(可以看做我自己取得名字,可能不大准) ,为什么叫多尺度目标框层呢,这一部分,我们要像yolov3一样得到输出层13*13*255,26*26*255,52*52*255,这个是yolov3的输出,不要混淆,而ssdnet的尺度有6种,比yolov3多一倍,这也是ssdnet3在小目标物体检测性能好的原因之一,如图3是原paper中的图,
图3多尺度目标框下的先验框
图(b)、图(c)是两种尺寸下的先验框,论文设置每个特征图中的单元格产生几个预选框,这里和yolov3是很像的,还记得吗?255=85*3,如何产生在下一篇具体阐述,为产生对于输出,需要进行一次卷积操作,而ssdnet 每组不同尺度的特征图共产生两种不同数量num_prior的预选框,分别是4、6,根据图1,6组不同尺度的特征图分别为38*38、19*19、10*10、5*5、3*3、1*1,为产生与物体检测相匹配的特征(需要预测类别与坐标信息),分别通过一次卷积操作生成维度为num_prior*classes*4的特征图。代码来了...来自 ssd.py 的第166-180行。
166 def multibox(vgg, extra_layers, cfg, num_classes):
167 loc_layers = []
168 conf_layers = []
169 vgg_source = [21, -2]
170 for k, v in enumerate(vgg_source):
171 loc_layers += [nn.Conv2d(vgg[v].out_channels,
172 cfg[k] * 4, kernel_size=3, padding=1)]
173 conf_layers += [nn.Conv2d(vgg[v].out_channels,
174 cfg[k] * num_classes, kernel_size=3, padding=1)]
175 for k, v in enumerate(extra_layers[1::2], 2):
176 loc_layers += [nn.Conv2d(v.out_channels, cfg[k]
177 * 4, kernel_size=3, padding=1)]
178 conf_layers += [nn.Conv2d(v.out_channels, cfg[k]
179 * num_classes, kernel_size=3, padding=1)]
180 return vgg, extra_layers, (loc_layers, conf_layers)
cfg 的参数为
[4, 6, 6, 6, 4, 4]
这里的代码解释一下,有点难以理解。首先,此函数是输出预测检测结果loc_layer和conf_layers ,它将置信度(类别)预测和坐标预测分开了。从170-173代码开始解释,这部分是由vgg基础层产生预测信息的,当k=0,v=21时,vgg[21]是conv4_3层输出的卷积层网络,通过卷积操作 3*3*(4*4)、3*3*(4*classes)分两部分预测得到loc_layers,conf_layers,k=1,v=-2是conv7层输出的卷积层网络,通过一样的卷积操作得到,需要注意的是此部分每个特征图单元生成6个预测框。从175-179行是额外增加层产生的预测信息,回顾一下,extra_layers中的layers结构组成部分[conv8_1,conv8_2,conv9_1,conv9_2,conv10_1,conv10_2,conv11_1,conv11_2],因为额外增加层的每一组要经过1*1,3*3两次卷积操作完成,这里放一个代码解释175部分,注意,真实的extra_layers中的元素不是str,完全是方便理解。下图一目了然,k=2,3,4,5,cfg 的参数也就是6,6,4,4,分别对conv8_2、conv9_2、conv_10_2、conv11_2进行卷积。
图4,辅助代码
上述的所有都是Pytorch 层的定义,从理论网络的介绍角度来说吗,网络细节都已经说的很清楚了,现在就是需要对所有层forward进行串起来,ssd.py 代码中的第10行-121行,sources中是x经过VGG4_3,VGG7,Conv8_2,Conv9_2,Conv10_2,Conv11_2得到的输出,再通过一个zip 将每组送入对应的loc,和conf层得到最后的loc和conf预测值,loc的维度(batch, 8732,4),conf的维度(batch,8732,num_classes),priors的维度(8732,4)。
下一篇我会继续讲解默认框的生成,也就是priors,先验框的匹配策略。