我们知道, FasterRCNN 作为目标检测任务的一个标志性的检测模型, 在目标检测领域具有十分广泛的应用, 其模型原理主要包含以下几个重要的组成部分:
- BackBone: VGG, ResNet等
- RoIPool: 感兴趣区域池化层
- RPN: 候选框区域推荐网络, FasterRCNN最主要的贡献点
接下来, 我们就按照上面的模块划分, 介绍一下 FasterRCNN 的具体实现(源码地址: https://github.com/jwyang/faster-rcnn.pytorch).
RPN
RPN 网络是在 FasterRCNN 中提出来的, 也算是 FasterRCNN 的核心所在.
rpn.py 文件:
该文件定义了 RPN 的网络结构.
由于 FasterRCNN 模型的突出贡献在于提出了 RPN 网络, 并且要实现 FasterRCNN 模型, 首先就需要实现 RPN 网络结构, 因此, 我们先来看一下如何利用 PyTorch 实现 RPN 的网络结构.
init 函数
1 | # ./lib/model/rpn/rpn.py |
在 RPN 网络类的初始化函数中, 可以看出, 除了定义预测预测分类和box坐标的两个卷积层外, 最关键的两行代码分别来自于 _ProposalLayer
和 _AnchorTargetLayer
这两个类, 前者定义在proposal_layer.py
文件中, 后者定义在anchor_target_layer.py
文件中. 因此, 在继续分析 RPN 网络的其他函数之前, 我建议你先看看这两个类的内部实现(点击名字直接跳转).
reshape 函数
将指定 tensor 的维度改变.1
2
3
4
5
6
7
8
9
10
11# ./lib/model/rpn/rpn.py
def reshape(x, d):
input_shape = x.size()
x = x.view(
input_shape[0], # batch 不变
int(d),
int(float(input_shape[1]*input_shape[2]) / float(d)),
input_shape[3] # ?
)
return x
forward 函数
1 | # ./lib/model/rpn/rpn.py |
proposal_layer.py 文件
该文件中定义了类 _ProposalLayer
, 其功能主要是根据规则的 anchor box 生成对应的 proposal box
init() 函数
1 | # ./lib/model/rpn/proposal_layer.py |
在上面的初始化函数中, 我们可以看到, 调用了 generate_anchors
(位于文件 generate_anchor.py
中)函数来生成标准的box (anchor box).
forward() 函数
接下来, 我们具体看一下该类的 foraward() 函数的实现方法, 其实现过程体现了 RoI 的生成方式.
1 | # ./lib/model/rpn/proposal_layer.py |
其他函数
1 | # ./lib/model/rpn/proposal_layer.py |
1 | # ./lib/model/rpn/proposal_layer.py |
1 | # ./lib/model/rpn/proposal_layer.py |
generate_anchors.py 文件
该文件的功能主要用于生成规则的 anchor box.
在 class _ProposalLayer
的初始化函数中, 我们可以看到, 调用了 generate_anchors()
函数来生成标准的box (anchor box), 该函数的具体实现如下:
1 | # ./lib/model/rpn/generate_anchors.py |
whctrs 函数
1 | # ./lib/model/rpn/generate_anchors.py |
mkanchors 函数
1 | # ./lib/model/rpn/generate_anchors.py |
ratio_enum 函数
相对于每一个anchor, 遍历其所有可能ratios对应的anchors1
2
3
4
5
6
7
8
9# ./lib/model/rpn/generate_anchors.py
def _ratio_enum(anchor, ratios):
w, h, x_ctr, y_ctr = whctrs(anchor) # 返回一个anchor的宽, 高, 以及中心点的(x,y)坐标值
size = w * h
size_ratios = size / ratios
ws = np.round(np.sqrt(size_ratios))
hs = np.round(ws * ratios)
anchors = _mkanchors(ws, hs, x_ctr, y_ctr)
return anchors
scale_enum() 函数
1 | # ./lib/model/rpn/generate_anchors.py |
anchor_target_layer.py 文件
该文件中定义了class _AnchorTargetLayer(nn.Module)
, 可以将 anchors 和 groun-truth 匹配起来, 然后生成相应的分类标签和bounding-box的回归targets.
init() 函数
1 | # ./lib/model/rpn/anchor_target_layer.py/class _AnchorTargetLayer(): |
forward() 函数
1 | # ./lib/model/rpn/anchor_target_layer.py/class _AnchorTargetLayer(): |
backward() 函数
1 | # ./lib/model/rpn/anchor_target_layer.py/class _AnchorTargetLayer(): |
reshape() 函数
1 | # ./lib/model/rpn/anchor_target_layer.py/class _AnchorTargetLayer(): |
unmap() 函数
1 | # ./lib/model/rpn/anchor_target_layer.py 不是类内部的函数 |
compute_targets_batch() 函数
1 | # ./lib/model/rpn/anchor_target_layer.py 不是类内部的函数 |
bbox_transform.py 文件
bbox_transform() 函数
bbox_transform_batch() 函数
bbox_transform_inv() 函数
BackBone
FasterRCNN 采用的 backbone 主要有两种, 一种是经典简单的 VGG16 网络, 一种是提取能力更强的 ResNet网络, 接下来我们对这两个网络的实现进行代码说明.
VGG16
首先, 我们可以利用 PyTorch 的 torchvision.models
来载入 VGG16 模型(当然也可以自己实现, 不过这不在本文的讨论范围内), 从卷积核的size等信息可以看出, 这已经是优化过的 vgg16 网络, 在网络层参数设置上和原始的 vgg16 有略微不同, 但大体上结构是相似的, 如下所示:
1 | import torchvision |
可以看一下 vgg16 网络的内部结构(可以依照此结构来复现 vgg16 网络):1
print(vgg)
输出如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44VGG(
(features): Sequential(
(0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): ReLU(inplace)
(2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(3): ReLU(inplace)
(4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(6): ReLU(inplace)
(7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(8): ReLU(inplace)
(9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(11): ReLU(inplace)
(12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(13): ReLU(inplace)
(14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(15): ReLU(inplace)
(16): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(18): ReLU(inplace)
(19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(20): ReLU(inplace)
(21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(22): ReLU(inplace)
(23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(25): ReLU(inplace)
(26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(27): ReLU(inplace)
(28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(29): ReLU(inplace)
(30): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(classifier): Sequential(
(0): Linear(in_features=25088, out_features=4096, bias=True)
(1): ReLU(inplace)
(2): Dropout(p=0.5)
(3): Linear(in_features=4096, out_features=4096, bias=True)
(4): ReLU(inplace)
(5): Dropout(p=0.5)
(6): Linear(in_features=4096, out_features=1000, bias=True)
)
)
ResNet
ResNet 的结构稍微复杂一些. 这里就不再贴出了, 不过和 VGGNet 相同, 都是利用 torchvision.mdoels
模块来导入的.
Faster RCNN 模型结构
在了解了以上两种模型骨架之后, 我们首先创建 Faster RCNN 的整个结构(包含 RoIPool 和 RPN, 不过, 这里只是先用作占位, 具体实现在后面).
1 | class _fasterRCNN(nn.Module): # 以单下划线开头, 表明为内部函数 |