> 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/fast_rcnn.md).

# Fast R-CNN

Fast R-CNN 是 Ross Girshick 于 2014 年提出的算法。 针对 R-CNN 速度慢的问题，提出了 Roi Pooling 算法，使卷积运算可以在不同的提议框之间进行共享，大幅提高了推理时算法的速度。 Fast R-CNN 还借助多任务学习（multi-task learning）的模式实现了模型的端到端训练，大幅提升了模型训练的速度。 受益于这两个创新点，Fast R-CNN 在训练速度和测试速度上提高了 10-100 倍。

## R-CNN 的速度问题

从检测精度的提升来看，RCNN 是非常成功的。但 RCNN 在速度方面有着明显的劣势。 即便使用 GPU 加速，推理一张图片也需要几十秒的时间。

RCNN 中存在大量重复的计算。 在推理过程中，selective search 可以产生 2000 个左右的提议框。 这些提议框无论大小，都需要统一拉伸到 227x227 再送入卷积网络。 这些 proposal 之间必然有大量重叠，但由于图像拉伸操作的存在，卷积运算不能在不同提议框之间共享。 即便在一张图像上进行推理，我们也需要上千次完整的 AlexNet 卷积网络的前传计算。

RCNN 的训练也非常复杂。在 RCNN 算法中，训练一个检测模型需要四个步骤：

1. 在 ImageNet 数据集上预训练一个图像分类网络
2. 使用裁剪下来的图像区域，对 ImageNet 预训练的网络进行微调训练
3. 用训练好的网络提取所有图像区域的特征
4. 用提取好的特征训练 SVM 分类器，和边界框回归模型

上述步骤前后依赖，耗时较长，而且需要大量的存储空间存储中间结果。

## 卷积共享

Fast RCNN 改进的出发点就是减少重叠区域的卷积计算。在 RCNN 算法中，为了提取不同区域的图像特征， 我们需要将 selective search 产生的提议框全部剪裁下来，再拉伸到同样的大小。由于拉伸尺度不同，重叠的区域在拉伸后就变成不同的图像了。 即便有所重叠，重叠部分也需要分别进行卷积计算。

为了减少重叠区域重复的卷积计算，Fast RCNN 采用了另一种方法，直接用卷积核扫过整个图像，产生整张图像上特征图，再从特征图上裁剪下不同的提议框，作为这些提议框的特征。考虑大小为$$H\times W$$的图像及图像上位置和大小分别为 $$(x, y, h, w)$$ 的提议框。一个空间采样率为 $$S$$ 的卷积网络可以将图像变为 $$\frac{H}{S}\times \frac{W}{S}$$ 大小的特征图。我们可以按比例提议框映射到特征图上的框 $$(\frac{x}{S}, \frac{y}{S}, \frac{h}{S}, \frac{w}{S})$$ ，再将这个框内的特征图裁剪下来作为提议框的特征。

需要注意的是，先拉伸再计算特征，先计算特征再裁剪，两种方法得到的结果是不同的。但两种方法得到的都是语义级别的特征，可以作为后续分类和边界框回归模型的输入。此外，这种映射关系只是人为定义一种几何关系，与神经网络的感受野在并没有本质上的联系，但针对大多数主流的主干网络（如 ResNet、VGG 等），提议框的感受野的中心是 $$(x, y)$$ 。

在 RCNN 中，如果有 N 个提议框，我们需要进行 N 次完整的前传计算，以得到每个提议框的特征。 在 Fast RCNN 中，无论有多少提议框，我们都只需要对原图进行一次前传计算，即可得到全部提议框的特征。

## RoI Pooling

接下来我们需要考虑另一个问题。不同的提议框大小不同，因而裁剪下来的特征图尺度大小也互不相同。但后续的全连接层要求输入特征具有固定的维度。因此我们就需要将不同大小的提议框特征图*处理*到一个固定的大小。为了达到这个目的，Faster RCNN 提出了 RoI Pooling 层，通过池化操作将提议框特征图映射到固定的大小。

RoI Pooling 在操作上与普通的池化有所区别。普通的池化层设置固定大小的池子，例如 2x2 大小，再将每个池子内的神经元响应通过平均或者最大化聚合成一个值，输出特征图的大小随输入变化。RoI Pooling 则是设置一个固定的输出尺寸，再根据这个尺寸和确定池子的大小，使得池化的输出恰好为期待的大小。

在下面的示例图中，整幅图像的特征图大小为 8x8，黑框表示提议框在特征图上的映射，目标大小为 2x2。RoI Pooling 将黑框划分成 2x2 个区域，再在每个区域内取最大值，使得池化后的大小为 2x2 。

