Cascade R-CNN

Cascade R-CNN 是 Zhaowei Cai 等人于 2019 年提出的一个检测算法。主要针对 faster rcnn 中 rcnn 部分 IOU 阈值选取对最终检测 bbox 质量的重大影响,而提出一种级联 rcnn 结构,不同级采用不同 IOU 阈值来进行重新定义正负样本和采样来逐渐提高 bbox 质量,效果稳定、特别好,应该说是目前打比赛的 baseline 算法。整篇论文通俗易懂、思路非常清晰,是不可多得的算法。其主要贡献可以归纳为:

  1. 深入研究了目标检测中 IoU 阈值选取问题,并通过大量实验分析验证了 IoU 阈值选取对检测器性能的影响。其指的是第二级 rcnn 部分 IOU 阈值,而没有考虑 rpn 部分 IOU 阈值,原因是 rcnn 部分才是决定 mAP 的核心,对于 RPN 部分的 mAP 多提高几个点其实带来的意义不能和 rcnn 相比。

  2. 基于上述研究,提出一种迭代 bbox 回归的级联 RCNN 网络,可显著提高 bbox 质量,提供检测精度,效果是非常好的。

1 faster rcnn 性能分析

在两阶段目标检测器中例如 FasterRCNN,RPN 输出 ROI,然后输入到 RCNN 层进行分类和回归,一般 RCNN 部分是设置 IOU=0.5 来进行正负样本划分,通过前面 n 多篇文章我想大概已经知道 IOU 阈值选取其实非常关键了。

论文首先分析 IOU 阈值对检测结果影响,如下图:

直观理解:iou 越大,检测的 bbox 越少,但是普遍质量更好,也就是说 iou 如果设置的比较小,那么在训练过程中可能会带来很多噪声,不利于训练,但是如果 iou 太高,会导致正样本太少出现过拟合。

上图能够反映出很多问题。首先看(a)图,横轴是 rpn 输出的 proposal 于 gt bbox 值的 IoU,纵轴是经过 rcnn 的 box 回归得到的新 IoU,不同线条代表不同阈值训练出来的 detector。以 u=0.5 为例,当对 RPN 输出的 proposal 使用阈值 0.5 来切分正负样本,对于那些 bbox 质量较差(也算正样本,0.5 < Input IOU < 0.55),经过 RCNN 回归后效果提升明显,IOU 可以直接回归到 0.75(应该是和此时正样本分布有关系,在该区间内的正样本应该比较多),且随着 proposal 质量增加,RCNN 回归后的 bbox 质量也会增加,但是 Input IOU 越大的 proposal,回归后提升越来越不明显,虽然精度没有下降。再看 u=0.7,对于低质量 proposal,其提升比较少,因为低质量的 proposal 被滤掉了,没有得到训练,但是对于高质量的 bbox,提升就比较大了。

可以发现,Input IOU 在 0.55~0.6 范围内 proposal 阈值设置为 0.5 时 detector 性能最好,在 0.6~0.75 阈值为 0.6 的 detector 性能最佳,而到了 0.75 之后就是阈值为 0.7 的 detector 了。只有输入 proposal 自身的 IOU 和 detector 训练用的阈值 IOU 较为接近的时候,detector 性能才最好(与其这样说,还不如说说阈值 IOU 设置应该和 rcnn 训练的输入样本 IOU 分布接近的时候,性能最好,其实就是样本重采样而已),如果两个阈值相距比较远,就是 mismatch 问题。简单来说就是如果当前 RPN 输出的 proposal 质量都一般,大部分是 0.5 IOU 的边界框,此时就应该用 IOU=0.5 的阈值进行 RCNN 训练,这样才会达到最佳效果。

再看图(b),其在当前设定的 iou 阈值处分类 loss 会存在一个峰值。这个是非常好理解的,假设 iou 阈值为 0.5,那么说明 proposal 于 gt bbox 值的 IoU 在大于 0.5 时候是正样本,低于 0.5 是背景样本,那么决策边界其实就在 0.5 附近了,那自然分类 loss 最大了。假设一个二分类问题,大于 0 是正样本,小于 0 是负样本,分类目的就是尽可能把 0 附近的正负样本区分开(对于正样本,预测 0 要变成预测 1,对于负样本,预测 0 要变成预测-1,那自然 loss 最大),此时靠近 0 附近的样本 loss 肯定最大,梯度也是最大的。

