Faster RCNN的代码流程梳理【转载整理】

Faster RCNN的代码流程梳理【转载整理】

          本文转载自知乎链接:从编程角度学习Faster R-CNN

          在目标检测领域, Faster R-CNN表现出了极强的生命力, 虽然是2015年的论文, 但它至今仍是许多目标检测算法的基础,这在日新月异的深度学习领域十分难得。Faster R-CNN还被应用到更多的领域中, 比如人体关键点检测、目标追踪、 实例分割还有图像描述等。现在很多优秀的Faster R-CNN博客大都是针对论文讲解,本文将尝试从编程角度讲解Faster R-CNN的实现。由于Faster R-CNN流程复杂,符号较多,容易混淆,本文以VGG16为例,所有插图、数值皆是基于VGG16+VOC2007 。

1.基本流程:

1.1 目标:

          从编程实现角度角度来讲, 以Faster R-CNN为代表的目标检测任务,可以描述成:给定一张图片, 找出图中的有哪些对象,以及这些对象的位置和置信概率。

1.2 整体架构:

          Faster R-CNN的整体流程如下图所示。

          从编程角度来说, Faster R-CNN主要分为四部分(图中四个绿色框):

               1)Dataset:数据,提供符合要求的数据格式(目前常用数据集是VOC和COCO);

               2)Extractor: 利用CNN提取图片特征features(原始论文用的是ZF和VGG16,后来又用ResNet101);

               3)RPN(Region Proposal Network): 负责提供候选区域ROIs(每张图给出大概2000个候选框);

               4)ROIHead: 负责对rois分类和微调,对RPN找出的ROIs,判断它是否包含目标,并修正框的位置和座标。

         Faster R-CNN整体的流程可以分为三步:

               1)特征提取: 图片经过预训练的网络(Extractor),提取到了图片的特征;

               2)区域推荐: 利用提取到的特征,经过RPN网络,找出一定数量的ROIs;

               3)分类与回归:将ROIs和图像特征,输入到ROIHead,对这些ROIs进行分类,判断都属于什么类别,同时对这些ROIs的位置进行微调。

2.详细实现:

2.1 数据:

        对于每张图片,需要进行如下数据处理:

               1) 图片进行缩放,使得长边小于等于1000,短边小于等于600(至少有一个等于);

               2)对相应的boundingboxes 也也进行同等尺度的缩放;

               3)对于Caffe 的VGG16 预训练模型,需要图片位于0-255,BGR格式,并减去一个均值,使得图片像素的均值为0。

       最后返回四个值供模型训练:

               1) images : 3×H×W (B-G-R三通道,宽W,高H);

               2) bboxes: 4×K , K个bounding boxes,每个bounding box的左上角和右下角的座标,形如(Y_min,X_min, Y_max,X_max);

               3) labels:K, 对应K个bounding boxes的label(对于VOC取值范围为[0-19]);

               4) scale: 缩放的倍数, 原图H' ×W'被resize到了HxW(scale=H/H' )。

       需要注意的是,目前大多数Faster R-CNN实现都只支持batch_size=1的训练。

2.2 Extractor:

       Extractor使用的是预训练好的模型提取图片的特征,论文中主要使用的是Caffe的预训练模型VGG16。

       一张图片,经过Extractor之后,会得到一个C×(H/16)×(W/16)的特征图。 修改如下图所示:

               1) 为了节省显存,前四层卷积层的学习率设为0;

               2) Conv5_3的输出作为图片特征,Conv5_3相比于输入,下采样了16倍,也就是说输入的图片尺寸为3×H×W,那么特征图的尺寸就是C×(H/16)×(W/16);

               3) VGG最后的三层全连接层的前两层,一般用来初始化ROIHead的部分参数。

2.3 RPN:

       Faster R-CNN最突出的贡献就在于提出了Region Proposal Network(RPN)代替了Selective Search,从而将候选区域提取的时间开销几乎降为0(2s -> 0.01s)。