![](https://3632175219-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MJhbbglPBd1Md8dFDXQ%2Fsync%2F66c61993265c69f497697b35c85bd26176e6da18.png?generation=1607683060221287\&alt=media)

[图片来源](https://medium.com/analytics-vidhya/object-detection-algorithms-r-cnn-vs-fast-r-cnn-vs-faster-r-cnn-3a7bbaad2c4a)

严格来讲，考虑在特征图上左上右下坐标分别为 $$(l, t, r, b)$$ 的框，如果我们希望将特征图池化到 $$H\times W$$ 的大小，那么池化结果的第 $$i$$ 行，第 $$j$$ 列（$$1\le i \le H$$, $$1\le j \le W$$）的单元来自特征图上

$$
\left(
l+\left\lfloor(j-1)\frac{r-l}{W}\right\rfloor,
t+\left\lfloor(i-1)\frac{b-t}{H}\right\rfloor,
l+\left\lceil j\frac{r-l}{W}\right\rceil,
t+\left\lceil i\frac{b-t}{H}\right\rceil
\right)
$$

范围内的所有响应值。

RoI Pooling 层输出的大小通常设置为所搭配的主干网络在 224x224 图像上输出的特征图的大小，例如对于 VGG 或 ResNet，RoI Pooling 输出大小设置为 7x7，对于 AlexNet 或 ZFNet，输出大小设置为 6x6。这种设置可以更好地利用与训练模型。如果我们把一个 VGG 分类网络最后的池化层直接换成 ROI Pooling，那么这个网络就可以在任意大小的图像及任意大小的框上，产生固定 7x7 大小的特征图，而 7x7 的特征图又恰好可以适配预训练模型中的全连接层输出分类结果。

RoI Pooling 与普通池化层一样，是可以传递梯度的，这就使得端到端训练整个检测模型成为可能。

## 端到端与多任务学习

Fast RCNN 将 RCNN 中的神经网络、SVM 分类器和边界框回归模型集合到了一起，形成了一个**单一**的神经网络模型。该模型可以根据单张图像和其上的一系列提议框，直接计算出每个提议框中物体的类别及边界框的修正值。由于输入的图像和输出的检测结果之间只有一个神经网络模型，因而我们说 Fast RCNN 是一个端到端（end-to-end）的模型。又由于一个模型可以同时学习分类和边界框回归两个任务，因而我们说它是一个多任务学习（multi-task learning）的模型。

Fast RCNN 模型的整体结构如下图所示：

![](https://3632175219-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MJhbbglPBd1Md8dFDXQ%2Fsync%2Ff5123f7d474a8389af0311d7958075082a94ef0a.png?generation=1607937194185255\&alt=media)

在推理阶段，Fast RCNN 以单张图片和 selective search （或其他外部算法）产生的一系列提议框为输入，经由如下步骤预测图中出现的物体：

1. 将图片输入主干网络，产生特征图（即上图中的 conv feature map）。这个过程只需要进行一次。
2. 将全部提议框，按照主干网络的缩放比例映射到特征图上。
3. 对于每一个映射框，通过 RoI Pooling 产生固定尺度（例如 7x7）的**提议框**特征图。
4. 将每个提议框特征图输入两个共享的全连接层，得到一个特征向量，即上图中的 roi feature vector。
5. 将每个特征向量输入分类和回归模型，得到物体的类别，以及边界框的修正值。
6. 删除分类为背景的提议框，利用其它后处理算法如 NMS 除去重叠的检测框，得到最终检测结果。

由于 Fast RCNN 中所有计算都是可微分的，因而我们可以抛弃 RCNN 中分步骤的训练方法，直接对其进行端到端的训练。训练的整体流程与图像分类网络类似，输入图像、前传计算分类和边界框偏移量，与真值比较计算损失函数并回传，更新网络参数，重复迭代至收敛。但由于检测模型在一张图上有若干个提议框，所以前传的过程比图像分类复杂一些。Fast RCNN 的训练的大体流程如下：

1. 输入一个 batch 的图像以及对应的提议框，使用主干网络前传计算得到一批特征图。
2. 将每张图像上的提议框与真值框进行**匹配**，将所有的提议框分为**正样本**、**负样本**、**丢弃样本**三类。大体而言，正样本即为与某些真值框重合度较大的提议框，负样本为与所有真值框重合都比较小的提议框，一些不适合用于训练的提议框则丢弃。在 Fast RCNN 的原始论文中，与某个真值框 IoU 大于 0.5 的提议框会被归为正样本，与所有真值框的 IoU 的最大值介于 0.1-0.5 的归为负样本，其余丢弃。需要注意的是，匹配算法并不是单一的，可以根据需要设计或调整。注意，这里的正样本和负样本都是提议框。
3. 对于正样本提议框，用与之 IoU 最大的真值框的类别、以及对应的边界框偏移量，作为这个样本预测的真值。对于负样本，类别标记为**背景**，不需要边界框偏移量。不难发现，提议框的真值是训练过程中动态生成的。
4. 按照一定的比例（如 1：3）随机选取一部分正样本和负样本，通过 RoI Pooling 得到这些提议框的特征图，并组成 batch，继续前传计算，得到对应的物体类别和边界框偏移量的预测值，与真值比较，计算损失函数，并回传。

对于正样本提议框，我们希望神经网络可以预测出其中物体的类别，以及边界框偏移量。对于负样本，我们只希望神经网络可以将其预测为背景，边界框偏移量对于背景是没有意义的。因而，损失函数可以表达为如下形式：

$$
L = \sum\_{i=1}^{P} L\_i
$$

$$
L\_i=\begin{cases}
L^{\mathrm{Cls}}\_i & \text{负样本}\\
L^{\mathrm{Cls}}\_i + \lambda L^{\mathrm{Loc}}\_i&  \text{正样本}
\end{cases}
$$

其中，$$L^{\mathrm{Cls}}\_i$$ 为分类损失函数，通常使用交叉熵。$$L^{\mathrm{Loc}}\_i$$ 为边界框回归的损失函数，通常使用 smooth L1 损失函数，且仅对于正样本计算这部分损失函数。

通过这种方式，我们实现了端到端的训练，也节省了中间结果需要的存储空间。

## 模型在 mmdetection 中的实现

mmdetection 在 [`configs/fast_rcnn/fast_rcnn_r50_fpn_1x_coco.py`](https://github.com/open-mmlab/mmdetection/blob/master/configs/fast_rcnn/fast_rcnn_r50_fpn_1x_coco.py) 中实现了 fast-rcnn 模型训练的配置文件。下面我们对这个配置文件进行简要分析，需要注意的是，mmdetection 中实现的 Fast RCNN 实现了许多 Fast RCNN 之后的技术，如 ResNet、FPN、RoI ALign 等等，许多内容与当年提出的算法有所区别。

### 数据

首先，Fast RCNN 对数据的加载流程进行了改写。我们可以看到，在定义 `data` 字段时，我们需要把外部算法已经生成好的提议框从预先生成的 pickle 文件中读取进来。在数据流水线中，需要增加一个 `LoadProposals` 操作，将图像对应的至多 2000 个提议框增加到数据的字典中。

```python
train_pipeline = [
    # ... omit some lines ...
    dict(type='LoadProposals', num_max_proposals=2000),
    # ... omit some lines ...
]

data = dict(
    # ... omit some lines ...
    train=dict(
        proposal_file=data_root + 'proposals/rpn_r50_fpn_1x_train2017.pkl',
        pipeline=train_pipeline),
    # ... omit some lines ...
```

### 模型

Fast RCNN 的模型定义继承自 [`configs/_base_/models/fast_rcnn_r50_fpn.py`](https://github.com/open-mmlab/mmdetection/blob/master/configs/_base_/models/fast_rcnn_r50_fpn.py)。

这个模型使用 ResNet 作为主干网络，并使用 FPN 输出了多尺度的特征图。我们会在后面的小节中讲解 FPN，这里可以先忽略之。不使用 FPN 的情况下，我们可以使用 ResNet 的第三个 block 的输出作为特征图，即指定 `out_indices=(2, )`

主干网络输出的特征图会输入 `StandardRoIHead` 模块，该模块会使用 `SingleRoIExtractor` 子模块，根据数据中提供的提议框和主干网络输出的特征图，通过 RoI Align 算法产生 proposal 框的特征图，这里 RoI Align 算法是 RoI Pooling 算法的升级版。我们可以就把它当作 RoI Pooling 来理解。这里 `roi_feat_size=7` 表明 RoI Pooling 的目标大小为 7x7 。

接下来的 `Shared2FCBBoxHead` 以 RoI Pooling 输出的 7x7 大小的特征图为输入，经过两个全连接层，产生一个特征向量，再分别通过各自的全连接层产生类别和边界框的预测。在训练过程中，分别使用`CrossEntropyLoss`和 `L1Loss`作为分类和边界框回归的损失函数，二者的权重通过`loss_weight`指定。

### RoI 采样

基础配置 [`configs/_base_/models/fast_rcnn_r50_fpn.py`](https://github.com/open-mmlab/mmdetection/blob/master/configs/_base_/models/fast_rcnn_r50_fpn.py) 中的 `train_cfg` 字段定义了模型在训练过程中的 RoI 采样规则。这份配置使用 `MaxIoUAssigner` 对提议框和真值框进行匹配，具体匹配规则为：

1. 与所有真值框 IoU 都小于 `neg_iou_thr=0.5` 的提议框为负样本
2. 与某个真值框 IoU 不低于 `pos_iou_thr=0.5` 的提议框为正样本

匹配完成后，使用 `RandomSampler` 从这些正负样本中采样，总共采样 `num=512` 个提议框，其中 `pos_fraction=0.25` 比例为正样本。同时，`add_gt_as_proposals=True` 把真值框也作为*提议框*加入导正样本中，提高正样本的基数。
