前面几节的网络骨架, 如VGGNet和ResNet等, 虽从各个角度出发提升了物体检测性能, 但究其根本是为ImageNet的图像分类任务而设计的。 而图像分类与物体检测两个任务天然存在着落差, 分类任务侧重全图的特征提取, 深层的特征图分辨率很低; 而物体检测需要定位出物体位置, 特征图分辨率不宜过小, 因此造成了以下两种缺陷:
·大物体难以定位: 对于FPN等网络, 大物体对应在较深的特征图上检测, 由于网络较深时下采样率较大, 物体的边缘难以精确预测, 增加了回归边界的难度。
小物体难以检测: 对于传统网络, 由于下采样率大造成小物体在较深的特征图上几乎不可见; FPN虽从较浅的特征图来检测小物体, 但浅层的语义信息较弱, 且融合深层特征时使用的上采样操作也会增加物体检测的难度。
针对以上问题, 旷视科技提出了专为物体检测设计的DetNet结构,引入了空洞卷积, 使得模型兼具较大感受野与较高分辨率, 同时避免了3.6节中FPN的多次上采样, 实现了较好的检测效果。
DetNet的网络结构如图3.22所示, 仍然选择性能优越的ResNet-50作为基础结构, 并保持前4个stage与ResNet-50相同, 具体的结构细节有以下3点:
引入了一个新的Stage 6, 用于物体检测。 Stage 5与Stage 6使用了DetNet提出的Bottleneck结构, 最大的特点是利用空洞数为2的3×3卷积取代了步长为2的3×3卷积.
与Stage 6的每一个Bottleneck输出的特征图尺寸都为原图的, 通道数都为256, 而传统的Backbone通常是特征图尺寸递减, 通道数递增.
在组成特征金字塔时, 由于特征图大小完全相同, 因此可以直接从右向左传递相加, 避免了上一节的上采样操作。 为了进一步融合各通道的特征, 需要对每一个阶段的输出进行1×1卷积后再与后一Stage传回的特征相加。
DetNet这种精心设计的结构, 在增加感受野的同时, 获得了较大的特征图尺寸, 有利于物体的定位。 与此同时, 由于各Stage的特征图尺寸相同, 避免了上一节的上采样, 既一定程度上降低了计算量, 又有利于小物体的检测。
中Bottleneck的细节如图3.23所示, 左侧的两个Bottleneck A与Bottleneck B分别对应图3.22的A与B, 右侧的为原始的ResNet残差结构。 DetNet与ResNet两者的基本思想都是卷积堆叠层与恒等映射的相加, 区别在于DetNet使用了空洞数为2的3×3卷积, 这样使得特征图尺寸保持不变, 而ResNet是使用了步长为2的3×3卷积。 B相比于A, 在恒等映射部分增加了一个1×1卷积, 这样做可以区分开不同的Stage, 并且实验发现这种做法对于特征金字塔式的检测非常重要
.
使用PyTorch来实现DetNet的两个Bottleneck结构A和B, 新建一个detnet_bott-leneck.py文件, 代码如下:
1 import torch
2 from torch import nn
3
4 class DetBottleneck(nn.Module):
5
6 # 初始化时extra为False时为Bottleneck A, 为True时则为Bottleneck B
7 def __init__(self, inplaces, planes, stride=1, extra=False):
8
9 super(DetBottleneck, self).__init__()
10 # 构建连续3个卷积层的Bottleneck
11 self.bottleneck = nn.Sequential(
12 nn.Conv2d(inplaces, planes, 1, bias =False),
13 nn.BatchNorm2d(planes),
14 nn.ReLU(inplace=True),
15 nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=2, dilation=2, bias=False),
16 nn.BatchNorm2d(planes),
17 nn.ReLU(inplace=True),
18 nn.Conv2d(planes, planes, 1, bias=False),
19 nn.BatchNorm2d(planes)
20 )
21
22 self.relu = nn.ReLU(inplace=True)
23 self.extra = extra
24
25 # Bottleneck B的1×1卷积
26 if self.extra:
27 self.extra_conv = nn.Sequential(
28 nn.Conv2d(inplaces, planes, 1, bias=False),
29 nn.BatchNorm2d(planes)
30 )
31
32 def forward(self, x):
33 # 对于Bottleneck B来讲, 需要对恒等映射增加卷积处理, 与ResNet类似
34 if self.extra:
35 indentity = self.extra_conv(x)
36 else:
37 indentity = x
38 out = self.bottleneck(x)
39 out += indentity
40 out = self.relu(out)
41
42 return out
View Code
1 import torch
2 from detnet_bottleneck import DetBottleneck
3
4 bottleneck_b = DetBottleneck(1024, 256, 1, True).cuda()
5 print(bottleneck_b)
6 >> DetBottleneck(
7 (bottleneck): Sequential(
8 (0): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
9 (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
10 (2): ReLU(inplace=True)
11 (3): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(2, 2), dilation=(2, 2), bias=False)
12 (4): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
13 (5): ReLU(inplace=True)
14 (6): Conv2d(256, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
15 (7): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
16 )
17 (relu): ReLU(inplace=True)
18 (extra_conv): Sequential(
19 (0): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
20 (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
21 )
22 )
23
24 bottleneck_a1 = DetBottleneck(256,256).cuda()
25 print(bottleneck_a1)
26 >>
27 DetBottleneck(
28 (bottleneck): Sequential(
29 (0): Conv2d(256, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
30 (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
31 (2): ReLU(inplace=True)
32 (3): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(2, 2), dilation=(2, 2), bias=False)
33 (4): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
34 (5): ReLU(inplace=True)
35 (6): Conv2d(256, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
36 (7): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
37 )
38 (relu): ReLU(inplace=True)
39 )
40
41
42 bottleneck_a2 = DetBottleneck(256, 256).cuda()
43 print(bottleneck_a2)
44 >> DetBottleneck(
45 (bottleneck): Sequential(
46 (0): Conv2d(256, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
47 (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
48 (2): ReLU(inplace=True)
49 (3): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(2, 2), dilation=(2, 2), bias=False)
50 (4): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
51 (5): ReLU(inplace=True)
52 (6): Conv2d(256, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
53 (7): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
54 )
55 (relu): ReLU(inplace=True)
56 )
57
58 input = torch.randn(1, 1024, 14, 14).cuda()
59
60 # 将input作为某一层的特征图, 依次传入Bottleneck B、 A1与A2三个模块
61 output1 = bottleneck_b(input)
62 output2 = bottleneck_a1(output1)
63 output3 = bottleneck_a2(output2)
64
65 # 三个Bottleneck输出的特征图大小完全相同
66 print(output1.shape)
67 >> torch.Size([1, 256, 14, 14])
68 print(output2.shape)
69 >> torch.Size([1, 256, 14, 14])
70 print(output3.shape)
71 >> torch.Size([1, 256, 14, 14])
View Code