Faster RCNN原理詳解
圖1 Faster RCNN基本結構
Faster RCNN分為4個主要內容:
- Conv layers。作為一種CNN目標檢測方法,Faster RCNN首先使用一組基礎的conv+relu+pooling層提取image的feature maps。該Feature maps被共享用於後續RPN層和全連線層。
- Region Proposal Networks。RPN網路用於生成region proposals,通過softmax判斷anchors屬於foreground or background,再利用bounding box regression修正anchors獲得精確的proposals。
- Roi Pooling。該層收集輸入的feature maps和proposals,綜合這些資訊後提取proposal feature maps,送入後續全連線層判定目標類別。
- Classification。利用proposal feature maps計算proposals的類別,同時再次bbox regression獲得檢測框最終的精確位置。
圖2 faster_rcnn_test.pt網路結構 (pascal_voc/VGG16/faster_rcnn_alt_opt/faster_rcnn_test.pt)
圖2為VGG16模型中的faster rcnn網路結構,可以看到:輸入P*Q的影象,首先縮放至固定大小M*N,然後送入網路;Conv layers中包含了13個conv+13個relu+4個pooling(vgg16:13conv+3fc);RPN網路首先經過3*3卷積,再分別生成foreground anchors和bounding box regressions偏移量,然後計算出proposals;而roi pooling層則利用proposals從feature maps中提取proposal feature送入後續全連線和softmax網路作classification。
1.Conv layers
Conv layers包含了conv,pooling,relu三種層。在Conv layers中:
- 所有的conv層都是:kernel_size=3 ,pad=1, stride=1
- 所有的pooling層都是:kernel_size=2, pad=0, stride=2
畫重點:在Faster RCNN Conv layers中對所有的卷積都做了擴邊處理(pad=1,即填充一圈0),導致原圖變為(M+2)*(N+2)大小,再做3*3卷積後輸出M*N。因此,conv layers中的conv層不改變輸入和輸出矩陣大小。如圖3所示。
圖3 卷積示意圖
類似的是,pooling層kernel_size=2,stride=2。這樣每個經過pooling層的M*N矩陣,都會變為(M/2)*(N/2)大小。綜上所述,在整個Conv layers中,conv和relu層不改變輸入輸出大小,只有pooling層使長寬都變為輸入的1/2。所以,一個M*N大小的矩陣經過Conv layers固定變為(M/16)*(N/16),這樣生成的feature map就可以和原圖對應起來。
2. Region Proposal Networks(RPN)
經典的檢測方法生成檢測框都非常耗時,如opencv adaboost使用滑動視窗+影象金字塔生成檢測框;如R-CNN使用selective search方法生成檢測框。Faster RCNN直接使用RPN生成檢測框,這也是Faster RCNN的巨大優勢,能極大提升檢測框的生成速度。
圖4 RPN網路結構
圖4展示了RPN網路的具體結構。可以看到RPN網路實際分為兩條線,上面一條通過softmax分類anchors獲得foreground和background,線面一條用於計算對於anchors的bbox regression偏移量,以獲得精確的proposal。最後的proposals層則負責綜合foreground anchors和bbox regression偏移量獲取proposals,同時剔除太小和超出邊界的proposals。整個網路到了Proposal layer這裡,相當於完成了目標定位的功能。
2.1 多通道影象卷積基礎知識介紹
- 對於單通道+單卷積核做卷積,圖3已經展示了;
- 對於多通道+多卷積核做卷積,計算方式如下:
圖5 多通道卷積計算方式
如圖5所示,輸入有3個通道,同時有2個卷積核。對於每個卷積核,先在3個輸入通道分別做卷積,再將3個通道結果加起來得到卷積輸出。所以對於某個卷積層,無論輸入影象有多少個通道,輸出影象通道數總是等於卷積核數量!
2.2 anchors
提到RPN,就不能不說anchors。anchors,實際上就是一組由rpn/generate_anchors.py生成的矩形。直接執行作者demo中的generate_anchors.py可以得到以下輸出:
[[ -84. -40. 99. 55.]
[-176. -88. 191. 103.]
[-360. -184. 375. 199.]
[ -56. -56. 71. 71.]
[-120. -120. 135. 135.]
[-248. -248. 263. 263.]
[ -36. -80. 51. 95.]
[ -80. -168. 95. 183.]
[-168. -344. 183. 359.]]
其中每行的4個值(x1, y1, x2, y2) 表矩形左上和右下角點座標。9個矩形共有3種形狀,長寬比為大約為with:height∈{1:1, 1:2, 2:1}三種,(論文:3 scales with box areas of 128,256,512 and 3 aspect ratios of 1:1,1:2,2:1)如圖6。實際上通過anchors就引入了檢測中常用到的多尺度方法。
圖6 anchors示意圖
那麼這9個anchors是做什麼的呢?借用Faster RCNN論文中的原圖,如圖7,遍歷Conv layers計算獲得的feature maps,為每一個點都配備這9種anchors作為初始的檢測框。這樣做獲得檢測框很不準確,不用擔心,後面還有2次bounding box regression可以修正檢測框位置。
圖7 論文中的anchors示意圖
解釋一下上面這張圖的數字。
- 在原文中使用的是ZF model(ZF net)中,其Conv Layers中最後的conv5層num_output=256,對應生成256張特徵圖,所以相當於feature map每個點都是256-dimensions。
- 在conv5之後,做了rpn_conv/3x3卷積且num_output=256,相當於每個點又融合了周圍3x3的空間資訊(猜測這樣做也許更魯棒?反正我沒測試),同時256-d不變(如圖4和圖7中的紅框)。
- 假設在conv5 feature map中每個點上有k個anchor(預設k=9),而每個anhcor要分foreground和background,所以每個點由256d feature轉化為cls=2k scores;而每個anchor都有[x, y, w, h]對應4個偏移量,所以reg=4k coordinates。
- 補充一點,全部anchors拿去訓練太多了,訓練程式會在合適的anchors中隨機選取128個postive anchors+128個negative anchors進行訓練(什麼是合適的anchors下文5.1有解釋)。
- 在本文講解中使用的VGG conv5 num_output=512,所以是512d,其他類似。
其實RPN最終就是在原圖尺度上,設定了密密麻麻的候選anchor,然後用cnn去判斷哪些anchor是含有目標的foreground anchor,哪些是沒目標的background。所以,僅僅是個二分類而已!
那麼Anchor一共有多少個?原圖800x600,VGG下采樣16倍,feature map每個點設定9個Anchor,所以:
其中ceil()表示向上取整,VGG輸出的feature map size=50*38。
圖8 Generate anchors
2.3 softmax判定foreground與background
一副MxN大小的矩陣送入Faster RCNN網路後,到RPN網路變為(M/16)x(N/16),不妨設 W=M/16,H=N/16。在進入reshape與softmax之前,先做了1x1卷積,如圖9:
圖9 RPN中判定fg/bg網路結構
該1x1卷積的caffe prototxt定義如下:
layer {
name: "rpn_cls_score"
type: "Convolution"
bottom: "rpn/output"
top: "rpn_cls_score"
convolution_param {
num_output: 18 # 2(bg/fg) * 9(anchors)
kernel_size: 1 pad: 0 stride: 1
}
}
可以看到其num_output=18,也就是經過該卷積的輸出影象為WxHx18大小(注意第二章開頭提到的卷積計算方式)。這也就剛好對應了feature maps每一個點都有9個anchors,同時每個anchors又有可能是foreground和background,所有這些資訊都儲存WxHx(9*2)大小的矩陣。為何這樣做?後面接softmax分類獲得foreground anchors,也就相當於初步提取了檢測目標候選區域box(一般認為目標在foreground anchors中)。 那麼為何要在softmax前後都接一個reshape layer?其實只是為了便於softmax分類,至於具體原因這就要從caffe的實現形式說起了。在caffe基本資料結構blob中以如下形式儲存資料:
blob=[batch_size, channel,height,width]
對應至上面的儲存bg/fg anchors的矩陣,其在caffe blob中的儲存形式為[1, 2x9, H, W]。而在softmax分類時需要進行fg/bg二分類,所以reshape layer會將其變為[1, 2, 9xH, W]大小,即單獨“騰空”出來一個維度以便softmax分類,之後再reshape回覆原狀。貼一段caffe softmax_loss_layer.cpp的reshape函式的解釋,非常精闢:
"Number of labels must match number of predictions; "
"e.g., if softmax axis == 1 and prediction shape is (N, C, H, W), "
"label count (number of labels) must be N*H*W, "
"with integer values in {0, 1, ..., C-1}.";
綜上所述,RPN網路中利用anchors和softmax初步提取出foreground anchors作為候選區域。
2.4 bounding box regression原理
如圖10所示綠色框為飛機的Ground Truth(GT),紅色為提取的foreground anchors,即便紅色的框被分類器識別為飛機,但是由於紅色的框定位不準,這張圖相當於沒有正確的檢測出飛機。所以我們希望採用一種方法對紅色的框進行微調,使得foreground anchors和GT更加接近。
圖10 anchor與ground truth
對於視窗一般使用四維向量 (x, y, w, h)表示,分別表示視窗的中心點座標和寬高。對於圖 11,紅色的框A代表原始的Foreground Anchors,綠色的框G代表目標的GT,我們的目標是尋找一種關係,使得輸入原始的anchor A經過對映得到一個跟真實視窗G更接近的迴歸視窗G',即:
- 給定:anchor 和
- 尋找一種變換F,使得:,其中
圖11
那麼經過何種變換F才能從圖10中的anchor A變為G'呢? 比較簡單的思路就是:
- 先做平移
- 再做縮放
觀察上面4個公式發現,需要學習的是 這四個變換。當輸入的anchor A與GT相差較小時,可以認為這種變換是一種線性變換, 那麼就可以用線性迴歸來建模對視窗進行微調(注意,只有當anchors A和GT比較接近時,才能使用線性迴歸模型,否則就是複雜的非線性問題了)。 接下來的問題就是如何通過線性迴歸獲得 了。線性迴歸就是給定輸入的特徵向量X, 學習一組引數W, 使得經過線性迴歸後的值跟真實值Y非常接近,即。對於該問題,輸入X是cnn feature map,定義為Φ;同時還有訓練傳入A與GT之間的變換量,即。輸出是四個變換。那麼目標函式可以表示為:
其中Φ(A)是對應anchor的feature map組成的特徵向量,w是需要學習的引數,d(A)是得到的預測值(*表示 x,y,w,h,也就是每一個變換對應一個上述目標函式)。為了讓預測值與真實值差距最小,設計損失函式:
函式優化目標為:
需要說明,只有在GT與需要回歸框位置比較接近時,才可近似認為上述線性變換成立。 說完原理,對應於Faster RCNN原文,foreground anchor與ground truth之間的平移量 與尺度因子 如下:
其中,x,y,w,h denotes the box's center coordinates and its width and height.x,xa and x* are for the predicted box,anchor box and ground-truth box respectively(likewise for y,w,h).對於訓練bouding box regression網路迴歸分支,輸入是cnn feature Φ,監督訊號是Anchor與GT的差距 ,即訓練目標是:輸入 Φ的情況下使網路輸出與監督訊號儘可能接近。 那麼當bouding box regression工作時,再輸入Φ時,迴歸網路分支的輸出就是每個Anchor的平移量和變換尺度 ,顯然即可用來修正Anchor位置了。
2.5 對proposals進行bounding box regression
在瞭解bounding box regression後,再回頭來看RPN網路第二條線路,如圖12。
圖12 RPN中的bbox reg
先來看一看上圖11中1x1卷積的caffe prototxt定義:
layer {
name: "rpn_bbox_pred"
type: "Convolution"
bottom: "rpn/output"
top: "rpn_bbox_pred"
convolution_param {
num_output: 36 # 4 * 9(anchors)
kernel_size: 1 pad: 0 stride: 1
}
}
可以看到其 num_output=36,即經過該卷積輸出影象為WxHx36,在caffe blob儲存為[1, 4x9, H, W],這裡相當於feature maps每個點都有9個anchors,每個anchors又都有4個用於迴歸的變換量。
2.6 Proposal Layer
Proposal Layer負責綜合所有 變換量和foreground anchors,計算出精準的proposal,送入後續RoI Pooling Layer。還是先來看看Proposal Layer的caffe prototxt定義:
layer {
name: 'proposal'
type: 'Python'
bottom: 'rpn_cls_prob_reshape'
bottom: 'rpn_bbox_pred'
bottom: 'im_info'
top: 'rois'
python_param {
module: 'rpn.proposal_layer'
layer: 'ProposalLayer'
param_str: "'feat_stride': 16"
}
}
Proposal Layer有3個輸入:fg/bg anchors分類器結果rpn_cls_prob_reshape,對應的bbox reg的變換量rpn_bbox_pred,以及im_info;另外還有引數feat_stride=16,這和圖4是對應的。 首先解釋im_info。對於一副任意大小PxQ影象,傳入Faster RCNN前首先reshape到固定MxN,im_info=[M, N, scale_factor]則儲存了此次縮放的所有資訊。然後經過Conv Layers,經過4次pooling變為WxH=(M/16)x(N/16)大小,其中feature_stride=16則儲存了該資訊,用於計算anchor偏移量。
圖13
Proposal Layer forward(caffe layer的前傳函式)按照以下順序依次處理:
- 生成anchors,利用對所有的anchors做bbox regression迴歸(這裡的anchors生成和訓練時完全一致)
- 按照輸入的foreground softmax scores由大到小排序anchors,提取前pre_nms_topN(e.g. 6000)個anchors,即提取修正位置後的foreground anchors。
- 限定超出影象邊界的foreground anchors為影象邊界(防止後續roi pooling時proposal超出影象邊界)
- 剔除非常小(width<threshold or height<threshold)的foreground anchors
- 進行nonmaximum suppression
- 再次按照nms後的foreground softmax scores由大到小排序fg anchors,提取前post_nms_topN(e.g. 300)結果作為proposal輸出。
之後輸出proposal=[x1, y1, x2, y2],注意,由於在第三步中將anchors映射回原圖判斷是否超出邊界,所以這裡輸出的proposal是對應MxN輸入影象尺度的,這點在後續網路中有用。另外我認為,嚴格意義上的檢測應該到此就結束了,後續部分應該屬於識別了~ RPN網路結構就介紹到這裡,總結起來就是:生成anchors -> softmax分類器提取fg anchors -> bbox reg迴歸fg anchors -> Proposal Layer生成proposals
3. ROI Pooling
而RoI Pooling層則負責收集proposal,並計算出proposal feature maps,送入後續網路。從圖2中可以看到Rol pooling層有2個輸入:
3.1 為何需要ROI Pooling
- 原始的feature maps
- RPN輸出的proposal boxes(大小各不相同
先來看一個問題:對於傳統的CNN(如AlexNet,VGG),當網路訓練好後輸入的影象尺寸必須是固定值,同時網路輸出也是固定大小的vector or matrix。如果輸入影象大小不定,這個問題就變得比較麻煩。有2種解決辦法:
- 從影象中crop一部分傳入網路
- 將影象warp成需要的大小後傳入網路
圖14 影象crop與wrap破壞結構資訊
兩種辦法的示意圖如圖14,可以看到無論採取那種辦法都不好,要麼crop後破壞了影象的完整結構,要麼warp破壞了影象原始形狀資訊。 回憶RPN網路生成的proposals的方法:對foreground anchors進行bounding box regression,那麼這樣獲得的proposals也是大小形狀各不相同,即也存在上述問題。所以Faster R-CNN中提出了RoI Pooling解決這個問題。不過RoI Pooling確實是從Spatial Pyramid Pooling發展而來,但是限於篇幅這裡略去不講,有興趣的讀者可以自行查閱相關論文。
3.2 ROI Pooling原理
分析之前先來看看RoI Pooling Layer的caffe prototxt的定義:
layer {
name: "roi_pool5"
type: "ROIPooling"
bottom: "conv5_3"
bottom: "rois"
top: "pool5"
roi_pooling_param {
pooled_w: 7
pooled_h: 7
spatial_scale: 0.0625 # 1/16
}
}
其中有新引數 ,另外一個引數 認真閱讀的讀者肯定已經知道知道用途。 RoI Pooling layer forward過程:在之前有明確提到: 是對應MxN尺度的,所以首先使用spatial_scale引數將其映射回(M/16)x(N/16)大小的feature maps尺度;之後將每個proposal水平和豎直分為pooled_w和pooled_h份,對每一份都進行max pooling處理。這樣處理後,即使大小不同的proposal,輸出結果都是 大小,實現了fixed-length output(固定長度輸出)。
圖15 proposal示意圖
4 Classification
Classification部分利用已經獲得的proposal feature maps,通過full connect層與softmax計算每個proposal具體屬於那個類別(如人,車,電視等),輸出cls_prob概率向量;同時再次利用bounding box regression獲得每個proposal的位置偏移量bbox_pred,用於迴歸更加精確的目標檢測框。Classification部分網路結構如圖16。
圖16 Classification 部分網路結構圖
從PoI Pooling獲取到7x7=49大小的proposal feature maps後,送入後續網路,可以看到做了如下2件事:
- 通過全連線和softmax對proposals進行分類,這實際上已經是識別的範疇了
- 再次對proposals進行bounding box regression,獲取更高精度的rect box
這裡來看看全連線層InnerProduct layers,簡單的示意圖如圖17,
圖17 全連線層示意圖
其計算公式如下:
其中W和bias B都是預先訓練好的,即大小是固定的,當然輸入X和輸出Y也就是固定大小。所以,這也就印證了之前Roi Pooling的必要性。
5 Faster rcnn訓練
Faster R-CNN的訓練,是在已經訓練好的model(如VGG_CNN_M_1024,VGG,ZF)的基礎上繼續進行訓練。實際中訓練過程分為6個步驟:
- 在已經訓練好的model上,訓練RPN網路,對應stage1_rpn_train.pt
- 利用步驟1中訓練好的RPN網路,收集proposals,對應rpn_test.pt
- 第一次訓練Fast RCNN網路,對應stage1_fast_rcnn_train.pt
- 第二訓練RPN網路,對應stage2_rpn_train.pt
- 再次利用步驟4中訓練好的RPN網路,收集proposals,對應rpn_test.pt
- 第二次訓練Fast RCNN網路,對應stage2_fast_rcnn_train.pt
可以看到訓練過程類似於一種“迭代”的過程,不過只迴圈了2次。至於只迴圈了2次的原因是應為作者提到:"A similar alternating training can be run for more iterations, but we have observed negligible improvements",即迴圈更多次沒有提升了。接下來本章以上述6個步驟講解訓練過程。
下面是一張訓練過程流程圖,應該更加清晰。
圖18 Faster RCNN訓練步驟
5.1 訓練RPN網路
圖19 stage1_rpn_train.pt(考慮圖片大小,Conv Layers中所有的層都畫在一起了,如紅圈所示,後續圖都如此處理)
在該步驟中,首先讀取RBG提供的預訓練好的model(本文使用VGG),開始迭代訓練。來看看stage1_rpn_train.pt網路結構,如圖19。
與檢測網路類似的是,依然使用Conv Layers提取feature maps。整個網路使用的Loss如下:
上述公式中, 表示anchors index, 表示foreground softmax probability,代表對應的GT predict概率(即當第i個anchor與GT間 ,認為是該anchor是foreground,;反之 時,認為是該anchor是background,;至於那些 的anchor則不參與訓練);代表predict bounding box,代表對應foreground anchor對應的GT box。可以看到,整個Loss分為2部分:
- cls loss,即rpn_cls_loss層計算的softmax loss,用於分類anchors為forground與background的網路訓練
- reg loss,即rpn_loss_bbox層計算的soomth L1 loss,用於bounding box regression網路訓練。注意在該loss中乘了 ,相當於只關心foreground anchors的迴歸(其實在迴歸中也完全沒必要去關心background)。
由於在實際過程中,和差距過大,用引數λ平衡二者(如,時設定 ),使總的網路Loss計算過程中能夠均勻考慮2種Loss。這裡比較重要是 使用的soomth L1 loss,計算公式如下:
瞭解數學原理後,反過來看圖18:
- 在RPN訓練階段,rpn-data(python AnchorTargetLayer)層會按照和test階段Proposal層完全一樣的方式生成Anchors用於訓練
- 對於rpn_loss_cls,輸入的rpn_cls_scors_reshape和rpn_labels分別對應與 , 引數隱含在與的caffe blob的大小中
- 對於rpn_loss_bbox,輸入的rpn_bbox_pred和rpn_bbox_targets分別對應於,rpn_bbox_inside_weigths對應,rpn_bbox_outside_weigths未用到(從soomth_L1_Loss layer程式碼中可以看到),而 同樣隱含在caffe blob大小中
這樣,公式與程式碼就完全對應了。特別需要注意的是,在訓練和檢測階段生成和儲存anchors的順序完全一樣,這樣訓練結果才能被用於檢測!
5.2 通過訓練好的RPN網路收集proposals
在該步驟中,利用之前的RPN網路,獲取proposal rois,同時獲取foreground softmax probability,如圖20,然後將獲取的資訊儲存在python pickle檔案中。該網路本質上和檢測中的RPN網路一樣,沒有什麼區別。
圖20 rpn_test.pt
5.3 訓練Faster RCNN網路
讀取之前儲存的pickle檔案,獲取proposals與foreground probability。從data層輸入網路。然後:
- 將提取的proposals作為rois傳入網路,如圖19藍框
- 計算bbox_inside_weights+bbox_outside_weights,作用與RPN一樣,傳入soomth_L1_loss layer,如圖20綠框
這樣就可以訓練最後的識別softmax與最終的bounding box regression了,如圖21。
圖21 stage1_fast_rcnn_train.pt
之後的stage2訓練都是大同小異,不再贅述了。Faster R-CNN還有一種end-to-end的訓練方式,可以一次完成train,有興趣請自己看作者GitHub吧。