深度學習筆記(十六)Faster RCNN + FPN (PyTorch)


之前雖然也了解一丟丟的 Faster RCNN,但卻一直沒用過,因此一直都是一知半解狀態。這里結合書中描述和 PyTorch 官方代碼來好好瞅瞅。

論文:    

Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks

Feature Pyramid Networks for Object Detection

一. 總覽

Faster RCNN 從功能模塊來看,可大致分為 特征提取RPNRoI PoolingRCNN 四個模塊,這里代碼上選擇了 ResNet50 + FPN 作為主干網絡:

model = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained=False)

1.1 特征提取

這里不用多說,就是選個合適的 Backbone 罷了,不過為了提升特征的判決性,一般會采用 FPN 的結構(自下而上、自上而下、橫向連接、卷積融合)。

1.2 RPN

這部分其實可以看成 One-Stage 檢測器的檢測輸出部分。實際上對於只檢測一類目標來說,可以直接拿去用了。RPN 在 Faster RCNN 中的作用是,結合先驗的 Anchor,將背景和前景區分開來(二分類),這樣的話大量的先驗 Anchor 就可以被篩選出來,並作些許的回歸(使得 Anchor 更接近於真實目標)。

1.3 RoI Pooling

這部分將結合上一步得到的 refine 后的 Anchor 和特征提取網絡中的 feature map。將這些 Anchor 映射到 feature map 上並通過 Pooling 操作將這些代表 anchor 的 feature 拉到同一維度。這樣的話就可以拿去給 RCNN 做最后更細致的多分類和回歸了。

1.4 RCNN

這里將 RoI Pooling 得到的特征送入后面的網絡中,預測每一個 RoI 的分類和邊界框回歸。

二. RPN

2.1 Anchor Generator

 以官方 PyTorch torchvision 里的 Faster RCNN 代碼為例:輸入圖片尺度為 768x1344,5 個 feature map 分別經過了 stride=(4, 8, 16, 32, 64),得到了 5 個大小為 (192x336, 96x168, 48x84, 24x42, 12x21) 的 feature。

代碼中預定義了 5 個尺度(32, 64, 128, 256, 512) ,3 種 aspect_ratio (0.5, 1.0, 2.0) 的 Anchor。這樣的話我們可以得到 5 組 base_anchor, 每一組包含 3 個面積相同,寬高比不同的以原點為中心點的基礎錨框。

[-23., -11., 23., 11.]    [-45., -23., 45., 23.]    [-91., -45., 91., 45.]
[-16., -16., 16., 16.]    [-32., -32., 32., 32.]   [-64., -64., 64., 64.]
[-11., -23., 11., 23.]    [-23., -45., 23., 45.]    [-45., -91., 45., 91.]


[-181., -91., 181., 91.]       [-362., -181., 362., 181.]
[-128., -128., 128., 128.]   [-256., -256., 256., 256.]
[ -91., -181., 91., 181.]      [-181., -362., 181., 362.]

然后將這些 base_anchor 撒到對應的 feature map 上(起點是 [0,0])。這樣的話共有:

(192*336*3=193536) + (96x168*3=48384) + (48*84*3=12096) + (24*42*3=3024) + (12*21*3=756) = 257796 個 Anchor

具體參考 torchvision/models/detection/rpn.py

anchors = self.anchor_generator(images, features)

2.2 RPN Head

這部分很簡單,就是將特征提取網絡獲得的 feature map 先經過一個 1x1 的卷積操作,然后分別用兩個 3x3 的卷積進行分類和回歸操作。以大小是 256x48x84 的 feature map 為例,經過 1x1 的卷積操作(不改變特征圖尺寸),假定 feature map 上的寬高平面上每個點有 3 個 Anchor (寬高比分別是 0.5, 1.0, 2.0),那個分類支路上的 3x3 卷積輸出維度是 3x48x84,而回歸支路上的 3x3 卷積輸出維度是 12x48x84。

 有多少 Anchor 就有多少分類和回歸結果,最終多個尺度(FPN 5 個尺度)cancat 后的分類維度為 257796x1, 回歸維度為 257796x4。值得注意的是這里的回歸結果是基於編碼后的 Anchor 的偏移量。說道這里就要將下 Faster RCNN 里的 encode 和 decode 過程。區別於之前的 SSDYOLOV3 兩個檢測算法的編解碼方式:

