目标检测入门系列Task02-锚框和模型结构

一、锚框和先验框

1.1 先验框

1.1.1 设置不同尺度的先验框

通常,为了覆盖更多可能的情况,在图中的同一个位置,我们会设置几个不同尺度的先验框。这里所说的不同尺度,不单单指大小,还有长宽比,如下面的示意图所示:
在这里插入图片描述可以看到,通过设置不同的尺度的先验框,就有更高的概率出现对于目标物体有良好匹配度的先验框(体现为高IoU)。

1.1.2 先验框与特征图的对应

除了不同尺度,我们肯定要将先验框铺洒在图片中不同位置上面。

但是遍历原图每个像素,设置的先验框就太多了,完全没必要。一个224x224的图片,假设每个位置设置3个不同尺寸的先验框,那么就有224x224x3=150528个,但是如果我们不去遍历原图,而是去遍历原图下采样得到的feature map呢?以vgg16的backbone为例,下采样了5次,得到7x7的feature map,那就只需要得到7x7x3=147个先验,这样的设置大大减少了先验框的数量,同时也能覆盖大多数情况。

在这里插入图片描述因此,我们就将先验框的设置位置与特征图建立一一对应的关系。而且,通过建立这种映射关系,我们可以通过特征图,直接一次性的输出所有先验框的类别信息以及坐标信息。

1.1.3 先验框类别信息的确定

我们铺设了很多的先验框,我们先要给出这些先验框的类别信息,才能让模型学着去预测每个先验框是否对应着一个目标物体。

这些先验框中有很多是和图片中我们要检测的目标完全没有交集或者有很小的交集,

我们的做法是,设定一个IoU阈值,例如iou=0.5,与图片中目标的iou<0.5的先验框,这些框我们将其划分为背景,Iou>=0.5的被归到目标先验框,通过这样划分,得到供模型学习的ground truth信息,如图所示:
在这里插入图片描述

1.2 先验框的生成

此处结合代码进行解释

  • 设置细节介绍:

    • 离散程度 fmap_dims = 7: VGG16最后的特征图尺寸为 7*7
    • 在上面的举例中我们是假设了三种尺寸的先验框,然后遍历坐标。在先验框生成过程中,先验框的尺寸是提前设置好的,
      本教程为特征图上每一个cell定义了共9种不同大小和形状的候选框(3种尺度*3种长宽比=9)
  • 生成过程:

    • cx, cy表示中心点坐标
    • 遍历特征图上每一个cell,i+0.5是为了从坐标点移动至cell中心,/fmap_dims目的是将坐标在特征图上归一化
    • 这个时候我们已经可以在每个cell上各生成一个框了,但是这个不是我们需要的,我们称之为base_prior_bbox基准框。
    • 根据我们在每个cell上得到的长宽比1:1的基准框,结合我们设置的3种尺度obj_scales和3种长宽比aspect_ratios就得到了每个cell的9个先验框。
    • 最终结果保存在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

根据上面的代码,我们得到了先验框,那么接下来进行一下可视化吧,为了便于观看,仅展示特征图中间那个cell对应的先验框。

这里为了对比,我们设置两组obj_scales尺度参数。

  1. obj_scales = [0.1, 0.2, 0.3]

这里的参数是归一化的,0.1代表anchor的基准大小为原图长/宽的0.1那么大。
在这里插入图片描述可以看到,我们在图片中心得到了各个尺度和宽高比的先验框。

  1. obj_scales = [0.2, 0.4, 0.6]

在这里插入图片描述这里对比两组不同的尺度设置,是想展示一个需要注意的小问题,那就是越界,可以看到第二组可视化部分蓝色和绿色的先验框都超出图片界限了,这种情况其实是非常容易出现的,越靠近四周的位置的先验框越容易越界,那么这个问题怎么处理呢?这里我们一般用图片尺寸将越界的先验框进行截断,比如某个先验框左上角坐标是(-5, -9),那么就截断为(0,0),某个先验框右下角坐标是(324,134),当我们的图片大小为(224,224)时,就将其截断为(224,134)。

对应于代码中是这行,prior_boxes.clamp_(0, 1),由于进行了归一化,所以使用0-1进行截断。

二、模型结构

本章教程所介绍的网络,后面我们称其为Tiny_Detector,代码是由一个外国的开源SSD教程改编而来,因此很多细节上也更接近SSD网络,可以认为是一个简化后的版本。

2.1 VGG16作为backbone

完全采用vgg16的结构作为特征提取模块,只是去掉fc6和fc7两个全连接层
在这里插入图片描述对于网络的输入尺寸的确定,由于vgg16的ImageNet预训练模型是使用224x224尺寸训练的,因此我们的网络输入也固定为224x224,和预训练模型尺度保持一致可以更好的发挥其作用。通常来说,这样的网络输入大小,对于检测网络来说还是偏小,在完整的进行完本章的学习后,不妨尝试下将输入尺度扩大,看看会不会带来更好的效果。

