以前雖然也瞭解一丟丟的 Faster RCNN,但卻一直沒用過,所以一直都是隻知其一;不知其二狀態。這裏結合書中描述和 PyTorch 官方代碼來好好瞅瞅。html
論文: 算法
Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks網絡
Feature Pyramid Networks for Object Detectionapp
一. 總覽
Faster RCNN 從功能模塊來看,可大體分爲 特徵提取,RPN,RoI Pooling,RCNN 四個模塊,這裏代碼上選擇了 ResNet50 + FPN 做爲主幹網絡:ide
model = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained=False)
1.1 特徵提取
這裏不用多說,就是選個合適的 Backbone 罷了,不過爲了提高特徵的判決性,通常會採用 FPN 的結構(自下而上、自上而下、橫向鏈接、卷積融合)。post
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 作最後更細緻的多分類和迴歸了。spa
1.4 RCNN
這裏將 RoI Pooling 獲得的特徵送入後面的網絡中,預測每個 RoI 的分類和邊界框迴歸。3d
二. 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。code
代碼中預約義了 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 過程。區別於以前的 SSD 和 YOLOV3 兩個檢測算法的編解碼方式:
記 $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)