在RCNN,Fast RCNN之后,Ross B. Girshick在2016年提出Faster RCNN,將特征提取(feature extraction),proposal提取,目標定位location,目標分類classification整合到了一個網絡中,性能大幅提升。作為Two-stage的代表,相比於yolo,ssd等one-stage檢測方法,Faster RCNN的檢測精度更高,速度相對較慢。
為了加深對Faster RCNN的理解,還是從網絡結構,正負樣本分配,loss函數三個方面來記錄下自己的學習過程。原版Faster RCNN的backbone為VGG16, 而實際工作中,我主要使用Resnet50為backbone的Faster RCNN,這里以Resnet50_Faster_RCNN為例進行說明
1. Resnet50_Faster_RCNN 網絡結構
下面兩張圖中,第一張是Resnet50_Faster_RCNN的網絡結構流程圖,第二張是詳細展開后的網絡卷積模塊。可以發現其網絡結構中主要包括Resnet50 Conv layers,RPN(Region Proposal Network), ROIPooling/ROIAlign, class/box Predictors四個模塊:
1.Resnet50 Conv layers: 包括了Head, Layer1, Layer2, Layer3, Layer4,主要負責特征提取;(Head, Layer1, Layer2, Layer3為最開始的特征提取,layer4是在確定正負樣本后第二次的特征提取) 2.RPN:主要負責proposal的提取,給出每個proposal的屬於正負樣本的分數,並修正其位置; 3.ROIPooling/ROIAlign:根據RPN給出的Proposal,從Feature map中得到每個proposal對應的局部feature,並進行匯總后輸入下一層網絡 4.class/box Predictors: 預測proposal所屬類別(class), 以及其位置偏移量(box regression),從而修正box
圖1 resnet50_faster_rcnn流程結構圖
圖2 resnet50_faster_rcnn網絡結構圖
網絡結構中比較難理解的主要是RPN和ROIPooling/ROIAlign兩部分,需要詳細了解下。
RPN網絡流程
1. 上述圖中Layer3得到的feature為B*1024*36*63,送入RPN網絡首先卷積得到B*1024*36*63,然后score分支預測proposal的概率,尺寸為B*15*36*63(每個位置有15個anchor),loc預測proposal的偏移量,尺寸為B*60*36*63;
2. 根據B*15*36*63的分數置信度,B*60*36*63的位置偏移量,利用已經設置好的34020(36*63*15)個anchor和匹配規則,選擇128個proposal(32個正樣本,96個負樣本)
ROIPooling/ROIAlign流程
(ROIPooling和ROIAlign詳細解釋參見:https://zhuanlan.zhihu.com/p/73138740)
1.上述圖中Layer3得到的feature為B*1024*36*63,RPN網絡得到proposal為B*128*4,對於每一個proposal根據其坐標位置,在feature中找到其對應的局部特征sub_feature(例如1024*20*30),對這個局部特征進行AvgPooling得到1024*14*14。共有B*128個proposal,因此所有proposal處理完成得到尺寸為[B*128, 1024, 14, 14]
2. 正負樣本分配
faster_rcnn網絡結構中有兩次正負樣本分配,一是從設置好的anchor中挑選樣本給RPN網絡學習,從而使RPN網絡預測出精准的proposal;二是從RPN預測出的proposal挑選樣本給RCNN網絡(layer4+predictor部分)學習,使RCNN網絡預測proposal的類別,並修正其坐標位置
2.1 faster_rcnn網絡anchor設置
faster_rcnn論文采用9個anchor(三個尺寸,三個比例),這里采用了15個anchor(5個尺寸,三個比例),增加對小目標的檢測。以網絡layer3輸出feature為基礎,其anchor設置的示意圖如下:
產生anchor的簡單示例代碼如下:

import numpy as np strides=16 # scales=(8, 16, 32) scales=(2,4,8,16,32) ratios=(0.5, 1, 2) alloc_size=(36, 63) scales = [i*strides for i in scales] print(scales) center_point = ((strides-1)/2, (strides-1)/2) anchors = [] for scale in scales: for ratio in ratios: area = scale*scale ws = np.round(np.sqrt(area/ratio)) hs = np.round(np.sqrt(area*ratio)) anchor = [center_point[0]-(ws-1)/2, center_point[1]-(hs-1)/2, center_point[0]+(ws-1)/2, center_point[1]+(hs-1)/2] anchors.append(anchor) print(np.array(anchors))
2.2 RPN網絡正負樣本分配
RPN網絡的目的是給出精確的proposal,其學習樣本來自於設置的anchor。如上一步共設置了34020個anchor,根據這些anchor和gt_box的IOU,挑選出256個anchor作為樣本給RPN網絡學習,anchor挑選流程如下:
1. 去掉anchor中坐標超出圖片邊界的(圖片為562*1000) 2. 計算所有anchor和gt_box的IOU,和gt_box具有最大IOU的anchor為正樣本(無論是否滿足IOU>0.7),剩余的anchor, IOU>0.7的為正樣本,0<IOU<0.3的為負樣本 3. 挑選出256個樣本,正負樣本各128個。(若正樣本不夠128個時,有多少取多少,若正本超過128個,隨機選取128個正樣本,多余的標注未忽略樣本;負樣本一般會多余128個,隨機選取128個負樣本,多余的標注未忽略樣本) (最后會出現兩種情況,一是正負樣本各128個,總共256個樣本;二是正樣本少於128個(如50個),負樣本128個,總樣本少於256個)
2.3 RCNN網絡正負樣本分配
同樣的,對於layer3 feture(36*63), 每個像素點預測15個proposl,因此RPN網絡會預測出34020個proposal,需要從這34020個proposal中選出128個proposal給RCNN網絡的(layer4+predictor)模塊來學習。proposal挑選流程如下:
1.共34020(36*63*15)個proposal,對proposal邊界進行clip,去掉寬高太小(小於16)的proposal; 2.根據預測分數排序,對前12000個proposal進行box_nms(閾值為0.7),nms后選擇排序最前面的2000個proposal 3.rpn_score>0, IOU>0.5的proposal為正樣本,IOU<0.5的為負樣本;rpn_score<=0的為忽略樣本 4.從2000個proposal中挑出128個樣本,正樣本最多32個,負樣本96個(128-32)
3. Loss函數理解
faster_rcnn的損失包括RPN loss和RCNN loss兩部分,都包括置信度和位置偏移量損失;RPN Loss的置信度采用二分類交叉熵損失SigmoidBinaryCrossEntropyLoss,位置偏移量采用SmoothL1;RCNN Loss的置信度采用多分類交叉熵損失SoftmaxCrossEntrophyLoss, 位置偏移量采用SmoothL1。faster_rcnn的訓練策略上,原始論文中采用先訓練RPN網絡(只對RPN Loss進行backward),然后再訓練RCNN網絡(只對RCNN Loss進行backward),現在一般都同時訓練RPN和RCNN,即將RPN Loss和RCNN Loss匯總后一起backward,下面是摘錄的部分源碼,便於理解loss的計算和backwar
#self.rpn_cls_loss =mx.gluon.loss.SigmoidBinaryCrossEntropyLoss(from_sigmoid=False) #self.rpn_box_loss = mx.gluon.loss.HuberLoss(rho=config.rpn_smoothl1_rho) # == smoothl1 #self.rcnn_cls_loss = mx.gluon.loss.SoftmaxCrossEntropyLoss() #self.rcnn_box_loss = mx.gluon.loss.HuberLoss(rho=config.rcnn_smoothl1_rho) # == smoothl1 with autograd.record(): gt_label = label[:, :, 4:5] gt_box = label[:, :, :4] cls_pred, box_pred, _, _, _Z, rpn_score, rpn_box, _, cls_targets, \ box_targets, box_masks, _ = self.net(data, gt_box, gt_label) # losses of rpn rpn_score = rpn_score.squeeze(axis=-1) num_rpn_pos = (rpn_cls_targets >= 0).sum() rpn_loss1 = self.rpn_cls_loss(rpn_score, rpn_cls_targets, rpn_cls_targets >= 0) * rpn_cls_targets.size / num_rpn_pos #rpn_cls_targets中1表示正樣本,0負樣本,-1忽略樣本 rpn_loss2 = self.rpn_box_loss(rpn_box, rpn_box_targets, rpn_box_masks) * rpn_box.size / num_rpn_pos #rpn_box_masks中1表示正樣本,0表示負樣本和忽略樣本 # rpn overall loss, use sum rather than average rpn_loss = rpn_loss1 + rpn_loss2 # losses of rcnn num_rcnn_pos = (cls_targets >= 0).sum() rcnn_loss1 = self.rcnn_cls_loss( cls_pred, cls_targets, cls_targets.expand_dims(-1) >= 0) * cls_targets.size/num_rcnn_pos #cls_targets中1表示正樣本,0負樣本,-1忽略樣本 rcnn_loss2 = self.rcnn_box_loss(box_pred, box_targets, box_masks) * box_pred.size/num_rcnn_pos #box_masks中1表示正樣本,0表示負樣本和忽略樣本 rcnn_loss = rcnn_loss1 + rcnn_loss2 # overall losses total_loss = rpn_loss.sum() * self.mix_ratio + rcnn_loss.sum() * self.mix_ratio total_loss.backward()
參考:https://zhuanlan.zhihu.com/p/273587749?utm_source=wechat_session
https://zhuanlan.zhihu.com/p/82185598?utm_source=wechat_session
https://zhuanlan.zhihu.com/p/123962549
https://zhuanlan.zhihu.com/p/31426458