最后看图(c),在 IOU=0.5 到 0.6 的时候,检测性能下降比较少,但是一旦 IOU=0.7,检测性能就非常差了。这说明不能一味的提高 IOU 来达到输出高质量 bbox 的目的(对应匹配正样本数目不够,会出现过拟合)。

经过上面图示分析,可以得到如下结论:

  1. 低 IOU 阈值对质量较差的 bbox 提高较大,高 IOU 预测对质量较好 bbox 提升较大;

  2. 不能一味的提高 IOU 来达到输出高质量 bbox 的目的。

那么原因是啥呢?其实原因可以归为两点:

  1. 高 IOU 阈值会导致正样本减少,样本减少引发过拟合,后面有实验证明

  2. 在 train 和 inference 使用不一样的阈值很容易导致 mismatch

2 算法改进

2.1 原理分析

既然不能一味的提高 IOU 来达到输出高质量 bbox 的目的,那一个很自然的想法是级联结构,在每个层级采用不同的 iou 阈值来提升。

(a)是标准的 Faster RCNN 第二级 bbox 回归;(c)是前向时候不断迭代 bbox 回归,训练时候还是和 faster rcnn 一样(注意是:H1 H1 H1);(d)是对分类概率进行多次分类,使用了不同的阈值来进行分类,然后融合他们的结果进行分类推理,并没有同时进行 Box reg;(b)是作者所提结构,注意 H1 H2 H3,很明显该结构可以保证训练和测试流程完全一样,一致性更强。

对于(d)这种结构,当 IoU 提高的时候,proposal 的比重下降非常迅速,这种方法没有从根本上克服 overfit 问题,另外这种结构使用了多个高阈值的分类器,训练阈值却只能有一个,必然会导致 mismatch 问题而影响性能。

对于(c)这种级联结构存在问题,单一阈值 0.5 是无法对所有 proposal 取得良好效果的,如前面所示,proposal 经过 0.5 阈值的 detector 后 IoU 都在 0.75 以上,再使用这一阈值并不明智。第二个,detector 会改变样本的分布,这时候再使用同一个结构效果也不好,如下图所示:

第一行横纵轴分别是回归目标中 bbox 的 x 方向和 y 方向偏移量;第二行横纵轴分别是回归目标中 bbox 的宽、高偏差量。可以看到,从 1st stage 到 2nd stage,proposal 的分布其实已经发生很大变化了,因为很多噪声样本经过 bbox 回归后实际上也提高了 IoU,2nd 和 3rd 中的那些红色点已经属于 outliers,如果不提高阈值来去掉它们,就会引入大量噪声干扰,对结果很不利。从这里也可以看出,阈值重新选取本质上是一个 resample 过程,它保证了样本质量。

但是这里会有另一个问题,通过不断提高 IOU 阈值,这样子真的不会减少样本数量么?因为样本数量减少,就容易过拟合。作者做了大量实验,结果如下:

可以看出,样本不仅没有减少,而且稍稍有增加。说明本文方法不会减少正样本数量,是有效的。并且可以发现各个阶段那个 IOU 的 bbox 分布最多。

通过上面的深入分析,作者指出级联多个 rcnn 模块,并且不断提高 iou 阈值,在每个阶段不断进行正负样本重采样策略,不仅不会出现过拟合,而且可以实现极大的性能提升。在代码层面实现就非常简单了,就是在 faster rcnn 后面再级联 n 个 rcnn,每个 rcnn 的输入都是前一个 rcnn 的检测输出。

2.2 代码实现

熟悉了 faster rcnn 的代码,那么 cascade rcnn 代码几乎没有啥东西了,就是 copy 几遍 rcnn 代码就行了。

(1) 骨架+fpn+rpn

这三个部分和 fatser rcnn 结构和用法完全相同,要说唯一不同就是 rpn 的 bbox 回归,fatser rcnn 是 l1 loss,而 cascade rcnn 是 smooth l1 loss

(2) 级联 rcnn

假设一共级联 3 个 rcnn head,那个 head 都包括 faster rcnn 里面说的部分:

  1. roi 提取模块 roialign;

  2. Shared2FCBBoxHead

  3. 正负样本定义策略和随机采样

  4. loss 计算

