目标检测学习
文章目录
前言
Pytorch是一个基于python的科学计算包,主要面向两部分受众:
一个为了充分发挥GPU能力而设计的Numpy的GPU版本替代方案
一个提供更大灵活性和速度的深度学习研究平台
一、 锚框 or 先验框
1.1 关于先验框
在众多经典的目标检测模型中,均有先验框的说法,有的paper(如Faster RCNN)中称之为anchor(锚点),有的paper(如SSD)称之为prior bounding box(先验框),实际上是一个概念。
那么,为什么要有先验框这个概念呢?按理说我们的图片输入模型,模型给出检测结果就好了,为什么还要有先验框?那么关于它的作用,我们不妨回顾一下前面在2.1节所说的那个目标检测最初的解决方案,我们说,我们要遍历图片上每一个可能的目标框,再对这些框进行分类和微调,就可以完成目标检测任务。
你脑中目前很可能没有清晰的概念,因为这个描述很模糊,本节介绍的先验框就是在解决如何定义哪些位置是候选目标框的问题。
接下来需要介绍3个概念:
- 设置不同尺度的先验框
- 先验框与特征图的对应
- 先验框类别信息的确定
1.1 .1 设置不同尺度的先验框
通常,为了覆盖更多可能的情况,在图中的同一个位置,我们会设置几个不同尺度的先验框。这里所说的不同尺度,不单单指大小,还有长宽比,通过设置不同的尺度的先验框,就有更高的概率出现对于目标物体有良好匹配度的先验框。
1.1 .2 先验框与特征图的对应
除了不同尺度,我们肯定要将先验框铺洒在图片中不同位置上面。但是遍历原图每个像素,设置的先验框就太多了,完全没必要。一个224x224的图片,假设每个位置设置3个不同尺寸的先验框,那么就有224x224x3=150528个,但是如果我们不去遍历原图,而是去遍历原图下采样得到的feature map呢?以vgg16的backbone为例,下采样了5次,得到7x7的feature map,那就只需要得到7x7x3=147个先验,这样的设置大大减少了先验框的数量,同时也能覆盖大多数情况。
因此,我们就将先验框的设置位置与特征图建立一一对应的关系。而且,通过建立这种映射关系,我们可以通过特征图,直接一次性的输出所有先验框的类别信息以及坐标信息,而不是想前面一直描述的那样,每个候选框都去独立的进行一次分类的预测,这样太慢了(阅读后面的章节后,你将会深刻理解这段话的含义,以及建立这种一一映射的重要意义)。
1.1 .3 先验框的生成
这里,我们来结合代码介绍先验框是如何生成的,更加具体的先验框的使用以及一些训练技巧如先验框的筛选在后面的章节会进一步的介绍。
model.py 脚本下有一个 tiny_detector 类,是本章节介绍的目标检测网络的定义函数,其内部实现了一个 create_prior_boxes 函数,该函数便是用来生成先验框的。
"""
设置细节介绍:
1. 离散程度 fmap_dims = 7: VGG16最后的特征图尺寸为 7*7
2. 在上面的举例中我们是假设了三种尺寸的先验框,然后遍历坐标。在先验框生成过程中,先验框的尺寸是提前设置好的,
本教程为特征图上每一个cell定义了共9种不同大小和形状的候选框(3种尺度*3种长宽比=9)
生成过程:
0. cx, cy表示中心点坐标
1. 遍历特征图上每一个cell,i+0.5是为了从坐标点移动至cell中心,/fmap_dims目的是将坐标在特征图上归一化
2. 这个时候我们已经可以在每个cell上各生成一个框了,但是这个不是我们需要的,我们称之为base_prior_bbox基准框。
3. 根据我们在每个cell上得到的长宽比1:1的基准框,结合我们设置的3种尺度obj_scales和3种长宽比aspect_ratios就得到了每个cell的9个先验框。
4. 最终结果保存在prior_boxes中并返回。
需要注意的是,这个时候我们的到的先验框是针对特征图的尺寸并归一化的,因此要映射到原图计算IOU或者展示,需要:
img_prior_boxes = prior_boxes * 图像尺寸
"""
def create_prior_boxes():
"""
Create the 441 prior (default) boxes for the network, as described in the tutorial.
VGG16最后的特征图尺寸为 7*7
我们为特征图上每一个cell定义了共9种不同大小和形状的候选框(3种尺度*3种长宽比=9)
因此总的候选框个数 = 7 * 7 * 9 = 441
:return: prior boxes in center-size coordinates, a tensor of dimensions (441, 4)
"""
fmap_dims = 7
obj_scales = [0.2, 0.4, 0.6]
aspect_ratios = [1., 2., 0.5]
prior_boxes = []
for i in range(fmap_dims):
for j in range(fmap_dims):
cx = (j + 0.5) / fmap_dims
cy = (i + 0.5) / fmap_dims
for obj_scale in obj_scales:
for ratio in aspect_ratios:
prior_boxes.append([cx, cy, obj_scale * sqrt(ratio), obj_scale / sqrt(ratio)])
prior_boxes = torch.FloatTensor(prior_boxes).to(device) # (441, 4)
prior_boxes.clamp_(0, 1) # (441, 4)
return prior_boxes
二、模型结构
本章教程所介绍的网络,后面我们称其为Tiny_Detector,是为了本教程特意设计的网络,而并不是某个经典的目标检测网络。如果一定要溯源的话,由于代码是由一个外国的开源SSD教程改编而来,因此很多细节上也更接近SSD网络,可以认为是一个简化后的版本,目的是帮助大家更好的入门。
那么下面,我们就开始介绍Tiny_Detector的模型结构
2.1 VGG16作为backbone
为了使结构简单易懂,我们使用VGG16作为backbone,即完全采用vgg16的结构作为特征提取模块,只是去掉fc6和fc7两个全连接层,对于网络的输入尺寸的确定,由于vgg16的ImageNet预训练模型是使用224x224尺寸训练的,因此我们的网络输入也固定为224x224,和预训练模型尺度保持一致可以更好的发挥其作用。通常来说,这样的网络输入大小,对于检测网络来说还是偏小,在完整的进行完本章的学习后,不妨尝试下将输入尺度扩大,看看会不会带来更好的效果。
特征提取模块对应代码模块在model.py中的VGGBase类进行了定义:
class VGGBase(nn.Module):
"""
VGG base convolutions to produce feature maps.
完全采用vgg16的结构作为特征提取模块,丢掉fc6和fc7两个全连接层。
因为vgg16的ImageNet预训练模型是使用224×224尺寸训练的,因此我们的网络输入也固定为224×224
"""
def __init__(self):
super(VGGBase, self).__init__()
# Standard convolutional layers in VGG16
self.conv1_1 = nn.Conv2d(3, 64, kernel_size=3, padding=