> For the complete documentation index, see [llms.txt](https://openmmlabbook.gitbook.io/openmmlab-book/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://openmmlabbook.gitbook.io/openmmlab-book/object-detection/two-stage/faster_rcnn.md).

# Faster R-CNN

Faster R-CNN 是一个非常主流的两阶段目标检测算法，它的出现深深的影响了目标检测的发展以及工业应用。 Faster R-CNN 叫做两阶段检测器，原因是其**包括一个区域提取网络 RPN 和 roi refine 网络 RCNN，同时为了将 RPN 提取的不同大小的 roi 特征图组成 batch 输入到后面的 rcnn 中，在两者中间还插入了一个 roipool 层，可以保证任意大小特征图输入都可以变成指定大小输出**。 所有 anchor-base 的 one stage 算法都可以认为只有第二级 RCNN 网络(其实要看成第一级的 RPN 网络也是可以的)，并且 RPN 提取的 roi 对应的就是常说的 anchor，从而将 two stage 中的 roi refine 网络变成了 one stage 中的 anchor refine 网络。

Faster R-CNN 简要流程是：首先采用二分类 RPN 网络提取大量前后景 roi 特征图，然后通过 roipool 变成统一大小，最后将 roi 特征图输入到 rcnn 中进行 roi 的 refine，得到优化后的 bbox。

## 1 算法分析

其整体核心结构如下：

![](http://static.zybuluo.com/huanghaian/o9ov57v9p6kcm2snpuwugw78/image.png)

详细一点的话，如下所示：

![](http://static.zybuluo.com/huanghaian/opxtefqx10vrqhehq2o1vkx8/image.png)

[图片来源](https://medium.com/@jonathan_hui/what-do-we-learn-from-region-based-object-detectors-faster-r-cnn-r-fcn-fpn-7e354377a7c9)

下面对每个部分进行深入分析。

### 1.1 backbone 骨架

这部分和 RPN 网络重合，请看 RPN 算法解读部分文章。

### 1.2 RPN 部分

RPN 层的作用是基于预设值 anchor 进行二分类前后景提取和 bbox 回归，主要目的是为 rcnn 层输入高质量的后续 bbbox，这部分内容请看 RPN 算法解读部分文章。

### 1.3 RCNN

RCNN 部分和 RPN 模块非常类似，只不过处理的问题不一样而已。整个 RCNN 部分的配置如下所示：

```python
roi_head=dict(
    type='StandardRoIHead',
    bbox_roi_extractor=dict(
        type='SingleRoIExtractor',
        roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=0),
        out_channels=256,
        featmap_strides=[4, 8, 16, 32]),
    bbox_head=dict(
        type='Shared2FCBBoxHead',
        in_channels=256,
        fc_out_channels=1024,
        roi_feat_size=7,
        num_classes=80,
        bbox_coder=dict(
            type='DeltaXYWHBBoxCoder',
            target_means=[0., 0., 0., 0.],
            target_stds=[0.1, 0.1, 0.2, 0.2]),
        reg_class_agnostic=False,
        loss_cls=dict(
            type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0),
        loss_bbox=dict(type='L1Loss', loss_weight=1.0))))
```

**(1) rpn 和 rcnn 中间数据转换流程**

```python
rpn_proposal=dict(
    nms_across_levels=False,
    nms_pre=12000,
    nms_post=2000,
    max_num=2000,
    nms_thr=0.7,
    min_bbox_size=0),
```

rcnn 需要的输入数据来自 rpn 预测 roi，故在运行 rcnn 模块前需要对 rpn 输出进行处理，配置如上，这个步骤其实就等价于 rpn 自身的前向推理流程。具体操作是可以看 RPN 算法解读的前向推理部分，其大概流程是：

* 遍历每个 RPN 输出层(其实就是 1 个)，对每一层的分类分值 scores 进行降序排列，保留 topk=nms\_pre 个样本，分值越大越可能是正样本
* 此时就可以收集到所有层的所有样本，先过滤掉不满足 min\_bbox\_size 预测结果，然后统一进行 nms 即可，最好保留最多前 nms\_post 个检测结果

此时假设可以得到(batch,2000,4)个候选 roi 了。

**(2) roipool 操作**

上述可以得到(batch,num,4)个候选 roi bbox，在特征图上面进行切割即可得到 batchxnum 个不同大小的 roi 特征图，但是这些 roi 特征图对于 rcnn 来说肯定不能是不同大小的，否则无法组成 batch 进行训练，故需要 roi 提取器，其有两个功能：**利用 roi 坐标在特征图上面切割出对应区域特征；然后将该区域特征变成指定固定大小输出。该模块称为 roipool**，该操作非常简单，通过下面的可视化图即可理解：

![](http://static.zybuluo.com/huanghaian/9tb7f0e9kym4zyut4vkqp0rg/image.png)

假设左上是输入特征图，右上的蓝色框是 roi，已经取整了。假设输出 shape 为 2x2 的统一大小，则左下是切割的示意图，实际上就是把 5x7 个块切割为 2x2 的块，第一个块是 wh=(7//2,5//2)，第二个块是 wh=(7-7//2,5-5//2),后面类似。最后在每个小块内部进行 max 操作即可。可以明显 roi 发现其存在两次非常严重的取整操作，第一次是将 roi proposal 值变成整数；第二次是切割时候。可想而知对于本身就是小物体的特征图而言，两次取整操作特征图会偏差很大，故在后续 mask rcnn 中提出了 roiAlign 操作。原论文中设置的指定输出大小是 7x7。

**(3) RCNN 网络结构**

将 roipool 操作得到的(batch,num,256,7,7)个 roi 特征图作为 RCNN 模块的输入即可。RCNN 的 head 部分是 Shared2FCBBoxHead，假设 RPN 阶段输出了 1000 个候选 bbox，并且经过了 roipool 层统一变成了(batchx1000,256,7,7)，那么 Shared2FCBBoxHead 的意思是先把(batchx1000,256,7,7)变成(batchx1000,256x7x7),再经过 2 次 fc 层，然后在分成两个 fc 分支用于分类和 roi refine 回归，前两个 fc 层是共享权重的。**分类分支 shape=(batchx1000,cls\_num+1)，回归分支 shape=(batchx1000,4\*num\_class)(因为参数 reg\_class\_agnostic=False)**

这里有一个细节需要强调下：在原始 faster rcnn 中 RCNN 网络还包括了 resnet 的最后一个 stage，也就是说 resnet 的 stage0-stage2 做为公共的基础网络部分，而 stage3 是在 RCNN 部分。**为何作者将 stage3 作为 fast RCNN 独有网络，不作为基础网络？**&#x4F5C;者认为把 stage3 作为基础网络的话，网络会偏向于分类问题，并且 imageNet 预训练权重的加载更会加深特征的平移不变性，不利于 bbox 回归。但是由于考虑到代码通用性以及后续 FPN 模块的引入，现在的 faster rcnn 都没有这样做。

**(4) 正负样本定义和采样**

首先解释下 rcnn 层的输入参数：

```python
def forward_train(self,
                  x,
                  img_metas,
                  proposal_list,
                  gt_bboxes,
                  gt_labels,
                  gt_bboxes_ignore=None,
                  gt_masks=None):
```

x 是 backbone 输出的 stride=16 的特征图，img\_metas 是每张图片各自的属性，proposal\_list 就是 RPN 输出的 roi，假设是(batch,1000,4)，后面几个都是关于 gt bbox 相关的数据。

RCNN 算法要完成的任务是：**基于 RPN 输出的 proposal\_list 来回归出更好的 bbox**。此时可以发现 RCNN 算法其实和 RPN 思想非常类似，只不过 RPN 中学习目标是 anchor 和 gt bbox 的变换值，而 RCNN 模块可以认为 proposal\_list 是稀疏的 anchor，其学习目标是 proposal\_list 和 gt bbox 的变换值。

既然 proposal\_list 可以认为是 anchor，那么正负样本定义和采样策略就不可少了

```python
rcnn=dict(
    assigner=dict(
        type='MaxIoUAssigner',
        pos_iou_thr=0.5,
        neg_iou_thr=0.5,
        min_pos_iou=0.5,
        match_low_quality=False,
        ignore_iof_thr=-1),
    sampler=dict(
        type='RandomSampler',
        num=512,
        pos_fraction=0.25,
        neg_pos_ub=-1,
        add_gt_as_proposals=True),
    pos_weight=-1,
    debug=False)
```

其代码和 RPN 中的完全一样，只不过阈值不一样而已。其主要区别是：

* 由于 rcnn head 预测值是 rpn head 输出 roi 的 refine，故 rcnn head 面对的 anchor 和 gt bbox 的 iou 会高于 rpn head 部分，anchor 质量更高，neg\_iou\_thr 阈值设置的就可以高一些
* 由于 pos\_iou\_thr 和 neg\_iou\_thr 设置都是 0.5，那么忽略区域就没有了，因为 rcnn head 面对的都是高质量样本，可以不需要忽略区域
* 由于 rcnn 在网络训练前期，rpn 无法输出大量高质量样本，故为了平衡和稳定 rcnn 训练过程，通常会对 rcnn head 部分添加 gt bbox 作为 proposal
* rcnn 部分 match\_low\_quality=False，原因是 rcnn 面对的正样本都是比较高质量的，没必要 match\_low\_quality 设置为 True，因为其可能会导致低质量 anchor 匹配

通过正负样本定义和随机采样策略就可以对每个 proposal 确定其正负样本属性。

**(5) loss 计算**

rcnn 部分的 bbox 编解码流程和 RPN 完全相同，就不再描述了。假设得到(batch,1000,256x7x7)的矩阵，后面经过 rcnn 的 fc head 即可进行分类和回归了，其中分类采用的是 ce loss，回归采用的是 L1Loss。

### 1.4 推理流程

```python
test_cfg = dict(
    rpn=dict(
        nms_across_levels=False,
        nms_pre=6000,
        nms_post=1000,
        max_num=1000,
        nms_thr=0.7,
        min_bbox_size=0),
    rcnn=dict(
        score_thr=0.05,
        nms=dict(type='nms', iou_threshold=0.5),
        max_per_img=100))
```

推理流程为：

1. 对于单张图片输入到 resnet 中，输出 1 个特征图 stride=16
2. 对输出图经过 rpn head，分别预测前后景和 bbox 坐标
3. 采用 rpn 测试配置对预测结果进行解码，并且经过 nms 得到 proposal\_list
4. 对每个 proposal 进行 roipool 操作，变成统一大小
5. 组成 batch 输入到 rcnn head 进行类别分类和 proposal refine 回归
6. 对预测结果再次进行 nms 操作得到优化后的最终 bbox

## 2 总结

faster rcnn 是经典的 two-stage 目标检测算法，其对 rpn 层提取的大量 roi 进行 refine 操作，可以得到精确的 bbox。由于其优雅高效的实现，理解上也通俗易懂，故依然是目前的主流算法。对于初学者而言一开始接触就 faster rcnn 会被其复杂代码绕晕的，最好先把 RPN 思想彻底理解后再来看本算法，会轻松很多。当然在理解了 faster rcnn 后，对于后续的 one-stage 算法就更加容易了。