2.3.1 Anchor:

       在RPN中,作者提出了Anchor的概念,Anchor是大小和尺寸固定的候选框。论文中用到的Anchor有三种尺寸和三种比例,如下图所示,三种尺寸分别是小(蓝128)中(红256)大(绿512),三个比例分别是1:1,1:2,2:1。

       3×3的组合总共有9种Anchor。

       然后用这9种Anchor在特征图左右上下移动,每一个特征图上的点都有9个Anchor,最终生成了 (H/16)× (W/16)×9个Anchor,对于一个512×62×37的特征图,有 62×37×9~ 20000个Anchor,也就是对一张图片,有20000个左右的Anchor,类似于暴力穷举的处理方式。

2.3.2 训练RPN:

       RPN的总体架构如下图所示:

       Anchor的数量和特征图相关,不同的特征图对应的Anchor数量也不一样。RPN在Extractor输出的特征图的基础之上,先增加了一个卷积用来作语义空间转换,然后利用两个1x1的卷积分别进行二分类(是否为正样本)和位置回归。进行分类的卷积核通道数为9×2(9个Anchor,每个Anchor二分类,使用交叉熵损失),进行回归的卷积核通道数为9×4(9个Anchor,每个Anchor有4个位置参数)。RPN是一个全卷积网络,这样对输入图片的尺寸就没有要求了。

       接下来RPN做的事情就是利用AnchorTargetCreator将20000多个候选的anchor选出256个Anchor进行分类和回归位置,选择过程如下:

               1)对于每一个ground truth bounding box (gt_bbox),选择和它重叠度(IOU)最高的一个Anchor作为正样本;

               2)对于剩下的Anchor,从中选择和任意一个gt_bbox重叠度超过0.7的Anchor,作为正样本,正样本的数目不超过128个;

               3)随机选择和gt_bbox重叠度小于0.3的Anchor作为负样本。负样本和正样本的总数为256;

               4)对于每个Anchor, gt_label 要么为1(前景),要么为0(背景),而gt_loc则是由4个位置参数(tx,ty,tw,th)组成,这样比直接回归座标更好;

               5)计算分类损失用的是交叉熵损失,而计算回归损失用的是Smooth_l1_loss. 在计算回归损失的时候,只计算正样本(前景)的损失,不计算负样本的位置损失。

2.3.3 RPN生成ROIs:

       RPN在自身训练的同时,还会提供ROIs给Fast RCNN(RoIHead)作为训练样本,RPN生成ROIs的过程(ProposalCreator)如下:

               1)对于每张图片,利用它的特征图, 计算 (H/16)× (W/16)×9(大概20000)个Anchor属于前景的概率,以及对应的位置参数;

               2)选取其中概率较大的12000个Anchor;

               3)利用回归的位置参数,修正这12000个Anchor的位置,得到ROIs;

               4)利用非极大值((Non-maximum suppression, NMS)抑制,选出概率最大的2000个ROIs。

       注意:在测试推理阶段,为了提高处理速度,12000和2000分别变为6000和300;

       注意:这部分的操作不需要进行反向传播,因此可以利用numpy/tensor实现,RPN的输出为ROIs(形如2000×4或者300×4的tensor)。

2.4 ROIHead/Fast R-CNN:

       RPN只是给出了2000个候选框,ROIHead在给出的2000个候选框之上继续进行分类和位置参数的回归。

2.4.1 网络结构:

       由于ROIs给出的2000个候选框,分别对应特征图不同大小的区域。首先利用ProposalTargetCreator 挑选出128个sample_ROIs, 然后使用了ROIPooling 将这些不同尺寸的区域全部池化到同一个尺度(7×7)上。下图就是一个例子,对于特征图上两个不同尺度的ROI,经过RoIPooling之后,最后得到了3×3的特征图。

       ROIPooling 是一种特殊的池化操作,给定一张图片的特征图 (512×H/16×W/16) ,和128个候选区域的座标(128×4),ROIPooling将这些区域统一下采样到 (512×7×7),就得到了128×512×7×7的向量。可以看成是一个batch_size=128,通道数为512,7×7的特征图。

       池化成7×7的尺度是为了能够共享权重,在之前讲过除了用到VGG前几层的卷积之外,最后的全连接层也可以继续利用,当所有的ROIs都被池化成(512×7×7)的特征图后,将它reshape 成一个一维的向量,就可以利用VGG16预训练的权重,初始化前两层全连接。最后再接两个全连接层,分别是:

               1)FC21:用来分类,预测RoIs属于哪个类别(20个类+背景);

               2)FC84:用来回归位置(21个类,每个类都有4个位置参数)。

