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 算法分析

其整体核心结构如下:

详细一点的话,如下所示:

图片来源

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

1.1 backbone 骨架

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

1.2 RPN 部分

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

1.3 RCNN

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

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 中间数据转换流程

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,该操作非常简单,通过下面的可视化图即可理解:

假设左上是输入特征图,右上的蓝色框是 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 独有网络,不作为基础网络?作者认为把 stage3 作为基础网络的话,网络会偏向于分类问题,并且 imageNet 预训练权重的加载更会加深特征的平移不变性,不利于 bbox 回归。但是由于考虑到代码通用性以及后续 FPN 模块的引入,现在的 faster rcnn 都没有这样做。

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

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

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,那么正负样本定义和采样策略就不可少了

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 推理流程

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 算法就更加容易了。

Last updated