本文所看的GitHub地址为:simple-faster-rcnn-pytorch
参考了些博客,主要为:逐字理解faster-rcnn
以及:Faster_RCNN
https://zhuanlan.zhihu.com/p/84465138 这个人最后画的图蛮好的
大的总结:
Region Proposal Network
以下提到的 offset, scale 其实都代表位置偏差,只是x,y是上下左右移动,框高就是缩放了。
一. Region Proposal Network 作用
这个RPN网络主要作用就两个:
作用一: 提供 proposal rois 给 Fast-rcnn,这里的rois是(x_left_top,y_left_top,width,height)。
作用二:训练自身的 bounding box regression 和分类。
1.RPN输入是 backbone提取的特征,然后经过3x3卷积,后面是两个并行的1x1的卷积,见下图:
这里一定要明白,上图左边36=9x4,这里的4是(x,y,w,h)四个值,且其是 offset 和 scale,不是与ground truth一样,也就是说,RPN预测值是预测的偏移量(不是预测的框的坐标),有了偏移量,把偏移量带入公式(公式如下,且预测值是d.(p)(这里用d.代表dx,dy,dw,dh))就得到真实的坐标,这里的坐标就是下面这个:
RPN训练自身的 box regression 是想达到预测的 p_offset, p_scale 非常准确,这样通过上面公式1-1就可以达到跟实际 Ground Truth 很接近的位置坐标。那么,其是怎么训练自身的?它是通过smooth L1 来求的,首先,求出 anchor 与 ground truth 之间的 (a_gt_offset, a_gt_scale),把(a_gt_offset, a_gt_scale)作为基准,然后把(p_offset, p_scale)和(a_gt_offset, a_gt_scale)带入smooth L1求loss。不断训练,让RPN网络预测的(p_offset, p_scale)值和(a_gt_offset, a_gt_scale)值无限接近,当loss最小的时候,就代表无限接近。那么,当(p_offset, p_scale)值和(a_gt_offset, a_gt_scale)值无限接近的时候,把(p_offset, p_scale)带入上面公式1-1,就可以直接得出一个G‘ = Ground Truth了。
那么,anchor与ground truth之间offset和scale怎么求呢?是通过下面公式1-2来求:
对于公式1-2大家不要让其Px,Py,Pw,Ph迷惑,当我们在计算 anchor 与 ground truth 的时候,P就是anchor的值,只不过anchor的坐标要转换为(x中心点,y中心点,宽度,高度)对应的格式。
对于label的值,其是根据 对于的 loc 与 bbox 的 iou值来决定的。
RPN 网络训练的时候,batch size =1,即每次都是一张图片训练。
二. Region Proposal Network 里的两个主要方法
1. AnchorTargetCreator 其作用是用于训练RPN,提高RPN的Proposal更准确。
(1). 首先这个方法输入feature_map_width * feature_map_widht * 9 个anchor,以及对应这张图片的 ground truth。
(2). 然后根据图片的框高过滤掉不在图片内部的anchor,接下来参与计算的只有图片内部的anchor。
(3). 接着计算anchor与ground truth bbox的iou。找到每个anchor与哪个bbox的iou最大,且其最大值大于一定阈值则为正样本,接着找到每个bbox与哪些anchor的iou最大(假如有bbox1, bbox2, 15000个anchor,那么找到与bbox1的iou最大的anchorX, 以及与bbox2的iou最大的anchorY,这里的anchorX, anchorY 可能有多个,因为iou相同大)的作为正样本,接着把iou小于一定阈值的作为负样本。
(4). 然后根据预先设定的正负样本比例1:1,正样本选取128个,负样本选取128个,共计256个。如果正样本只有26个,那么负样本就选取230个,反之类似;如果正样本,负样本都不足128,那就按其各自有多少算多少。
(5). 接着把 anchor 和 ground truth bbox 的位置坐标转换为(x_中心点,y_z中心点,宽度,高度),按照公式1-2计算 anchor 与 ground truth之间的offset, scale。
(6). 把除了正负样本的label给标记为-1,loc全部赋为0。
(7). 接着把正负样本的 a_gt_offset 与 RPN 网络预测的 p_loc 进行 smooth L1计算 loss, 和 a_gt_label 与 p_label 进行 cross_entropy 计算 loss。
2. ProposalCreator 其作用是把RPN网络生成的 9 * feature_map_width * feature_map_height 个筛选出2000个给ProposalTargetCreator
(1). 通过anchor 和 预测值 带入公式1-1计算每个预测值带入公式后的坐标,下面就是公式1-1代码形式,且src_xxxx就是anchor,dxxx 就是预测值。
ctr_y = dy * src_height[:, xp.newaxis] + src_ctr_y[:, xp.newaxis]
ctr_x = dx * src_width[:, xp.newaxis] + src_ctr_x[:, xp.newaxis]
h = xp.exp(dh) * src_height[:, xp.newaxis]
w = xp.exp(dw) * src_width[:, xp.newaxis]
(2). 把 ctr_y, ctr_x, h, w 转换为 左上右下坐标(x_left_top, y_left_top, x_right_down, y_right_down),记为 P_Box
(3). 过滤掉 P_Box 中在图片外部的坐标,得到 P_Box_B。
(3). 通过原始图片与resize到固定图片后,这两者的宽高比:scale,再乘以self.min_size(代码中设定为16)得到一个min_val,然后过滤掉 P_Box_B 中宽高不大于min_val的框,得到 P_Box_C。
(4). 接着对预测的值按照其概率从大到小排序,找到前12000个。( 在这里有一点不明白,就是一个框预测为前景概率和背景概率之和为1,但是它有两个值,作者统一用的第二列的值,难道作者这里默认第二列都为前景概率?不过作者这么选,也行,就是始终认为第二列为前景概率,然后不断训练)
(5). 对12000个进行NMS过滤,阈值为 0.7, 选出 前 2000个,不足2000就按实际有多少算多少。
3. ProposalTargetCreator 其作用是对 ProposalCreator 推荐的 2000个进行进一步筛选,筛选出 128个(32个正样本,128个正样本)给 ROI head,即 fast-rcnn网络
(1). 利用 ProposalCreator 推荐的 2000 个 与 真实的 ground truth 进行 iou 计算。
(2). 选出2000个与哪个 ground truth的iou最大的索引,与哪个最大就代表属于这个ground truth对应的label,然后对每个label加1,得到的gt_roi_label就是属于具体哪个label了,如下。
iou = bbox_iou(roi, bbox)
gt_assignment = iou.argmax(axis=1)
max_iou = iou.max(axis=1)
# Offset range of classes from [0, n_fg_class - 1] to [1, n_fg_class].
# The label with value 0 is the background.
gt_roi_label = label[gt_assignment] + 1
(3). 接着判断 iou 的值大于一定阈值为正样本,随机选出32个,不足就以实际为准;阈值大于零,小于一定阈值为负样本,随机选出96个,不足以实际为准。
(4). 对拷贝刚刚选出的128个样本一份,用拷贝版本与ground truth 进行 计算 offset,且对这个offset进行了归一化处理。这个offset 就作为一个基准了,就是 gt_offset了,也就是说,其给 ROI Head 输入有 proposal, 还有 ground truth(一般都是给proposal 和 ground truth,这里也不列外),只不过这个 ground truth就是 gt_offset,还有backbone提取的feature。
冬夜读书示子聿
陆游
古人学问无遗力,少壮工夫老始成。
纸上得来终觉浅,绝知此事要躬行。
小细节:
不知道大家看代码有没有注意到这里,下图所示:
for layer in features[:10]:
for p in layer.parameters():
p.requires_grad = False
能进入for p in layer.parameters():循环下的只有 Conv2d,而且会进入两次,刚开始不理解为啥两次,是因为有两个卷积,一个输入,一个输出,这两个卷积都不更新。
RegionProposalNetwork
-
实现了生成所有anchor
-
ProposalCreator在其内部
-
RPN网络后面 3x3 卷积,两个 1x1 卷积
Normal_init
VGG16RoIHead:输入:分类+1(背景),roi pooling size,spatial_scale(这个主要用于rois的坐标映射到 feature map 上的缩放比例),分类(decom_vgg16),这个就是fast-rcnn网络了。
RoIPool:这个用的pytorch.ops自带的
FasterRCNN:这个好像是基类
FasterRCNNVGG16 -> 提取到 extractor,classier -> RegionProposalNetwork
-> VGG16RoIHead
FasterRCNNVGG16 类只实现了init函数,其继承自FasterRCNN类,这个中实现了 forward,所以,提取feature 就是在这个FasterRCNN中做的。
所以,总的入口是FasterRCNNVGG16,然后其初始化了基类FasterRCNN,接着FasterRCNNVGG16定义一个其对象,这个对象传入FasterRCNNTrainer类中,然后在这个类中调用了AnchorTargetCreator()和ProposalTargetCreator()类,实现了训练自身和推荐rois给ROIHead。
1.当基类调用FasterRCNNVGG16对象时,对于传入图片,会调用extractor,提取到特征,然后调用rpn,产生 anchor,rois(2000个),接着调用self.proposal_target_creator,对传入的rois进行过滤,接着调用ROIHead,再调用anchor_target_creator,接着就是算loss。
不理解 gt_roi_label = label[gt_assignment] + 1 这个地方虽然说把gt的label从0-19转为1-20,但是label有时有两个值,gt_assignment是每行的最大索引,那分配分出来就是每行最大索引对应的label中的值,但是label不是背景和前景,但是gt_assignment是计算的box与iou最大的,我认为有些是背景,但是label中没有背景,label中是两个类别,这就没有把对应的 gt_assignment算的是与两个bbox(真实box)的概率,、
理解了理解了,到ProposalTargetCreator这一步输入的2000个其实已经全是前景了,所以上面就是看前景分别属于哪个gt对应的label,因为label里面包含两个bbox,这样gt_assignment就是说的与label中哪个bbox的iou最大,最大就属于对应的label
而且还会对选出的2000个选出128个,再选出32个为正样本,96个为负样本