2.4.2 训练:

       前面讲过,RPN会产生大约2000个ROIs,这2000个ROIs不是都拿去训练,而是利用ProposalTargetCreator 选择128个ROIs用以训练。选择的规则如下:

               1)ROIs和gt_bboxes 的IoU大于0.5的,选择一些(比如32个);

               2)选择 ROIs和gt_bboxes的IoU小于等于0(或者0.1)的选择一些(比如 128-32=96个)作为负样本;

       为了便于训练,对选择出的128个ROIs,还对他们的gt_roi_loc 进行标准化处理(减去均值除以标准差)。

       对于分类问题,直接利用交叉熵损失. 而对于位置的回归损失,一样采用Smooth_L1Loss, 只不过只对正样本计算损失,而且是只对正样本中的这个类别4个参数计算损失。举例来说:一个ROI在经过FC 84后会输出一个84维的loc 向量. 如果这个ROI是负样本,则这84维向量不参与计算 L1_Loss;如果这个ROI是正样本,属于label K,那么它的第 K×4, K×4+1 ,K×4+2, K×4+3 这4个数参与计算损失,其余的不参与计算损失。

2.4.3 生成预测结果

        测试的时候对所有的ROIs(大概300个左右) 计算概率,并利用位置参数调整预测候选框的位置,然后再执行一遍非极大值抑制(之前在RPN的ProposalCreator用过)。

        注意:

               1)在RPN的时候,已经对Anchor做了一遍非极大值抑制,在RCNN测试的时候,还要再执行一遍;

               2)在RPN的时候,已经对Anchor的位置做了回归调整,在RCNN阶段还要对ROI再执行一遍;

               3)在RPN阶段分类是二分类,而Fast RCNN阶段是21分类。

2.5 模型架构图:

        最后整体的模型架构图如下:

        需要注意的是: 蓝色箭头的线代表着计算图,梯度反向传播会经过。而红色部分的线不需要进行反向传播(论文了中提到了ProposalCreator生成ROIs的过程也能进行反向传播,但需要专门的算法)。

3.概念对比:

        在Faster RCNN中有几个概念,容易混淆,或者具有较强的相似性,在此列出来并做对比。

3.1 bbox/ anchor/ ROI/loc:

        BBox:全称是bounding box,边界框。其中Ground Truth Bounding Box是每一张图中人工标注的框的位置。一张图中有几个目标,就有几个框(一般小于10个框)。Faster R-CNN的预测结果也可以叫bounding box,不过一般叫 Predict Bounding Box;

        Anchor:锚是人为选定的具有一定尺度、比例的框,一个特征图的锚的数目有上万个(比如 20000);

        ROI:候选框,Faster R-CNN之前传统的做法是利用selective search从一张图上大概2000个候选框,现在利用RPN可以从上万的Anchor中找出一定数目更有可能的候选框。在训练RCNN的时候,这个数目是2000,在测试推理阶段,这个数目是300(为了速度)

        ROI不是单纯的从anchor中选取一些出来作为候选框,它还会利用回归位置参数,微调anchor的形状和位置。

        可以这么理解:在RPN阶段,先穷举生成千上万个Anchor,然后利用Ground Truth Bounding Boxes,训练这些Anchor,而后从Anchor中找出一定数目的候选区域(ROIs)。RoIs在下一阶段用来训练ROIHead,最后生成Predict Bounding Boxes。

        loc: bbox,Anchor和ROI,本质上都是一个框,可以用四个数(y_min, x_min, y_max, x_max)表示框的位置,即左上角的座标和右下角的座标。这里之所以先写y,再写x是为了数组索引方便,但不要混淆。 除了用这四个数表示一个座标之外,还可以用(y,x,h,w)表示,即框的中心座标和长宽,在训练中进行位置回归的时候,用的是后一种的表示。

