版权声明:本文为博主原创文章,转载请标明原始博文地址: /yuanlulu/article/details/86769589
本文动机
说实话,介绍Faster R-CNN的文章我看了很多,论文的英文原文和翻译都看过,我知道two-statge和anchor的主要思想,可是我脑子里始终没法建立一个完整的Faster R-CNN的框架,有太多的细节没有搞清楚,每个步骤的tensor是什么维度?这些维度是什么含义?第二阶段的坐标回归和第一阶段一样吗?有太多的细节让我疑惑不已。别人的文章讲的都是别人建立了整体概念以后的细节分解,而我始终不能把所有的细节串联起来。
为此我找了一个github上的tensorflow实现精读代码,希望把自己的理解画下来、写下来,帮自己总结的同时也帮助像我一样的人。
这篇文章主要是阅读参考资料里‘tf-faster-rcnn代码’对应的代码之后写的总结。这个tensorflow版本的实现和作者公开的caffe版本的实现有些许不同,但是不耽误理解原理,实现方面的不同我会在最后介绍。我对caffe不熟,所以就用这个tenorflow版本了。
本文侧重对Faster R-CNN由上到下的理解,有些知识点不会讲很细,可以参考文末《参考资料》里的其它文章。
本文是以VGG为主干网络的基础上解读的。换成其它的backbone也是可以的,但是本文未涉及。
本文解读的代码是以端到端的方法完成训练的,也就是说不需要Faster R-CNN那样分多个阶段分别训练网络的不同部分。
整体结构
下面的图片就是代码整体的流程框图:
注意上图中我只画出了我关心的部分tensor,很多不影响理解的tensor就没有画出来。
蓝色的tensor是参与最终LOSS计算的。从上图可以看出,之所以可以完成端到端的训练,是因为两个stage都参与了LOSS的计算。
细节分解
这一部分按照上面流程图里的大致顺序讲解各个步骤的具体细节。
网络输入
网络输入需要三个tensor:gt_boxes、img和im_info。
img :输入图片
代码里每次固定输入一张图片进行训练和测试,所以img的形状是[1, H, W, C]。其中H、W代表宽高, C表示通道数。因为backbone输出的feature_map会被裁剪为相同的大小送到stage-2的回归网络,所以Faster R-CNN对图片大小没有明确要求,多大的图片都是可以的。
im_info:图片形状
im_info的形状是[3], 存储是就是图片的H、W、C。
gt_boxes:ground truth的bbox坐标
存储的是训练集里的标注的边界框。形状是[None, 5]。第二个维度的长度为5,其格式为(x1, y1, x2, y2, cls_label),前四个数分别为左上角和右下角的这两个点的坐标,单位是像素。第二维的最后一个成员cls_label是这个bbox所属的分类,是一个int类型的值。
主干网络
原则上来说,任何分类网络都可以作为Faster R-CNN的主干网络,主干网络的作用是提取图片特征,为rpn和stage2提供原料。我看的代码里支持好几种分类网络,我选择了VGG模式下进行通读代码。
代码里主干网络输出的特征图名为net_conv,形状是[1, H/16, W/16, 512]。也就是说,相对于原图,宽高方向上缩放了16倍。注意这个16,后面要用很多次。
region_proposal网络
从上面图片中可以看到,net_conv和crop_proposal_layer右侧的部分都属于region_proposal网络(简称rpn)。这部分主要用于产生可能有物体的区域,交给stage2的网络去进一步定位和分类。这个部分产生的区域会比实际物体多。
net_conv后面紧接着一个3x3的卷积,卷积后特征图的大小和通道数都不变。输出的tensor名为rpn(这个rpn仅仅是一个tensor的名字,不表示整个region_proposal网络), 然后分成两个分支, 上面的分支接一个1x1的点卷积,用于为每个anchor框产生一个回归系数。下面的分支同样接一个1x1的点卷积,用于给每个anchor框做二分类(前景还是背景),前景就是可能有物体的,背景就是没物体的。
rpn_cls_score
rpn_cls_score是预测背景和前景分类的tensor。其shape为[1, H/16, W/16, num_anchors * 2]。注意长宽和前面的net_conv一样,只是通道数变为num_anchors * 2。
由于Faster R-CNN使用3种缩放的3种长宽比的anchor框,所以num_anchors为9。rpn_cls_score[:,:,:]的每个值对应num_anchors个anchor框的二分类,长度为为num_anchors * 2。
其中rpn_cls_score[:,:,:, 0:num_anchors]表示对应位置上的9个anchor框为背景的概率(此时还没进行softmax归一化),rpn_cls_score[:,:,:, num_anchors:]表示对应位置上的9个anchor框为前景(也就是有物体)的概率。
rpn_cls_score_reshape
对rpn_cls_score进行reshape之后得到rpn_cls_score_reshape,它的形状是[1, num_anchors*H/16, W/16, 2],注意最后一维的长度变成了2,这是为了好做softmax。
rpn_cls_prob
rpn_cls_prob是rpn_cls_score_reshape进行完softmax运算再做reshape操作得到的,它的形状和rpn_cls_score一样,都是[1, H/16, W/16, num_anchors * 2]。
需要强调的是,图片中的softmax和reshape并不是对应一个操作,而是好几个操作的组合。
rpn_bbox_pred
Faster R-CNN的anchor机制相当于一种变相的滑窗,只不过窗的位置和大小都是固定的。那怎么得到精确的bbox呢?方法就是回归得到在anchor框的基础上进行平移和缩放的偏移系数,对anchor框进行变换就得到了更为精确的bbox。众所周知,Faster R-CNN是属于2-stage类型的目标检测,所以这种回归会做两次。第一次就是得到上面图片里的rpn_bbox_pred,第二次是得到上面图片里的bbox_pred。
一个anchor的位置是四个数表示:x、y、h、w, 分辨表示方框中心点的坐标和宽高。rpn_bbox_pred的形状是[1, H/16, W/16, num_anchors * 4],最后一维之所以是num_anchors * 4,是因为每个anchor框对应的4个回归系数是tx、ty、th、tw,对应anchor的4个坐标信息的偏移系数。
下方带*的符号是预测值,带小写a的符号是anchor框坐标。t^*是rpn_bbox_pred内的内容,它和预测的bbox坐标的关系对应如下:
有了偏移系数,求x*、y*、h*、w*只要做一个相反的运算就可以了。
训练的时候,anchor坐标和实际物体的bbox坐标是已知的,那么这些anchor框对应的修正系数就是:
这样求出的偏移系数,可以和rpn_bbox_pred的内容做比较,作为训练的LOSS之一。
为何采用上面这种偏移系数而不是直接回归得出偏移的坐标值做加法,我们后面单独讨论。
proposal_layer
proposal_layer用于从前面预测的值里面用NMS挑选出最可能的区域传给下一个阶段。
proposal_layer的输入有以下内容:
rpn_bbox_pred:对所有anchor框坐标的偏移系数rpn_cls_prob::对所有anchor框做分类得到的softmax归一化值,分类种类只有两种:前景(包含目标)、背景(无目标)。anchors:所有anchor框的坐标值,也是利用tensorflow的计算图得到的,我在图片里没有画出。anchor框的位置是四个数表示:x、y、h、w, 分辨表示方框中心点的坐标和宽高。实际上我看的代码里anchor框是用左上角和右下角坐标表示的,计算的时候会先转化为中心点坐标的格式。RPN_POST_NMS_TOP_N:做nms运算的时候,选取最终roi的个数RPN_NMS_THRESH:nms的阈值
注意,上面的RPN_POST_NMS_TOP_N和RPN_NMS_THRESH有两套,可以看上面图片左上角的内容。训练和测试模式,采用不同的值。
由于前面的网络结构对每个anchor框都做了分类并产生了偏移系数,实际上后面用不了这么多,RPN_POST_NMS_TOP_N用于指定做nms的时候输出的roi个数。
训练模式下,RPN_POST_NMS_TOP_N为2000,测试模式下为300。也就是说训练的时候产生更多的roi用于提高训练质量,而测试的时候只产生300个roi,这样可以减轻后面的计算压力。
proposal_layer的输出主要是两个tensor:
rois(test):选择的roi,大小是[rois_num_test, 5],第二维每个值长度为5,后四个数就是bbox的中心点坐标及长宽,第一个数表示属于同一个batch里的第几张图片,由于网络固定只输入一张图片,所以第一个数总为0。这里的bbox中心坐标及长宽就是用rpn_bbox_pred及anchor坐标计算得到的。roi_scores: rois对应的得分,其形状为[rois_num_test, 1]。
rois_num_test等于RPN_POST_NMS_TOP_N。
注意,rois(test)的区域是经过裁剪的,保证每个区域都在图片内部。同时上面所说的所有坐标都是针对原图片的,不是针对conv_net这个feature map的。
另外这里rois(test)是测试模式下给stage2用的,训练模式下用的是图片上面的rois(train),后面会讲解。
anchor_target_layer
我总认为很多文章之所以没让我明白Faster R-CNN是因为没讲清楚proposal_target_layer和anchor_target_layer。这两部分之所以重要,是因为这两个模块会产生训练的标准答案,供计算LOSS的使用。
anchor_target_layer为stage1产生标准答案。proposal_target_layer这部分网络为stage2产生标准答案。
本节主要讲解anchor_target_layer。
anchor_target_layer主要功能
anchor_target_layer为stage1产生标准答案。它从所有的anchor里选出和实际的bbox有较大IOU的那部分作为正样本,选出IOU较小的部分anchor作为负样本,最多选出256个anchor框,正负样本比例保持1:1,如果某种样本太少,则会随机删除另一种保持比例。
注意,anchor_target_layer只保留全部区域范围都在图片内部的anchor框,那些有部分范围不在图片内部的anchor框将被抛弃,永远不参与训练。但是proposal_layer会利用所有的anchor,最后仅仅把超出图片部分的roi裁小,以保持roi在图片内部。这两个地方的这点区别还是要注意的。
anchor_target_layer的输入参数
rpn_cls_score:这个tensor仅仅为anchor_target_layer提供feature map的宽高这两个参数,其内容并不被anchor_target_layer需要。gt_boxes:包含物体的bbox,这个框是从训练集的标注中来的,用于筛选优质的正负样本anchors:所有anchor框的坐标。其坐标可以用于和gt_boxes计算IOU,有较大IOU的anchor框将被选为正样本,有较小IOU的被选为负样本。RPN_POSITIVE_OVERLAP:超参,正样本的IOU阈值,默认为0.7。高于这个值作为正样本。RPN_NEGATIVE_OVERLAP:超参,负样本的IOU阈值,默认为0.3。低于这个值作为负样本。RPN_BATCHSIZE:超参,anchor_target_layer输出的anchor框的上限个数,默认值为256。RPN_FG_FRACTION:超参,正样本比例,默认0.5。默认情况下RPN_BATCHSIZE*RPN_FG_FRACTION=256 * 0.5=128,所以默认情况下正负样本各占一半。如果正样本或者负样本超过了128个,则会随机删除部分,保持综述为128。少了就没办法了。anchor_target_layer的输出参数
rpn_labels:输出正负样本的标签。形状是[1, 1, num_anchors*H/16, W/16],也就说说针对所有的anchor框都有一个标准值,其中正样本标注为1,负样本标注为0,其它不关心的样本标注为-1.rpn_bbox_targets:输出所有正样本的anchor框对应的偏移系数。其形状为[1, H/16, W/16, num_anchors * 4],每个anchor都有对应的值,不过只有正样本对应的位置有有效值,负样本和不关心的anchor都用全0填充。rpn_bbox_inside_weights和rpn_bbox_outside_weights:计算LOSS的掩码。其形状是[1, H/16, W/16, num_anchors * 4],可以看到其形状和rpn_bbox_targets一致。被选中的作为正样本的anchor对应的位置其值为(1.0, 1.0, 1.0, 1.0)。 计算LOSS的时候用这两个值作为过滤rpn_bbox_targets的掩码。在代码实现里,这两个值是一样的。rpn_labels将和前面的rpn_cls_score_reshape一起计算交叉熵损失,rpn_bbox_targets将和前面的rpn_bbox_pred计算smooth_L1损失。
proposal_target_layer
proposal_target_layer这部分网络为stage2产生标准答案。
输入参数
输入参数包括前面proposal_layer的两个输出tensor:rois(test)和roi_scores。还包括:
gt_boxes:包含物体的bbox,这个框是从训练集的标注中来的,用于筛选优质的正负样本BATCH_SIZE:筛选出的roi的个数,默认值为128。FG_FRACTIONL:正样本比例,默认0.25。也就是说,默认情况下正样本做多取32个。FG_THRESH:和gt_box的IOU大于这个阈值会被作为正样本,默认为0.5BG_THRESH_HI, BG_THRESH_LO:和gt_box的IOU在这两个阈值范围内的roi会被作为负样本。默认值是0.5和0.1
注意,proposal_target_layer输出的样本个数总是固定的,如果:
正负样本个数都不为0:正样本过多则去掉多余正样本,少于BATCH_SIZExFG_FRACTION就保持原样;负样本进行有放回随机抽取,保证总个数是BATCH_SIZE个。负样本个数为0:正样本个数大于BATCH_SIZE则进行无放回随机抽取, 否则对正样本进行有放回随机抽取,保证总个数是BATCH_SIZE个。正样本个数为0:负样本个数大于BATCH_SIZE则进行无放回随机抽取, 否则对负样本进行有放回随机抽取,保证总个数是BATCH_SIZE个。
由此可见,无论如何,proposal_target_layer输出的样本个数都是BATCH_SIZE。
输出参数
bbox_targets:计算gt_boxes相对rois(test)的偏移系数,注意前面stage1的回归系数是相对anchor框的,这里是相对stage1的输出rois(test)的。bbox_targets的形状是[rois_num_train, num_classes * 4],num_classes是总的分类的个数,rois_num_train等于BATCH_SIZE。最后一维表示对应的roi在所有分类上的偏移系数,长度虽然是num_classes * 4,只有正确分类的对应的位置有有效值,其余位置都是0.labels:输出的分类值。一个roi的分类值等于与之有最大IOU的gt_box所属的分类。labels的形状是[rois_num_train, 1]。bbox_inside_weights和bbox_outside_weights:形状都是[rois_num_train, num_classes * 4],作用就是计算LOSS的时候作为掩码过滤bbox_targets中正确分类的偏移系数。前面bbox_targets的最后一维只在正确分类的对应位置上有有效的偏移系数,计算LOSS的使用需要掩码屏蔽其它分类的影响。代码中这两个值是相等的。rois(train):形状是[rois_num_train, 5],含义和rois(test)一致。crop_proposal_layer
crop_proposal_layer的作用就是利用生成的roi的位置裁剪feature map。裁剪的时候先裁剪为14x14的大小,然后采用最大池化为7x7。这和官方直接裁剪为7x7是不同的。
注意crop_proposal_layer在训练模式和测试模式下的输入源是不同的:训练模式下的输入是proposal_target_layer产生的rois(train),测试模式下的输入是proposal_layer产生的rois(test)。
crop_proposal_layer输出的tensor名为pool5,其形状是[rois_num, 7, 7, 512]。根据上面的讲解我们知道,(使用默认值)训练模式下产生128个roi, 测试模式下产生300个roi。所以训练模式下rois_num为128,测试模式下rois_num为300。
fc7
pool5的内容会被拉成一维向量,经过两层4096的全连接节点生成fc7,fc7的形状是[rois_num, 4096]。
cls_score和bbox_pred
整个网络最终的输出值是:
cls_score:对应每个roi在所有分类上的可能性(此时还没做softmax归一化),形状是[rois_num, num_classes]bbox_pred:预测的目标位置相对于roi的偏移系数,形状是[rois_num, num_classes*4]。roi和偏移系数计算bbox的方法和stage1是一样的。
LOSS计算
代码中的训练事端到端的,LOSS计算时使用了stage1和stage2两个阶段的输出。最上方推按中蓝色的tenor都参与了LOSS的计算。
代码中LOSS共分为5部分:
权重正则化:利用slim的接口为所有的权重定义了衰减系数为0.0001的L2正则化损失,偏置没有参与正则化stage1的分类损失:由rpn_bbox_pred和rpn_bbox_targets这两个tenor计算交叉熵得到stage1的bbox损失:由rpn_cls_score_reshape和rpn_labels计算smooth_L1损失得到stage2的分类损失:由cls_score和labels计算交叉熵得到stage2的bbox损失:由bbox_pred和bbox_targets计算smooth_L1损失得到
代码实现和官方的不同
我看的这份代码专门写了篇论文讲解和官方代码实现上的不同,文章位置在:
《An Implementation of Faster RCNN with Study for Region Sampling》:/pdf/1702.02138.pdf
文章提到实现上的差异包括:
原始实现中RoI pooling layer直接裁剪了一个7x7大小的特征图,本代码里用tf.image.crop_and_resize裁剪一个14x14的特征图,然后用最大池化缩小为7x7大小。原始实现每个batch两张图片,anchor_target_layer为每个图片产生128个样本,本代码每个批次只有一张图片,产生256个样本原始的实现里会删除过小的区域(宽或高小于16像素)。本代码发现这是多余的操作,会损害小目标的检测
当然还有一些其他影响不大的小改动。不再翻译了。
作者在论文里还做了一组测试,那就是发现训练时让proposal_layer用NMS算法,测试时让proposal_layer直接选取得分最高的5000个框作为roi得到的map有略微的优势,当然时间会长不少。
问题探讨
计算下共有多少anchor框
假设原图的宽高是W和H,经过backbone以后降为原来的1/16。再假设每个anchor点上有不同大小的anchor框共9个。
那么总公共的anchor框个数为w/16 x h/16 x 9。
为何用偏差系数而不是直接回归得到偏差量
Faster R-CNN对bbox做回归,并没有直接回归得到偏移量,而是学习偏移系数。那么为什么会这样呢?
论文里说Faster R-CNN的anchor回归方法的一个重要特性是它具有平移不变性。
参考资料里的国外博客有张图很能说明问题(做了缩放后偏移系数不变):
同时参考资料里面的《One Stage目标检测算法可以直接回归目标的坐标是什么原理?》也提出了另外两个角度:
正则化:视觉上相差很近的大物体和相差很远的小物体,偏差的像素数可能是一样的,这就不合理了。所以偏移系数加入了对anchor框大小的正则化。防止出现负数:宽和高不可能是负数,进行log/exp变换防止出现负数
smooth L1 loss
Faster R-CNN在求最后LOSS的时候,对偏移系数采用的是smooth L1 loss。那么这种算法和L1和L2 loss比有啥优点呢?
L1及L2 loss的公式如下:
L2的问题是对于异常点(outliers)和噪声更敏感,容易梯度爆炸;L1倒是对异常点(outliers)和噪声不敏感的,但是在0处不平滑,可能会在0附近震荡,影响收敛。
smooth L1 loss结合两者的优点应运而生:
上图来自知乎。
后记
说老实话,从开始接触这份代码到现在有一个多月了,中间穿插了很多其它事,但是阅读代码的工作量还是超出我想象。但是不读代码我又有无数的疑问,让我无法相信自己真的懂了Faster R-CNN。
写完文章才发现,我写的还是给自己看的笔记而不是给别人看的教程。也许这就是我看不懂别人文章的原因吧,毕竟专门为别人写博客的人不多,大家还都是为自己写的。
参考资料1
这部分参考资料看的比较多
论文原文: Faster R-CNN: Towards Real-Time Object
Detection with Region Proposal Networks
知乎:Faster R-CNN译文
tf-faster-rcnn代码:/endernewton/tf-faster-rcnn
代码对应的改动:An Implementation of Faster RCNN with Study for Region Sampling
国内博客:一文读懂Faster RCNN
国外介绍faster RCNN的文章:Object Detection and Classification using R-CNNs
One Stage目标检测算法可以直接回归目标的坐标是什么原理?:
对x, y, w, h坐标进行log/exp变换防止出现负数。为何要学习变换系数,而不直接学习坐标差?为了正则化。
L1 loss 和 L2 loss
请问faster rcnn和ssd 中为什么用smooth l1 loss,和l2有什么区别?
损失函数:L1 loss, L2 loss, smooth L1 loss
参考资料2
这部分资料看的少,也放在这吧
从R-CNN到RFBNet,目标检测架构5年演进全盘点
对应英文论文:Deep Learning for Generic Object Detection: A Survey
目标检测算法中检测框合并策略技术综述
基于深度学习的目标检测算法综述
Faster R-CNN
原始图片中的ROI如何映射到到feature map?
Faster R-CNN - 目标检测详解
损失函数:L1 loss, L2 loss, smooth L1 loss
如果觉得《OCR:深度学习-目标检测-超详细图解Faster R-CNN》对你有帮助,请点赞、收藏,并留下你的观点哦!