我们的Tiny_Detector特征提取层输出的是7x7的feature map,下面我们要在feature_map上设置对应的先验框,或者说anchor。

在本实验中,anchor的配置如下:

  • 将原图均匀分成7x7个cell
  • 设置3种不同的尺度:0.2, 0.4, 0.6
  • 设置3种不同的长宽比:1:1, 1:2, 2:1

因此,我们对这 7x7 的 feature map 设置了对应的7x7x9个anchor框,其中每一个cell有9个anchor框:
在这里插入图片描述对于每个anchor,我们需要预测两类信息,一个是这个anchor的类别信息,一个是物体的边界框信息:
在这里插入图片描述在我们的实验中,类别信息由21类别的得分组成(VOC数据集的20个类别 + 一个背景类),模型最终会选择预测得分最高的类作为边界框对象的类别。

而边界框信息是指,我们大致知道了当前anchor中包含一个物体的情况下,如何对anchor进行微调,使得最终能够准确预测出物体的bbox。

这两种预测我们分别成为分类头回归头,那么分类头预测和回归头预测是怎么得到的?

其实我们只需在7x7的feature map后,接上两个3x3的卷积层,即可分别完成分类和回归的预测。

下面我们就对分类头和回归头的更多细节进行介绍。

2.2 分类头和回归头

2.2.1 边界框的编解码

Tiny_Detector并不是直接预测目标框,而是回归对于anchor要进行多大程度的调整,才能更准确的预测出边界框的位置。那么我们的目标就是需要找一种方法来量化计算这个偏差。

对于一只狗的目标边界框和先验框的示例如下图所示:
在这里插入图片描述
我们的模型要预测anchor与目标框的偏移,并且这个偏移会进行某种形式的归一化,这个过程我们称为边界框的编码。

这里我们使用的是与SSD完全一致的编码方法,具体公示表达如下:(对应锚框的坐标(cx,cy,w,h)来看

g c x ​ = c x ​ − c ^ x w ^ ​ ​ g_{cx}​=\frac{c_x​−\hat{c}_x}{\hat{w}}​​ gcx=w^cxc^x

g c y = c y − c ^ y h ^ ​ ​ g_{cy}=\frac{c_y−\hat{c}_y}{\hat{h}}​​ gcy=h^cyc^y

g w = l o g ( w w ^ ) g_w=log(\frac{w}{\hat{w}}) gw=log(w^w)

g h = l o g ( h h ^ ) g_h=log(\frac{h}{\hat{h}}) gh=log(h^h)

模型预测并输出的是这个编码后的偏移量 ( g c x , g c y , g w , g h ) (g_{cx},g_{cy},g_w,g_h) (gcx,gcy,gw,gh),最终只要再依照公式反向进行解码,就可以得到预测的目标框的信息。

2.2.2 分类头与回归头预测

按照前面的介绍,对于输出7x7的feature map上的每个先验框我们想预测:

1)边界框的一组21类分数,其中包括VOC的20类和一个背景类。

2)边界框编码后的偏移量 ( g c x , g c y , g w , g h ) (g_{cx},g_{cy},g_w,g_h) (gcx,gcy,gw,gh)

为了得到我们想预测的类别和偏移量,我们需要在feature map后分别接上两个卷积层:

1)一个分类预测的卷积层采用3x3卷积核padding和stride都为1,每个anchor需要分配21个卷积核,每个位置有9个anchor,因此需要21x9个卷积核。

2)一个定位预测卷积层,每个位置使用3x3卷积核padding和stride都为1,每个anchor需要分配4个卷积核,因此需要4x9个卷积核。这里,对于7*7的特征图,每个cell我们需要铺设9个不同大小形状的anchor,每个anchor我们预测4个值 ( g c x , g c y , g w , g h ) (g_{cx},g_{cy},g_w,g_h) (gcx,gcy,gw,gh),所以回归头的卷积层需要输出36个通道。

在这里插入图片描述这个回归头和分类头的输出分别用蓝色和黄色表示。其feature map的大小7x7保持不变。我们真正关心的是第三维度通道数,把其具体的展开可以看到:
在这里插入图片描述也就是说,最终回归头的输出有36个通道,其中每4个值就对应了一个anchor的编码后偏移量的预测,这样的4个值的预测共有9组,因此通道数是36。

分类头可以用同样的方式理解:
在这里插入图片描述
按照上面的介绍,我们的模型输出的shape应该为:

分类头 batch_size x 7 x 7 x 189
回归头 batch_size x 7 x 7 x 36

但是为了方便后面的处理,我们肯定更希望每个anchor的预测独自成一维,也就是:

分类头 batch_size x 441 x 21
回归头 batch_size x 441 x 4

441是因为我们的模型定义了总共441=7x7x9个先验框

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值