3.2 四类损失:

        虽然原始论文中用的4-Step Alternating Training 即四步交替迭代训练。然而现在github上开源的实现大多是采用近似联合训练(Approximate joint training),端到端,一步到位,速度更快。

        在训练Faster RCNN的时候有四个损失:

               1)RPN 分类损失:Anchor是否为前景(二分类);

               2)RPN位置回归损失:Anchor位置微调;

               3)ROI分类损失:ROI所属类别(21分类,多了一个类作为背景);

               4)ROI位置回归损失:继续对RoI位置微调。

        四个损失相加作为最后的损失,反向传播,更新参数。

3.3 三个creator:

        在一开始阅读源码的时候常常把Faster RCNN中用到的三个Creator弄混。

               1)AnchorTargetCreator : 负责在训练RPN的时候,从上万个anchor中选择一些(比如256)进行训练,以使得正负样本比例大概是1:1. 同时给出训练的位置参数目标。 即返回gt_rpn_loc和gt_rpn_label;

               2)ProposalTargetCreator: 负责在训练ROIHead/Fast R-CNN的时候,从ROIs选择一部分(比如128个)用以训练。同时给定训练目标, 返回(sample_RoI, gt_RoI_loc, gt_RoI_label);

               3)ProposalCreator: 在RPN中,从上万个Anchor中,选择一定数目(2000或者300),调整大小和位置,生成ROIs,用以Fast R-CNN训练或者测试。

        其中AnchorTargetCreator和ProposalTargetCreator是为了生成训练的目标,只在训练阶段用到,ProposalCreator是RPN为Fast R-CNN生成ROIs,在训练和测试阶段都会用到。三个共同点在于他们都不需要考虑反向传播(因此不同框架间可以共享numpy实现)

3.4 感受野与scale:

        感受野:从直观上讲,感受野receptive field)就是视觉感受区域的大小,在卷积神经网络中,感受野的定义是卷积神经网络每一层输出的特征图上的像素点在原始图像上映射的区域大小。我的理解是,特征图上的某一点f对应输入图片中的一个区域,这个区域中的点发生变化,f可能随之变化。而这个区域外的其它点不论如何改变,f的值都不会受之影响。VGG16的conv5_3的感受野为228,即特征图上每一个点,都包含了原图一个228×228区域的信息。

        Scale:输入图片的尺寸比/特征图的尺寸。比如输入图片是3×224×224,特征图是 512×14×14,那么scale就是         14/224=1/16。可以认为特征图中一个点对应输入图片的16个像素。由于相邻的同尺寸、同比例的Anchor是在特征图上的距离是一个点,对应到输入图片中就是16个像素。在一定程度上可以认为Anchor的精度为16个像素。不过还需要考虑原图相比于输入图片又做过缩放(这也是dataset返回的scale参数的作用,这个的scale指的是原图和输入图片的缩放尺度,和上面的scale不一样)。

4 实现方案:

         在@罗若天ruotianluo/pytorch-faster-rcnn 之前,PyTorch的Faster R-CNN并未有合格的实现(速度和精度)。最早PyTorch实现的Faster R-CNN有longcw/faster_rcnn_pytorch 和 fmassa/fast_rcnn 后者是当之无愧的最简实现(1,245行代码,包括空行注释,纯Python实现),然而速度太慢,效果较差,fmassa最后也放弃了这个项目。前者又太过复杂,map也比论文中差一点(0.661VS 0.699)。当前github上的大多数实现都是基于py-faster-rcnn,RBG大神的代码很健壮,考虑的很全面,支持很丰富,基本上clone下来,准备一下数据模型就能直接跑起来。然而对我来说太过复杂,上百个文件,动不动就好几层的嵌套封装,很容易令人头大。

        趁着最近时间充裕了一些,我决定从头写一个,最终写了一个简单版本的Faster R-CNN,代码地址在 github:simple-faster-rcnn-pytorch

        这个实现主要有以下几个特点:

               1)代码简单:除去空行,注释,说明等,大概有2000行左右代码,如果想学习如何实现Faster R-CNN,这是个不错的参考;

               2)效果够好:超过论文中的指标(论文mAP是69.9, 本程序利用caffe版本VGG16最低能达到0.70,最高能达到0.712,预训练的模型在github中提供链接可以下载);

               3)速度足够快:TITAN Xp上最快只要3小时左右(关闭验证与可视化)就能完成训练;

               4)显存占用较小:3G左右的显存占用。

       

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章