文章目录
欢迎访问个人网络日志🌹🌹知行空间🌹🌹
1.Regionn Proposal Network背景
RPN
,Region Proposal Network是中科大与微软亚洲研究院联合培养博士,原Momenta
研发总监任少卿
与何凯明,Ross Girshick
共同发表的论文Faster R-CNN
中提出的一个网络结构,用于目标检测,RPN
论文最早发表于2015
年06
月04
号,是在Fast RCNN
上的改进,与其一起提出的Translation-Invariant Anchors
极大的提高的检测的性呢和准确度。Faster R-CNN
使用RPN
和anchors
替代RCNN
和Fast RCNN
中的selective search
方法,Faster R-CNN
将检测问题分成了特征提取backcbone
的训练和RPN
候选框生成网络的训练,因此是Two Stage
检测框架。RPN
用于生成Proposal Boxes
, 以用来输入ROI Pooling
和ROI Align
,做进一步的类别判断和检测框定位。
因RPN
是Faster R-CNN
中提出的,先来看下Faster R-CNN
的整体结构:
上图中,可以看到,对于输入的图像,先经过Conv Layers
进行特征提取得到feature map
,再将feature map
一支用于输入RPN
结合anchors
来生成Proposal Boxes
,另一支feature map
和RPN
生成的Proposal boxes
一起输入ROI Pooling
,经过全连接层后
做检测物体类别的回归和检测框的精细化定位。从上图中可以知道,RPN
网络的作用就是输入feature map
,输出Proposal Boxes
,在进行检测网络整体训练之前,需基于现有的Model
先训练RPN
网络,使其能够用来生成Proposal Boxes
,然后再训练Model
,循环3
次。
2.Regionn Proposal Network的结构
如图,这是目前R-CNN
衍生出来的检测算法都会使用的RPN Head
的网络结构,用来生成Proposal Boxes
,并判断其中是否包含物体,输出每个Proposal Boxes
的置信度。结合上图,介绍一下rpn
网络的结构,RPN
网络的输入是backbone
提取得到的feature map(NCHW)
,网络结构中先有一个3x3
的卷积,进一步融合特征,然后将卷积结果分别输入到两个分支上。每个分支都包含一个1x1
的卷积,只改变输入特征图的通道大小,不改变feature map
的宽高。其中一支负责预测anchors
偏移量,输出通道数变为num_anchors*box_dims
,关于anchors
的介绍见下一部分。另一支负责预测每个proposal boxes
的置信度,其输出通道数为num_anchors
。因总的proposal boxes
数里过多,得到置信度
和proposal boxes对应的位置
后,可据此对proposal boxes
进行过滤,正是图中proposals
层做的事情,其介绍见图2,这里是以一个feature map
进行说明的,对于FPN
结构的网络,对不同层级的特征分别进行处理即可。代码实现可以参考detectron2
class StandardRPNHead(nn.Module):
def forward(self, features: List[torch.Tensor]):
"""
Args:
features (list[Tensor]): list of feature maps
Returns:
list[Tensor]: A list of L elements.
Element i is a tensor of shape (N, A, Hi, Wi) representing
the predicted objectness logits for all anchors. A is the number of cell anchors.
list[Tensor]: A list of L elements. Element i is a tensor of shape
(N, A*box_dim, Hi, Wi) representing the predicted "deltas" used to transform anchors
to proposals.
"""
pred_objectness_logits = []
pred_anchor_deltas = []
for x in features:
t = self.conv(x)
pred_objectness_logits.append(self.objectness_logits(t))
pred_anchor_deltas.append(self.anchor_deltas(t))
return pred_objectness_logits, pred_anchor_deltas
3.Anchors
‵Anchors是
Faster RCNN论文中提出的用来更好的回归
bounding boxes`的算法。
_C.MODEL.ANCHOR_GENERATOR.SIZES = [[32, 64, 128, 256, 512]]
# Anchor aspect ratios. For each area given in `SIZES`, anchors with different aspect
# ratios are generated by an anchor generator.
# Format: list[list[float]]. ASPECT_RATIOS[i] specifies the list of aspect ratios (H/W)
# to use for IN_FEATURES[i]; len(ASPECT_RATIOS) == len(IN_FEATURES) must be true,
# or len(ASPECT_RATIOS) == 1 is true and aspect ratio list ASPECT_RATIOS[0] is used
# for all IN_FEATURES.
_C.MODEL.ANCHOR_GENERATOR.ASPECT_RATIOS = [[0.5, 1.0, 2.0]]
如上代码中,分别是5种size
,3
种宽高比的Anchors
配置,Anchors
的大小是在检测输入图像的尺度上的,通过变换可知对于每个点共有15
种不同宽高比和大小的anchors
,
Anchors
是作用在feature map
上的每个cell
中心点的,再根据图像信息和特征提取网络的stride
,找到原图上Anchors
的对应位置。其应用可以参考faster rcnn
论文的一个图,
使用Anchors
时,bounding boxes
回归的原理是anchors
的中心
x
,
y
x,y
x,y和宽高
w
,
h
w,h
w,h通过平移和缩放可以得到对应的bounding box
。给定
a
n
c
h
o
r
A
(
A
x
,
A
y
,
A
w
,
A
h
)
anchor A(A_x, A_y, A_w, A_h)
anchorA(Ax,Ay,Aw,Ah)和
g
t
G
(
G
x
,
G
y
,
G
w
,
G
h
)
gt G(G_x, G_y,G_w, G_h)
gtG(Gx,Gy,Gw,Gh)可以寻找一种变换F使得
F
(
A
x
,
A
y
,
A
w
,
A
h
)
=
G
′
(
G
x
′
,
G
y
′
,
G
w
′
,
G
h
′
)
F(A_x, A_y, A_w, A_h)=G'(G_x', G_y', G_w', G_h')
F(Ax,Ay,Aw,Ah)=G′(Gx′,Gy′,Gw′,Gh′),而
G
≈
G
′
G\approx G'
G≈G′,变换F
可以表示为:
-
先平移
G x ′ = A w ∗ d x ( A ) + A x G y ′ = A h ∗ d x ( A ) + A y \begin{matrix} G_x'=A_w*d_x(A)+A_x \\ G_y'=A_h*d_x(A)+A_y \end{matrix} Gx′=Aw∗dx(A)+AxGy′=Ah∗dx(A)+Ay -
再缩放
G w ′ = A w e x p ( d w ( A ) ) G h ′ = A h e x p ( d h ( A ) ) G_w'=A_w exp(d_w(A)) \\ G_h'=A_h exp(d_h(A)) Gw′=Awexp(dw(A))Gh′=Ahexp(dh(A))
其中
d
x
(
A
)
,
d
y
(
A
)
,
d
w
(
A
)
,
d
h
(
A
)
d_x(A),d_y(A),d_w(A),d_h(A)
dx(A),dy(A),dw(A),dh(A)四个变换,当anchor
与gt box
相差很小时,可看成线性变换即Y=WX
。
上图种蓝色的网格表示feature map
特征图,在其中间一个cell
上生成的一个Anchor
如图中黄色框所示,其中心
A
x
,
A
y
A_x,A_y
Ax,Ay取cell
的中心对应的原图上的坐标,与当前这个对应IoU
最大的ground truth box
如图中红色的框,其中心坐标为
G
x
,
G
y
G_x,G_y
Gx,Gy,可以知道一般情况下Anchor
只是大概定位了检测框的位置,还需对其进行少量的平移才能实现准确定位。同样Anchor
也只是大概确定了检测框的宽高,还需在宽高方向上进行适量的缩放才能得到准确的检测框。
faster rcnn
中的偏移量预测是tx
,没有范围限制,容易导致产生超出边界的预测框,在2016年12月25号Joseph Redmon
发表的Yolov2
中对其进行了修改,改成了在预测相对于featue map cell
左上点的偏移量,并做sigmoid
,使得偏移量始终在
0
−
1
0-1
0−1之间。
对于检测网络训练时,传入A
与Ground Truth Boxes
之间的变换量
t
x
,
t
y
,
t
ω
,
t
h
t_x, t_y, t_\omega,t_h
tx,ty,tω,th,借此使用L1
损失函数回归Y=WX
函数即可完成RPN
的训练。
4.Regionn Proposal Network的训练
训练RPN
网络时,需先将feaure map
经过RPN
前向推理得到shape=(N, Hi*Wi*A)
的pred_objectness_logits scores
和shape=(N, Hi*Wi*A, B)
的pred_anchor_deltas
。然后将shape = [N, H*W*A]
的anchors
和shape=[N, M, B]
的ground truth boxes
对应起来,再计算含有物体的positive target anchors
和predicted anchors
之间的定位损失及positive and negative target anchors
和对应predicted anchors
之间的分类损失。
训练RPN
网络时,比较多的工作花费在了anchor assignment
,即实现anchors
与ground truth box
之间的匹配。faster R-CNN
中主要使用的anchor
与ground truth box
之间的IoU
来实现。对于M
个anchors
和N
个round truth boxes
,两两之间分别计算IoU
,可以得到MxN
的IoU_Match
矩阵,取每个anchor
与N
个gt boxes
IoU最大的box
作为与anchor
匹配的Ground Truth Box
,如此找到了每个anchor
对应的Ground Truth Box
。再根据两者之间的IoU
,判断其是背景bg
还是前景fg
即是否有物体,判断方式通常是设者IoU_Threshold_Low
和IoU_Threshold_High
,IoU
大于IoU_Threshold_High
的是positive
,小于IoU_Threshold_Low
的是negative
,介于两者之间的忽略。通过这样定义可以知道一个ground truth box
可以对应多个anchor
。初步判断出positive/negative anchors
之后,还需经过超参数每个图像中训练最多使用的anchor box数量
和其中positive anchors fraction
对anchors
再次处理,限制参与训练的anchor box
不多于超参数最大数量
,见detectron2 rpn.py中的label_and_sample_anchors函数。
def label_and_sample_anchors(
self, anchors: List[Boxes], gt_instances: List[Instances]
) -> Tuple[List[torch.Tensor], List[torch.Tensor]]:
anchors = Boxes.cat(anchors)
gt_boxes = [x.gt_boxes for x in gt_instances]
image_sizes = [x.image_size for x in gt_instances]
del gt_instances
gt_labels = []
matched_gt_boxes = []
for image_size_i, gt_boxes_i in zip(image_sizes, gt_boxes):
"""
image_size_i: (h, w) for the i-th image
gt_boxes_i: ground-truth boxes for i-th image
"""
match_quality_matrix = retry_if_cuda_oom(pairwise_iou)(gt_boxes_i, anchors)
matched_idxs, gt_labels_i = retry_if_cuda_oom(self.anchor_matcher)(match_quality_matrix)
# M个anchors与N个ground truth boxes匹配,得到M个anchors分别对应的ground truth box,找到每个anchor的标签
gt_labels_i = gt_labels_i.to(device=gt_boxes_i.device)
del match_quality_matrix
if self.anchor_boundary_thresh >= 0:
# Discard anchors that go out of the boundaries of the image
# NOTE: This is legacy functionality that is turned off by default in Detectron2
anchors_inside_image = anchors.inside_box(image_size_i, self.anchor_boundary_thresh)
gt_labels_i[~anchors_inside_image] = -1
# A vector of labels (-1, 0, 1) for each anchor
# 根据超参数,限制每张图像中参与训练的anchors上限和positive anchor的比例
gt_labels_i = self._subsample_labels(gt_labels_i)
if len(gt_boxes_i) == 0:
# These values won't be used anyway since the anchor is labeled as background
matched_gt_boxes_i = torch.zeros_like(anchors.tensor)
else:
# TODO wasted indexing computation for ignored boxes
matched_gt_boxes_i = gt_boxes_i[matched_idxs].tensor
gt_labels.append(gt_labels_i) # N,AHW
matched_gt_boxes.append(matched_gt_boxes_i)
return gt_labels, matched_gt_boxes
损失函数
- 位置回归使用的是
smooth L1 loss
,通过positive mask
实现只取target positive anchor
和对应的predicted anchors
计算regressive loss
s m o o t h L 1 ( x ) = { 0.5 x 2 i f ∣ x ∣ < 1 ∣ x ∣ − 0.5 o t h e r w i s e smooth_{L_1(x)} = \left\{\begin{matrix} 0.5x^2 & if \left | x \right | \lt 1\\ \left | x \right | - 0.5 & otherwise \end{matrix}\right. smoothL1(x)={0.5x2∣x∣−0.5if∣x∣<1otherwise
- 分类损失使用的是
binary cross entropy loss
,只取positive/negative target loss
计算。
L i = y t r u e l o g y p r e d + ( 1 − y t r u e ) l o g ( 1 − y p r e d ) L_i = y_{true}log y_{pred} + (1-y_{true})log(1-y_{pred}) Li=ytruelogypred+(1−ytrue)log(1−ypred)
def losses(
self,
anchors: List[Boxes],
pred_objectness_logits: List[torch.Tensor],
gt_labels: List[torch.Tensor],
pred_anchor_deltas: List[torch.Tensor],
gt_boxes: List[torch.Tensor],
) -> Dict[str, torch.Tensor]:
num_images = len(gt_labels)
gt_labels = torch.stack(gt_labels) # (N, sum(Hi*Wi*Ai))
# Log the number of positive/negative anchors per-image that's used in training
pos_mask = gt_labels == 1
num_pos_anchors = pos_mask.sum().item()
num_neg_anchors = (gt_labels == 0).sum().item()
storage = get_event_storage()
storage.put_scalar("rpn/num_pos_anchors", num_pos_anchors / num_images)
storage.put_scalar("rpn/num_neg_anchors", num_neg_anchors / num_images)
localization_loss = _dense_box_regression_loss(
anchors,
self.box2box_transform,
pred_anchor_deltas,
gt_boxes,
pos_mask,
box_reg_loss_type=self.box_reg_loss_type,
smooth_l1_beta=self.smooth_l1_beta,
)
valid_mask = gt_labels >= 0
objectness_loss = F.binary_cross_entropy_with_logits(
cat(pred_objectness_logits, dim=1)[valid_mask],
gt_labels[valid_mask].to(torch.float32),
reduction="sum",
)
normalizer = self.batch_size_per_image * num_images
losses = {
"loss_rpn_cls": objectness_loss / normalizer,
# The original Faster R-CNN paper uses a slightly different normalizer
# for loc loss. But it doesn't matter in practice
"loss_rpn_loc": localization_loss / normalizer,
}
losses = {k: v * self.loss_weight.get(k, 1.0) for k, v in losses.items()}
return losses
在Fast R-CNN论文中
,bounding boxes回归使用的就是smooth L1 loss
了,与
L
1
,
L
2
L_1,L_2
L1,L2相比,smooth L1
的导数在x
较小时(0-1)
时更敏感,因此可以有更好的收敛效果。
欢迎访问个人网络日志🌹🌹知行空间🌹🌹