[关闭]
@huanghaian 2020-11-18T05:54:14.000000Z 字数 4933 阅读 2176

mmdetection:Faster RCNN深入分析

mmdetection


0 摘要

做过或者了解过目标检测的朋友,我想第一个接触算法应该就是Faster RCNN了吧,这是一个非常主流的two-stage目标检测算法,深深的影响了目标检测算法的发展。对于Faster RCNN的解读随处可见,故本文主要是结合mmdet代码进行原理性分析,希望大家看完本文能对faster rcnn有深入的理解。

在阅读本文前你需要先阅读RPN解读章节,faster rcnn叫做two-stage检测器,原因是其包括一个区域提取网络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 rcnn简要流程是:首先采用二分类RPN网络提取大量前后景roi特征图,然后通过roipool变成统一大小,最后将roi特征图输入到rcnn中进行roi的refine,得到优化后的bbox。

1 算法分析

其整体核心结构如下:
image.png-113.7kB

详细一点的话,如下所示:
image.png-231.8kB

图片来自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部分的配置如下所示:

  1. roi_head=dict(
  2. type='StandardRoIHead',
  3. bbox_roi_extractor=dict(
  4. type='SingleRoIExtractor',
  5. roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=0),
  6. out_channels=256,
  7. featmap_strides=[4, 8, 16, 32]),
  8. bbox_head=dict(
  9. type='Shared2FCBBoxHead',
  10. in_channels=256,
  11. fc_out_channels=1024,
  12. roi_feat_size=7,
  13. num_classes=80,
  14. bbox_coder=dict(
  15. type='DeltaXYWHBBoxCoder',
  16. target_means=[0., 0., 0., 0.],
  17. target_stds=[0.1, 0.1, 0.2, 0.2]),
  18. reg_class_agnostic=False,
  19. loss_cls=dict(
  20. type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0),
  21. loss_bbox=dict(type='L1Loss', loss_weight=1.0))))

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

  1. rpn_proposal=dict(
  2. nms_across_levels=False,
  3. nms_pre=12000,
  4. nms_post=2000,
  5. max_num=2000,
  6. nms_thr=0.7,
  7. min_bbox_size=0),

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

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

(2) roipool操作
上述可以得到(batch,num,4)个候选roi bbox,在特征图上面进行切割即可得到batchxnum个不同大小的roi特征图,但是这些roi特征图对于rcnn来说肯定不能是不同大小的,否则无法组成batch进行训练,故需要roi提取器,其有两个功能:利用roi坐标在特征图上面切割出对应区域特征;然后将该区域特征变成指定固定大小输出。该模块称为roipool,该操作非常简单,通过下面的可视化图即可理解:
image.png-119.8kB
假设左上是输入特征图,右上的蓝色框是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层的输入参数:

  1. def forward_train(self,
  2. x,
  3. img_metas,
  4. proposal_list,
  5. gt_bboxes,
  6. gt_labels,
  7. gt_bboxes_ignore=None,
  8. 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,那么正负样本定义和采样策略就不可少了

  1. rcnn=dict(
  2. assigner=dict(
  3. type='MaxIoUAssigner',
  4. pos_iou_thr=0.5,
  5. neg_iou_thr=0.5,
  6. min_pos_iou=0.5,
  7. match_low_quality=False,
  8. ignore_iof_thr=-1),
  9. sampler=dict(
  10. type='RandomSampler',
  11. num=512,
  12. pos_fraction=0.25,
  13. neg_pos_ub=-1,
  14. add_gt_as_proposals=True),
  15. pos_weight=-1,
  16. debug=False))

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

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

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

1.4 推理流程

  1. test_cfg = dict(
  2. rpn=dict(
  3. nms_across_levels=False,
  4. nms_pre=6000,
  5. nms_post=1000,
  6. max_num=1000,
  7. nms_thr=0.7,
  8. min_bbox_size=0),
  9. rcnn=dict(
  10. score_thr=0.05,
  11. nms=dict(type='nms', iou_threshold=0.5),
  12. 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算法就更加容易了。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注