記  $x, y, w, h$ 是檢測框的中心點坐標和寬高,$x, x_a, x^*$ 分別代表檢測框、Anchor 和 GT 的對象坐標, $t_x$ 是檢測偏移量。

解碼:

參考 torchvision/models/detection/rpn.py

proposals = self.box_coder.decode(pred_bbox_deltas.detach(), anchors)

\begin{equation}
\label{decode}
\begin{split}
& x =  t_x * w_a + x_a \\
& y =  t_y * h_a + y_a \\
& w =  e^{t_w} * w_a \\
& h =  e^{t_h} * h_a \\
\end{split}
\end{equation}

同理,在計算 loss 時我們需要將 GT 進行編碼

編碼:

參考 torchvision/models/detection/rpn.py

regression_targets = self.box_coder.encode(matched_gt_boxes, anchors)

\begin{equation}
\label{encode}
\begin{split}
& t_x^* = (x^* - x_a) / w_a \\
& t_y^* = (y^* - y_a) / h_a \\
& t_w^* = log(\frac{w^*}{w_a}) \\
& t_h^* = log(\frac{h^*}{h_a}) \\
\end{split}
\end{equation}

解碼后就將 Anchor + BBox_reg 轉換成了 Proposal 了, 注意每個 Proposal 的物理意義是輸入圖片上的(xmin, ymin, xmax, ymax) 。

 2.3 篩選 Proposal

首先每個檢測尺度上篩選出前 min(pre_nms_top_n, num_anchors) 個 anchor(上面 257796 個 Proposal 就會篩選出 4756 個),用 scale_level 來標記每個 Proposal 的尺度等級,值域集合為 {0, 1, 2, 3, 4};

隨后對這些篩選出來的 Proposal, 按照 clip, remove_small_boxes, 每個尺度上分別做 nms(具體實現時是把所有的 Proposal + (scale_level * Proposal.max()) 來加速操作的)至多保留 post_nms_top_n 個 Proposal。

具體參考  torchvision/models/detection/rpn.py

boxes, scores = self.filter_proposals(proposals, objectness, images.image_sizes, num_anchors_per_level)

三. RoI Pooling

 有了篩選出來的 Proposal,我們就可以將映射到某個 feature map 上然后利用 RoI Pooling 提取每個 Proposal 的特征供后續的 rcnn 細致的分類和回歸了。

參考 torchvision/models/detection/roi_heads.py

box_features = self.box_roi_pool(features, proposals, image_shapes)

代碼中只選擇了其中 4 個尺度來進行操作(最小的那個 feature map 沒有用到)。

因為是多尺度特征,把每個 Proposal 映射到哪個 feature_map 上是個問題,代碼是用 LevelMapper 這玩意來操作的, 理論參考 RPN 論文 。

\begin{equation}
\label{level}
k = \lfloor k_0 + log2(\sqrt{wh}/224) \rfloor
\end{equation}

其中 $k_0$ 是一個面積為 224*224 的 Proposal 應該處於的 feature map level。其他尺度的 Proposal 按照上面的公式安排 feature map level。
代碼中選擇的 256x48x84 這個尺度的 feature map 為 $k_0$, 對應 224*224 這個大小范圍的 Proposal。

隨后使用 roi_align 提取每個 Proposal 的特征

參考 torchvision/ops/poolers.py

result_idx_in_level = roi_align(
                per_level_feature, rois_per_level,
                output_size=self.output_size,
                spatial_scale=scale, sampling_ratio=self.sampling_ratio)

這樣的話 我們就可以獲得 post_nms_top_n 個  256 x 7 x7 維度的前景框的特征。最后 flatten 特征維度接上兩個全連接層獲得 post_nms_top_n 個 1024 維度的特征。

四. RCNN

這部分很簡單,就是兩個全連接層,一個用於分類,一個用於回歸。

參考 torchvision/models/detection/faster_rcnn.py 里的 class FastRCNNPredictor(nn.Module)

self.cls_score = nn.Linear(in_channels, num_classes)
self.bbox_pred = nn.Linear(in_channels, num_classes * 4)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM