rpn和roi align是two-stage detector中比較關鍵的兩個操作,這兩個操作将two-stage detector中的兩個stage連接配接起來,變成end-to-end(端到端)的網絡,同時也給整個檢測方法的性能帶來提升。rpn為roi align提供高品質的候選框,即proposal,關系如圖1所示:
下面詳細說明一下各個過程,并且配合了代碼的說明,其中代碼來自facebook的detectron2,其中相關參數的配置檔案可以參考detectron2/config/defaults.py
一、RPN
rpn全稱是region proposal network,作用是為第二階段提供高品質的目标候選框,如圖1所示,包括了anchor generator、anchor target generator、rpn loss、proposal generator幾個關鍵的步驟,下面分别詳細說明。
1,anchor generator
Anchor是根據scale和ratio預先設定的,這些參數在全特征圖中共享,如下圖所示。左邊表示使用stride=16的feature map作為目标檢測的特征圖,對于輸入大小為800*600的圖像,該特征圖大小為50*38。其中anchor的scale包括為(8,16,32),ratio參數包括(0.5,1,2),故産生9種anchor;右圖表示對于50*38大小的特征圖,共産生17100個anchor,平鋪到原圖時,有一部分框會超出圖像邊界。
步驟及代碼實作:
detectron2/modeling/anchor_generator.py:
grid_anchors(self, grid_sizes)
1)根據scale和ratio産生,預先生成n種anchor,這n種anchor所在坐标系為以anchor中心為原點的圖像坐标系。
generate_cell_anchors(self, sizes=(32, 64, 128, 256, 512), aspect_ratios=(0.5, 1, 2))
2)在特征圖中的每個坐标點處,計算anchor中心與該坐标的偏移
_create_grid_offsets(size, stride, offset, device)
3)通過anchor與生成的相對偏移,計算每張特征圖中的所有anchor
anchors.append((shifts.view(-1, 1, 4) + base_anchors.view(1, -1, 4)).reshape(-1, 4))
2,anchor target generator
有了anchor,這些anchor是均勻地平鋪在特征圖的每個像素處,但是我們不知道哪些anchor是包括真實目标的,是以anchor target layer就完成區分哪些anchor是為正樣本(包括真實目标),哪些anchor為負樣本(隻包括背景)的任務,具體方法是計算anchor與ground truth的IoU,評判标準有3條:對于每一個ground truth,選取一個與之有最大IoU的anchor作為正樣
對于每一個anchor,與ground truth的IoU大于某一個門檻值t1的anchor,作為正樣本。
并不是除了以上兩條的anchor為負樣本,而是與ground truth的IoU小于某一個門檻值t2的anchor為負樣本,[t2,t1]之間的樣本為“don’t care”樣本,既不是正樣本也不是負樣本,不參與模型優化,即:不計算rpn loss
第一條保證了每個ground truth都有一個anchor與之相對應,第二條保證了在衆多的anchor中,可以篩選出一定資料量的anchor作為正樣本,保證正負樣本的平衡。
步驟及代碼實作:
detectron2/modeling/proposal_generator/rpn_outputs.py
detectron2/modeling/matcher.py
_get_ground_truth(self)
1)生成anchor與ground truth的IoU矩陣
match_quality_matrix=retry_if_cuda_oom(pairwise_iou)(gt_boxes_i,anchors_i)
2)根據評判标準,生成2組向量:anchor與ground truth比對ID,用于bbox(bounding box回歸);anchor的label:正樣本、負樣本、don’t care。
matched_idxs, gt_objectness_logits_i =retry_if_cuda_oom(self.anchor_matcher)(match_quality_matrix )
3)是否删除超過圖像大小的anchor
if self.boundary_threshold>= 0:
# Discard anchors that go outof the boundaries of the image
# NOTE: This is legacyfunctionality that is turned off by default in Detectron2
anchors_inside_image =anchors_i.inside_box(image_size_i, self.boundary_threshold)
gt_objectness_logits_i[~anchors_inside_image] = -1
3,RPN Loss
rpn有兩個任務:從衆多anchor中,判斷哪些anchor是正樣本,哪些是負樣本,即分類任務;對于正樣本的anchor,回歸獲得真正的目标,即回歸任務。是以loss由兩部分組成,即:
其中分類任務,使用交叉熵loss:
回歸任務使用smooth_l1 loss:
代碼實作:
detectron2/modeling/proposal_generator/rpn_outputs.py
detectron2/modeling/box_regression.py
losses(self)
1)分類loss
objectness_loss =F.binary_cross_entropy_with_logits(
pred_objectness_logits[valid_masks],
gt_objectness_logits[valid_masks].to(torch.float32),
reduction="sum",
)
2)回歸loss
localization_loss =smooth_l1_loss(
pred_anchor_deltas[pos_masks],gt_anchor_deltas[pos_masks], smooth_l1_beta, reduction="sum"
)
4,proposal generator
獲得候選框的目的是為了給第二階段提供優質的roi框,首先通過rpn_cls_prob篩選出topk_rpn_pre_nms個框,然後再經過nms得到topk_rpn_post_nms個框,最終輸出給roi align。主要流程如下圖。
主要步驟和相應代碼實作如下:
detectron2/modeling/proposal_generator/rpn_outputs.py
find_top_rpn_proposals(
proposals,
pred_objectness_logits,
images,
nms_thresh,
pre_nms_topk,
post_nms_topk,
min_box_side_len,
training,
)
1)擷取top_k_pre_nms個候選框
logits_i, idx =logits_i.sort(descending=True, dim=1)
topk_scores_i = logits_i[batch_idx,:num_proposals_i]
topk_idx = idx[batch_idx,:num_proposals_i]
# each is N x topk
topk_proposals_i =proposals_i[batch_idx[:, None], topk_idx] # N x topk x 4
2)對候選框做一些後處理,如:截斷超出圖像範圍的框;删除非常小的框。
boxes.clip(image_size)
# filter empty boxes
keep =boxes.nonempty(threshold=min_box_side_len)
3)nms篩選得到更可信的top_k_post_nms個候選框作為roi
keep = batched_nms(boxes.tensor, scores_per_img, lvl, nms_thresh)
keep = keep[:post_nms_topk]
二、ROI Align
這個階段是在rpn提供的proposal的基礎上,篩選出第二階段的訓練樣本,并提取相應的特征,用于組建第二階段的訓練網絡,主要包括兩個部分:proposal target generator、feature crop and pooling
1,proposal target generator
這個操作的主要目的是: 在rpn産生的proposal的基礎上,選擇一定量(min_batch: 一般每張圖選擇256個proposal,或者512個proposal)的roi,作為訓練第二階段的樣本,并且要設定該min_batch中正負樣本的比例,如正:負=1:3。
主要步驟與代碼實作如下:
detectron2/modeling/roi_heads/roi_heads.py
detectron2/modeling/sampling.py
label_and_sample_proposals(
self, proposals: List[Instances], targets: List[Instances]
) -> List[Instances]:
1)判斷proposal中,哪些是正樣本,哪些是負樣本
match_quality_matrix =pairwise_iou(
targets_per_image.gt_boxes,proposals_per_image.proposal_boxes
)
matched_idxs, matched_labels =self.proposal_matcher(match_quality_matrix)
2)篩選min_batch的樣本,并給正樣本賦予正确的目标類别
sampled_idxs, gt_classes =self._sample_proposals(
matched_idxs, matched_labels,targets_per_image.gt_classes
)
# Set target attributes of thesampled proposals:
proposals_per_image =proposals_per_image[sampled_idxs]
proposals_per_image.gt_classes =gt_classes
gt_boxes = Boxes(
targets_per_image.gt_boxes.tensor.new_zeros((len(sampled_idxs), 4))
)
proposals_per_image.gt_boxes =gt_boxes
2,feature crop and pooling
得到roi之後,根據roi大小,需要選擇合适的特征層crop并pooling得到固定大小的feature map,這個過程稱為roi align。原始的roi pooling也可以完成這個操作,但是由于計算過程使用取整操作,造成特征粗糙,對小目标檢測效果不好,取整操作主要展現在以下兩步:将roi邊界量化為整數點坐标值,然後再選擇合适feature map進行crop。
将crop得到的區域平均分割成 k x k 個單元(bin),在擷取每個單元的左右邊界時取整
Roi align的步驟及相應代碼如下,其中所有操作均為浮點數操作:
detectron2/modeling/roi_heads/roi_heads.py
detectron2/modeling/poolers.py
detectron2/layers/roi_align.py
1)計算rpn得到的roi([x1, y1, x2, y2])在相應的特征層box,即:[x1, y1, x2, y2]/stride。stride為特征圖相對輸入圖像縮小的倍數。
pooler_fmt_boxes= convert_boxes_to_pooler_format(box_lists)
2)特征crop并pooling得到目标大小輸出的特征圖。
output[inds] =pooler(x_level, pooler_fmt_boxes_level)