源码文件
在 Faster R-CNN 中, 首次提出了 RPN 网络, 该网络用于生成目标检测任务所需要候选区域框, 在 MaskrcnnBenchmark 中, 关于 RPN 网络的定义位于 ./maskrcnn_benchmark/modeling/rpn/
文件夹中, 该文件夹包含以下四个文件:
在 class GeneralizedRCNN(nn.Module)
类中, 会通过 self.rpn = build_rpn(cfg)
函数来创建 RPN 网络, 该函数位于 ./maskrcnn_benchmark/modeling/rpn/rpn.py
文件中, 下面我们就先来看看该文件的内部实现.
rpn.py 区域候选框网络
在 rpn.py
文件中的最后, 定义了 build_fpn()
函数, 如下所示:
1 | def build_fpn(cfg): |
可以看出, 构建 RPN 网络的核心定义在 class RPNModule
中, 该类的定义如下所示:
1 | # ./maskrcnn_benchmark/modeling/rpn/rpn.py |
在 class RPNModule
中, 使用了 class RPNHead
作为其头部, 下面我们就来看一下该类的定义及实现:
1 | # ../maskrcnn_benchmark/modeling/rpn/rpn.py |
在定义 RPNModule
时, 分别使用了 make_anchor_generator()
, make_rpn_postprocessor()
和 make_rpn_loss_evaluator()
函数来构建模型的 anchor_generator
, box_selector
以及 loss_evaluator
, 这三个函数分别定义在其他的三个文件中, 下面我们就根据函数的调用顺序, 对这几个文件展开解析.
anchor_generator.py 生成 anchors
首先是 make_anchor_generator()
函数, 该函数定义在 rpn/
文件夹下的 anchor_generator.py
文件中, 由于该文件内容较多, 因此, 我们先看一下文件的整体概览, 了解一下文件中都包含了哪些类和函数.
文件概览
1 | # ./maskrcnn_benchmark/modeling/rpn/anchor_generator.py |
make_anchor_generator() 函数
由于外部文件往往通过 make_anchor_generator(config)
函数来获取对应的 anchors, 因此, 我们就从这个函数入手解析, 代码如下所示:
1 | def make_anchor_generator(config): |
AnchorGenerator 类
根据上面的函数我们知道, make_anchor_generator(config)
函数会根据对应的配置文件创建一个 AnchorGenerator 的实例, 因此, 我们下面就对 class AnchorGenerator(nn.Module)
类进行解析, 代码如下:
1 | # ./maskrcnn_benchmark/modeling/rpn/anchor_generator.py |
根据参数生成 anchors
在 class AnchorGenerator
中, 利用了 generate_anchors()
函数来生成对应的 anchors, 该函数是生成 anchors 的入口函数, 在生成 anchors 时, 需要进行一些计算和转换, 其大致流程和对应的实现函数如下所示
- 获取生成 anchors 必要的参数, 包括:
stride
,sizes
, 和aspect_ratios
, 其中,stride
代表特征图谱上的 anchors 的基础尺寸,sizes
代表 anchor 对应在原始图片中的大小(以像素为单位), 因此, 我们容易知道 anchor 在特征图谱上的放缩比例为sizes/stride
,aspect_ratios
代表 anchors 的高宽比, 于是, 最终返回的 anchors 的数量就是sizes
(在特征图谱上固定 base_window 的尺寸, 根据比例的不同来对应不同大小的物体)的数量和aspect_ratios
数量的乘积; - 在获取特征图谱上对应的 base_size(stride)后, 我们将其表示成
[x1, y1, x2, y2]
(坐标是相对于 anchor 的中心而言的) 的 box 形式. 例如对于stride=4
的情况, 我们将其表示成[0, 0, 3, 3]
, 此部分的实现位于_generate_anchors(...)
函数中 然后根据
aspect_ratios
的值来获取不同的 anchor boxes 的尺寸, 例如, 对于stride=4
的 base_anchor 来说, 如果参数aspect_ratios
为[0.5, 1.0, 2.0]
, 那么它就应该返回面积不变, 但是高宽比分别为[0.5, 1.0, 2.0]
的三个 box 的坐标, 也就是应该返回下面的 box 数组(注意到这里 box 的比例实际上是[5/2, 1, 2/5]
, 并不是绝对符合aspect_ratios
, 这是因为像素点只能为整数, 后面还能对这些坐标取整). 这部分的实现位于_ratio_enum()
函数中;1
2
3[[-1. 0.5 4. 2.5]
[ 0. 0. 3. 3. ]
[ 0.5 -1. 2.5 4. ]]在获取到不同比例的特征图谱上的 box 坐标以后, 我们就该利用
scales = sizes/stride
来将这些 box 坐标映射到原始图像中, 也就是按照对应的比例将这些 box 放大, 对于我们刚刚举的例子scales = 32/4 = 8
来说, 最终的 box 的坐标如下所示. 这部分的代码实现位于_scale_num()
函数中.1
2
3[[-22., -10., 25., 13.],
[-14., -14., 17., 17.],
[-10., -22., 13., 25.]]
代码解析如下:
1 | # ./maskrcnn_benchmark/modeling/rpn/anchor_generator.py |
在上面的函数上, 分别使用了 _ratio_enum()
和 _scale_enum()
函数来实现高宽比和放缩比的组合, 下面, 我们就先对这两个函数进行解析:
1 | ./maskrcnn_benchmark/modeling/rpn/anchor_generator.py |
接下来看看对放缩比进行遍历的函数 _scale_enum()
的代码实现1
2
3
4
5
6
7
8
9
10
11
12
13def _scale_enum(anchor, scales):
# anchor: [-1. 0.5 4. 2.5] (举例)
# scales: 8
# 获取 anchor 的宽, 高, 以及中心坐标
w, h, x_ctr, y_ctr = _whctrs(anchor)
# 将宽和高各放大8倍
ws = w * scales
hs = h * scales
# 根据新的宽, 高, 中心坐标, 将 anchor 转化成 (x1, x2, y1, y2) 的形式
return anchors
在 _ratio_enum()
和 _scale_enum()
函数中, 都使用了 _whctrs()
和 _mkanchors
函数, 前者可以根据 box 的坐标信息得到 box 的宽高以及中心点坐标, 后者则是根据宽高以及中心点坐标得到 box 的 (x1, y1, x2, y2) 形式, 这两个函数的代码解析如下所示: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./maskrcnn_benchmark/modeling/rpn/anchor_generator.py
def _whctrs(anchor):
# 根据左上角和右下角坐标返回该 box 的宽高以及中心点坐标
w = anchor[2] - anchor[0] + 1
h = anchor[3] - anchor[1] + 1
x_ctr = anchor[0] + 0.5 * (w - 1)
y_ctr = anchor[1] + 0.5 * (h - 1)
return w, h, x_ctr, y_ctr
def _mkanchors(ws, hs, x_ctr, y_ctr):
# 将给定的宽, 高以及中心点坐标转化成 (x1, y1, x2, y2) 的坐标形式
# 这里新增加了一个维度, 以便可以是有 hstack 将结果叠加.
ws = ws[:, np.newaxis]
hs = hs[:, np.newaxis]
# 将结果组合起来并返回
anchors = np.hstack(
(
x_ctr - 0.5 * (ws - 1),
y_ctr - 0.5 * (hs - 1),
x_ctr + 0.5 * (ws - 1),
y_ctr + 0.5 * (hs - 1),
)
)
return anchors
经过以上步骤, 最终的 anchors 会被作为初始化参数来实例化一个 class BufferList(nn.Module)
对象, 这一部分的详细解析可以翻到上面的关于 class AnchorGenerator(nn.Module)
的解析.
inference.py 文件解析
在 class RPNModule(torch.nn.Module)
中, 使用了下面的语句来分别创建训练时和测试时的 box selector:1
2
3
4rpn_box_coder = BoxCoder(weights=(1.0, 1.0, 1.0, 1.0))
box_selector_train = make_rpn_postprocessor(cfg, rpn_box_coder, is_train=True)
box_selector_test = make_rpn_postprocessor(cfg, rpn_box_coder, is_train=False)
以 box_selector_train
为例, 该方法用于在 end-to-end 的模型中, 返回训练用的 boxes, 调用形式如下:1
2
3boxes = self.box_selector_train(
anchors, objectness, rpn_box_regression, targets
)
由于 make_rpn_postprocessor()
函数位于 inference.py
文件中, 因此, 我们将在这一小节对该文件进行解析.(关于 BoxCoder
的解析请看模型定义-其他辅助文件解析)
inference.py 文件概览
./maskrcnn_benchmark/modeling/rpn/inference.py
文件概览如下:
1 | # ./maskrcnn_benchmark/modeling/rpn/inference.py |
make_rpn_postprocessor() 入口函数
在 rpn.py
中使用了 make_rpn_postprocessor()
函数来创建 class RPNPostProcessor(nn.Module)
实例, 该函数的第二个参数是一个 class BoxCoder(object)
的实例, 关于该类的解析请看模型定义-其他辅助文件解析).make_rpn_postprocessor()
函数解析如下:
1 | # ./maskrcnn_benchmark/modeling/rpn/rpn.py |
可以看到, 上面函数的主要功能就是根据配置文件的信息创建一个 RPNPostProcessor 的实例对象, 下面我们来看看这个类的定义.
RPNPostProcessor 类
RPNPostProcessor 类中的 proposals 使用了 BoxList
数据结构, 关于该结构的定义可以看BoxList结构解析. 另外, 还使用了三个函数: cat_boxlist()
, boxlist_nms()
, 以及 remove_small_boxes()
, 关于它们的详细解析请看BoxList Ops 解析
RPNPostProcessor 类的代码解析如下:
初始化函数
1 | # ./maskrcnn_benchmark/modeling/rpn/inference.py |
添加真实候选框函数
1 | # ./maskrcnn_benchmark/modeling/rpn/inference.py |
在单一的特征图谱上执行前向传播
1 | # ./maskrcnn_benchmark/modeling/rpn/inference.py |
前向传播函数
1 | # ./maskrcnn_benchmark/modeling/rpn/inference.py |
在所有层次上进行选择
1 | # ./maskrcnn_benchmark/modeling/rpn/inference.py |
loss.py
在 rpn.py
中, 使用了下面的语句来创建损失函数评价器:1
2
3
4
5# ./maskrcnn_benchmark/modeling/rpn/rpn.py
from .loss import make_rpn_loss_evaluator
loss_evaluator = make_rpn_loss_evaluator(cfg, rpn_box_coder)
可以看出, 该语句使用了 ./rpn/loss.py
文件中的 make_rpn_loss_evaluator()
函数来创建 RPN 网络的损失函数评价器, 下面我们就来看看该函数的代码实现是怎样的, 代码解析如下.
1 | # ./maskrcnn_benchmark/modeling/rpn/loss.py |
从上面的代码我们可以看出, 在 make_rpn_loss_evaluator()
函数中, 创建了 ./modeling/matcher.py
中的 Matcher
实例, 同时还创建了 ./modeling/balanced_positive_negative_sampler.py
文件中的 BalancedPositiveNegativeSampler
, 最后, 利用这两个实例创建了本文件中定义的 RPNLossComputation
实例, 前两个类的解析我们已经介绍过, 下来, 我们就来详细介绍一下本文件的 RPNLossComputation
类的代码实现
1 | # ./maskrcnn_benchmark/modeling/loss.py |