每个部分结构都是完全相同的,但是loss 权重、正负样本定义策略中的 iou 阈值、target_stds 不一样。这三个参数要改变是因为随着训练进行,每个部分的正负样本分布不一致了,对应的核心参数就要改变,才能最大程度发挥级联效果,并且因为每次 rcnn 都会更新 proposal,故 roialign 层也需要三个或者说运行三遍。本文为了代码简单,全部是 copy 了三遍。

for roi_extractor, head in zip(bbox_roi_extractor, bbox_head):
    # roialign
    self.bbox_roi_extractor.append(build_roi_extractor(roi_extractor))
    # Shared2FCBBoxHead
    self.bbox_head.append(build_head(head))
for idx, rcnn_train_cfg in enumerate(self.train_cfg):
    # 正负样本定义和随机采样策略
    self.bbox_assigner.append(
        build_assigner(rcnn_train_cfg.assigner))
    self.current_stage = idx
    self.bbox_sampler.append(
        build_sampler(rcnn_train_cfg.sampler, context=self))

还有一个细节需要注意:相比 faster rcnn 的 rcnn 部分,reg_class_agnostic 参数不一样,faster rcnn 是 False,对应的回归分支输出 shape 是(batch,4*num_class),在 cascade rcnn 的 rcnn 中为了简单设置为 True,每个 rcnn 的回归分支输出 shape 是(batch,4)。而且 faster rcnn 的 rcnn 部分回归 Loss 是 l1loss,分类 loss 是 sigmoid+ce,而 cascade rcnn 是 smooth l1 loss,分类 loss 是 softmax+ce。

第一个 rcnn 的结构如下:

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=True,
        loss_cls=dict(
            type='CrossEntropyLoss',
            use_sigmoid=False,
            loss_weight=1.0),
        loss_bbox=dict(type='SmoothL1Loss', beta=1.0,
                       loss_weight=1.0)),

(3) 训练流程

  1. RPN 输出经过 get_bboxes 函数输出一系列经过 nms 的 proposal_list

  2. 假设级联 rcnn 个数是 N,n 初始化为 1。首先利用第 n 个 rcnn 的正负样本定义策略和随机采样对 proposal_list 计算正负样本;然后对 proposal_list 确定其应该切割的特征图层;然后利用第 n 个 rcnn 的 roialign 模块进行统一 size 操作;最后输入到第 n 个 rcnn 的 Shared2FCBBoxHead 模块进行预测输出和 loss 计算,返回原始预测结果和 loss

  3. 如果不是最后一个 rcnn 模块,则还需要对利用上述 proposal_list 和原始预测结果进行解码(refine_bboxe 函数),得到新的 refine 后的 proposal_list,然后 n+1,重复(2)-(3)步骤即可,不断 refine 前一个 rcnn 的预测结果

  4. 将 n 个 rcnn 的总 loss 按照权重加起来返回即可

(4) 推理流程

其推理流程和 faster rcnn 非常类似,只不过相当于 rcnn 重复了 N 遍而已。作者实验发现推理时如果单独只用 cascade RCNN 的 stage3,效果相比 stage2 和 stage1,确实是最差的,所以在 cascade RCNN 是用三个 stage 的分数取平均作为最后框的分数,这实际上是一种集成。

2.3 实验结果

级联 rcnn 中一个比较核心的问题是应该级联几个最合适?

结论就是级联 3 个最合适,级联太多不仅速度很慢,而且性能也没有提升了。

并且将级联思想应用于各种 two-stage 目标检测算法中都有很大的性能提升。

3 总结

目标检测其实并不是一个很合适的分类问题,没有一个明确的离散正负样本定义,而是通过 IoU 来连续定义的。但是 IoU 这个指标很难通过 gradient descent 来优化,虽然之前也有一些 IoU loss 的工作,但是效果并不理想。Cascade RCNN 便是一个在这个方向上很好的尝试。

论文看起来很炫,其实分析到本质就是一个不断增加的 IOU 来进行正负样本重采样的算法而已。如果不采用迭代级联算法,不管设置何种 IOU 都只能提升相对应 IOU 的 bbox 质量,而且如果一开始就设置很大的 IOU,那么就会导致正样本太少,出现过拟合(只能检测出部分物体)。那么自然就可以设置级联结构,随着 bbox 样本的 Iou 分布从均值 0.5 不断移动到均值 0.9 的过程中,我也不断提高阈值 IOU,对样本进行重采样,否则会引入大量噪声,挑选合适的正样本进行训练。

Last updated