前面几节的网络骨架, 如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的特征图尺寸相同, 避免了上一节的上采样, 既一定程度上降低了计算量, 又有利于小物体的检测。

物理信息神经网络 参数辨识 物体检测神经网络_ide

 

中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, 并且实验发现这种做法对于特征金字塔式的检测非常重要
.

物理信息神经网络 参数辨识 物体检测神经网络_卷积_02

 

使用PyTorch来实现DetNet的两个Bottleneck结构A和B, 新建一个detnet_bott-leneck.py文件, 代码如下:

物理信息神经网络 参数辨识 物体检测神经网络_物理信息神经网络 参数辨识_03

物理信息神经网络 参数辨识 物体检测神经网络_2d_04

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

物理信息神经网络 参数辨识 物体检测神经网络_物理信息神经网络 参数辨识_03

物理信息神经网络 参数辨识 物体检测神经网络_2d_04

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