基于PyQt5的SSD目标检测软件(二)
SSD 目标检测网络详解
一、前言
这次SSD目标检测网络是基于pytorch版本的并且我的代码中去掉了cuda加速(部分代码残留没有删除),原因很简单,因为我得AMD不YES了,等有机会用intel的显卡再说吧,b站up主的原版代码有加速部分可以参考增加,不过仅使用cpu硬算这样效率确实降低了很多,有待以后更改。
二、SSD是什么?
目标检测近年来已经取得了很多重要的发展,那么主流的算法也主要分为两个部分:
- Two-stage方法,如R-CNN系列算法,其主要思路是先通过启发式方法( selective search ) 或者CNN网络(RPN)产生一系列稀疏的候选框,然后对这些候选框进行分类与回归,因为中间使用了一些图像分割方法判别特征后进行候选框的生成,所以two-stage方法的优势是准确度高;
- One-stage方法,如Yolo 和SSD , YOLO在卷积层后接全连接层,即检测时只利用了最高层Feature maps(包括Faster RCNN也是如此),SSD主要思路是对图片进行均匀的分割如38*38而后对每一块区域进行抽样,抽样时可以采用不同尺度和长宽比, 然后利用CNN提取特征后同时进行分类与回归,整个过程只需要一步,所以其优势是速度快,但是均匀的密集采样的一个重要缺点是训练比较困难,这主要是因为正样本与负样本(背景)极其不均衡,导致模型准确度稍低。
SSD全称为 Single Shot MultiBox Detector,Single shot说明SSD算法属于one-stage方法,MultiBox说明SSD算法基于多框预测。 SSD是Wei Liu在ECCV 2016上提出的一种目标检测算法, 截至目前是主要的检测框架之一,相比Faster RCNN有明显的速度优势,相比YOLO又有明显的mAP优势(不过已经被CVPR 2017的YOLO9000超越)。
三、代码实现及原理介绍
1.预测部分
1.1 主干网络原理介绍
SSD网络模型是由VGG16网络修改而来,而VGG是由Simonyan和Ziss而man在文献《Very Deep Convolutional Networks for Large Scale Image Recognition》提出的卷积神经网络模型,其名称来源于作者所在的牛津大学视觉几何组( Visual Geometry Group )的缩写。
VGG16的原理图如下所示:
1、一张原始图片被resize到(224,224,3)。
2、conv1两次[3,3]卷积网络,输出的特征层为64,输出为(224,224,64),再2X2最大池化,输出net为(112,112,64)。
3、conv2两次[3,3]卷积网络,输出的特征层为128,输出net为(112,112,128),再2X2最大池化,输出net为(56,56,128)。
4、conv3三次[3,3]卷积网络,输出的特征层为256,输出net为(56,56,256),再2X2最大池化,输出net为(28,28,256)。
5、conv4三次[3,3]卷积网络,输出的特征层为512,输出net为(28,28,512),再2X2最大池化,输出net为(14,14,512)。
6、conv5三次[3,3]卷积网络,输出的特征层为512,输出net为(14,14,512),再2X2最大池化,输出net为(7,7,512)。
7、利用卷积的方式模拟全连接层,效果等同,输出net为(1,1,4096)。共进行两次。
8、利用卷积的方式模拟全连接层,效果等同,输出net为(1,1,1000)。
最后输出的就是每个类的预测。
SSD网络模型图
其中不同的地方在于:
- 将VGG16的FC6和FC7层转化为卷积层
- 去掉所有的Dropout层和FC8层
- 新增了Conv6、Conv7、Conv8、Conv9层
1.2 SSD_VGG(前)
如图所示,当图片输入进网络模型时,首先经过VGG16网络(Conv1-FC7)然后进行特征提取进行分类和回归预测,紧接着进入增加的几个卷积层(Conv6-Conv9),每经过一层便进行一次特征提取进行分类和回归预测:
a、输入一张图片后,被Resize到300x300x3的shape
b、Conv1,经过两次 [3,3] 卷积,输出的特征层为64,输出为(300,300,64),再2X2最大池化,输出net为(150,150,64)。
c、Conv2,经过两次 [3,3] 卷积,输出的特征层为128,输出net为(150,150,128),再2X2最大池化,输出net为(75,75,128)。
d、Conv3,经过三次 [3,3] 卷积,输出的特征层为256,输出net为(75,75,256),再2X2最大池化,输出net为(38,38,256)。
e、Conv4,经过三次 [3,3] 卷积,输出的特征层为512,输出net为(38,38,512),再2X2最大池化,输出net为(19,19,512)。
f、Conv5,经过三次 [3,3] 卷积,输出的特征层为512,输出net为(19,19,512),再2X2最大池化,输出net为(19,19,512)。
g、利用卷积代替全连接层,进行了两次 [3,3] 卷积,输出的特征层为1024,因此输出的net为(19,19,1024)。(从这里往前都是VGG的结构)
代码实现:
1 | base = [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'C', 512, 512, 512, 'M', |
1.3 追加层(后)
h、Conv6,经过一次 [1,1] 卷积,调整通道数,一次步长为2的[3,3]卷积网络,输出的特征层为512,因此输出的net为(10,10,512)。
i、Conv7,经过一次 [1,1] 卷积,调整通道数,一次步长为2的[3,3]卷积网络,输出的特征层为256,因此输出的net为(5,5,256)。
j、Conv8,经过一次 [1,1] 卷积,调整通道数,一次padding为valid的[3,3]卷积网络,输出的特征层为256,因此输出的net为(3,3,256)。
k、Conv9,经过一次 [1,1] 卷积,调整通道数,一次padding为valid的[3,3]卷积网络,输出的特征层为256,因此输出的net为(1,1,256)。
代码实现:
1 | def add_extras(i, batch_norm=False): |
1.4.从特征获取预测结果
红框部分圈出SSD网络模型在预测时与yolo最大得差别就是:YOLO在卷积层后接全连接层,即检测时只利用了最高层Feature maps(包括Faster RCNN也是如此),而SSD是取 Conv4的第三次卷积(Conv4_3即第21层)的特征、FC7的特征、Conv6的第二次卷积(Conv6_2)的特征、Conv7的第二次卷积(Conv7_2)的特征、Conv8的第二次卷积(Conv8_2)的特征、Conv9的第二次卷积(Conv9_2)的特征,一共6个特征层,为了和普通特征层区分,我们称之为有效特征层,来获取预测结果。Relu激活函数和L2Norm并不影响特征。
对获取到的每一个有效特征层,我们分别对其进行一次num_priors x 4的卷积、一次num_priors x num_classes的卷积、并需要计算每一个有效特征层对应的先验框。而num_priors指的是该特征层所拥有的先验框数量。
其中:
num_priors x 4的卷积 用于预测 该特征层上 每一个网格点上 每一个先验框的变化情况。(为什么说是变化情况呢,这是因为ssd的预测结果需要结合先验框获得预测框,预测结果就是先验框的变化情况。)
num_priors x num_classes的卷积 用于预测 该特征层上 每一个网格点上 每一个预测框对应的种类。
每一个有效特征层对应的先验框对应着该特征层上 每一个网格点上 预先设定好的多个框。
所有的特征层对应的预测结果的shape如下:
1 | class SSD(nn.Module): |
1.5 预测结果得解码
我们通过对每一个特征层的处理,可以获得三个内容,分别是:
线路1: 经过一次卷积后,生成了 **[1, num_class*num_priorbox, layer_height, layer_width]**大小的feature用于softmax分类目标和非目标(其中num_class是目标类别,SSD300中num_class = 21,即20个类别+1个背景)
num_priors x num_classes的卷积 用于预测 该特征层上 每一个网格点上 每一个预测框对应的种类。
线路2 : 经过一次卷积后, 生成了**[1, 4*num_priorbox, layer_height, layer_width]**大小的feature用于bounding box regression(边界框回归 在训练中 微调边界框大小匹配真实框)(即每个点一组[dxmin,dymin,dxmax,dymax])
线路3: 生成了**[1, 2, 4*num_priorbox*layer_height*layer_width]**大小的prior box blob,其中2个channel分别存储prior box的4个点坐标(x1, y1, x2, y2)和对应的4个参数variance
num_priors x 4的卷积 用于预测 该特征层上 每一个网格点上 每一个先验框的变化情况。
每一个有效特征层所对应的先验框 是对应着该特征层上 每一个网格点上 预先设定好的多个框(如:Conv4_3->5776个)。
我们利用 num_priors x 4 的卷积 与 每一个有效特征层对应的先验框 匹配获得框的预测位置。
每一个有效特征层对应的先验框就是,如图所示的作用:
每一个有效特征层将整个图片分成与其长宽对应的网格,如conv4-3的特征层就是将整个图像分成38x38个网格;然后从每个网格中心建立多个先验框,如conv4-3的特征层就是建立了4个先验框;对于conv4-3的特征层来讲,整个图片被分成38x38个网格,每个网格中心对应4个先验框,一共包含了,38x38x4个,5776个先验框。
先验框虽然可以代表一定的框的位置信息与框的大小信息,但是其是有限的,无法表示任意情况,因此还需要调整,ssd利用num_priors x 4的卷积的结果对先验框进行调整。
num_priors x 4中的num_priors表示了这个网格点所包含的先验框数量,其中的4表示了x_offset、y_offset、h和w的调整情况。
x_offset与y_offset代表了真实框中心距离先验框中心的xy轴偏移情况。
h和w代表了真实框的宽与高相对于先验框的变化情况。
SSD解码过程就是将每个网格的中心点加上它对应的x_offset和y_offset,加完后的结果就是预测框的中心,然后再利用 先验框和h、w结合 计算出预测框的长和宽。这样就能得到整个预测框的位置了。
当然得到最终的预测结构后还要进行得分排序与非极大抑制筛选这一部分基本上是所有目标检测通用的部分。
- 取出每一类得分大于
Config["nms_thresh"]
的框和得分。 - 利用框的位置和得分进行非极大抑制(NMS)生成预测框。
代码实现:
1 | class Detect(Function): |
1.6 在原图上进行绘制
我们可以获得预测框在原图上的位置,而且这些预测框都是经过(NMS)筛选的。这些筛选后的框可以直接绘制在图片上,就可以获得结果了。