算法發展:

R-CNN:把2000個建議框,分別送入網絡

Fast-RCNN:把圖片送入網絡中,再把2000個建議框映射到網絡訓練出來的feature map上

Faster-RCNN:利用RPN選取300建議框,加入ROI層,ROI pooling層能實現訓練和測試的顯著加速,並提高檢測的正確率。

算法框架:

fastrcnn權重_fastrcnn權重

 


算法流程:

fastrcnn權重_ios_02

  1. 輸入圖片(224x224x3)
  2. 整張圖輸入CNN,Faster RCNN首先使用一組基礎的conv+relu+pooling層提取input image的feature maps,該feature maps會用於後續的RPN層和全連接層
  3. RPN:利用RPN網絡生成建議框(Anchor box),每張圖片生成300個建議框窗口(包括IoU和NMS),對這些建議框進行裁剪過濾(reshape)後通過softmax判斷anchors屬於前景(foreground)或者後景(background),即是物體or不是物體,所以這是一個二分類;同時,另一分支bounding box regression修正anchor box,形成較精確的proposal
  4. RoI pooling層:該層把RPN生成的proposals映射到VGG16最後一層得到的feature map得到固定大小的proposal feature map,進入到後面可利用全連接操作來進行目標識別和定位
  5. Classifier:將Roi Pooling層形成固定大小的feature map進行全連接操作,利用Softmax Loss進行具體類別的分類,同時,利用SmoothL1 Loss完成bounding box regression迴歸操作獲得物體的精確位置

算法細節:

1.Conv layers

Faster RCNN首先是支持輸入任意大小的圖片,進入網絡之前對圖片進行了規整化尺度的設定,如可設定圖像短邊不超過600,圖像長邊不超過1000,我們可以假定M*N=1000*600(如果圖片少於該尺寸,可以邊緣補0,即圖像會有黑色邊緣)。

VGG16:(2+2+3+3+3)的連續卷積塊

  • 13個conv層:kernel_size=3,pad=1,stride=1;
  • 13個relu層:激活函數,不改變圖片大小
  • 4個pooling層:kernel_size=2,stride=2;pooling層會讓輸出圖片是輸入圖片的1/2

經過Conv layers,圖片大小變成(M/16)*(N/16),即:60*40(1000/16≈60,600/16≈40);則,Feature Map就是60*40*512

注:VGG16是512-d,ZF是256-d,表示特徵圖的大小為60*40,深度為512

2.RPN

RPN-Anchor box 生成:

經過Conv layers後,圖片大小變成了原來的1/16,令feat_stride=16,在生成Anchors時,我們先定義一個base_anchor,大小為16*16的box(因為特徵圖(60*40)上的一個點,可以對應到原圖(1000*600)上一個16*16大小的區域),源碼中轉化為[0,0,15,15]的數組,參數ratios=[0.5, 1, 2]scales=[8, 16, 32]

[0,0,15,15],面積保持不變,長、寬比分別為[0.5, 1, 2]是產生的Anchors box:

fastrcnn權重_寬高_03

經過scales變化,即長、寬分別均為 (16*8=128)、(16*16=256)、(16*32=512),對應anchor box:

fastrcnn權重_ios_04

綜合以上兩種變換(1:1,1:2,2:1),最後生成9個Anchor box:

fastrcnn權重_寬高_05

特徵圖大小為60*40,所以會一共生成60*40*9=21600個Anchor box。

generate_anchors源碼解析:

首先看main函數:

if __name__ == '__main__':
    import time
    t = time.time()
    a = generate_anchors()   #最主要的就是這個函數
    print time.time() - t
    print a
    from IPython import embed; embed()

進入到generate_anchors函數中:

def generate_anchors(base_size=16, ratios=[0.5, 1, 2],
                     scales=2**np.arange(3, 6)):
    """
    Generate anchor (reference) windows by enumerating aspect ratios X
    scales wrt a reference (0, 0, 15, 15) window.
    """
 
    base_anchor = np.array([1, 1, base_size, base_size]) - 1
    print ("base anchors",base_anchor)
    ratio_anchors = _ratio_enum(base_anchor, ratios)
    print ("anchors after ratio",ratio_anchors)
    anchors = np.vstack([_scale_enum(ratio_anchors[i, :], scales)
                         for i in xrange(ratio_anchors.shape[0])])
    print ("achors after ration and scale",anchors)
    return anchors

參數有三個:

1.base_size=16

這個參數指定了最初的類似感受野的區域大小,因為經過多層卷積池化之後,feature map上一點的感受野對應到原始圖像就會是一個區域,這裏設置的是16,也就是feature map上一點對應到原圖的大小為16x16的區域。也可以根據需要自己設置。

2.ratios=[0.5,1,2]

這個參數指的是要將16x16的區域,按照1:2,1:1,2:1三種比例進行變換

3.scales=2**np.arange(3, 6)

這個參數是要將輸入的區域,的寬和高進行三種倍數,2^3=8,2^4=16,2^5=32倍的放大,如16x16的區域變成(16*8)*(16*8)=128*128的區域,(16*16)*(16*16)=256*256的區域,(16*32)*(16*32)=512*512的區域。

接下來看第一句代碼:

base_anchor = np.array([1, 1, base_size, base_size]) - 1
 
'''base_anchor值為[ 0,  0, 15, 15]'''

表示最基本的一個大小為16x16的區域,四個值,分別代表這個區域的左上角和右下角的點的座標。

ratio_anchors = _ratio_enum(base_anchor, ratios)

這一句是將前面的16x16的區域進行ratio變化,也就是輸出三種寬高比的anchors,這裏調用了_ratio_enum函數,其定義如下:

輸入參數為一個anchor(四個座標值表示)和三種寬高比例(0.5,1,2)

def _ratio_enum(anchor, ratios):
    """
    Enumerate a set of anchors for each aspect ratio wrt an anchor.
    """
    size = w * h   #size:16*16=256
    size_ratios = size / ratios  #256/ratios[0.5,1,2]=[512,256,128]
    #round()方法返回x的四捨五入的數字,sqrt()方法返回數字x的平方根
    ws = np.round(np.sqrt(size_ratios)) #ws:[23 16 11]
    hs = np.round(ws * ratios)    #hs:[12 16 22],ws和hs一一對應。as:23&12
    #給定一組寬高向量,輸出各個預測窗口,也就是將(寬,高,中心點橫座標,中心點縱座標)的形式,轉成
    #四個座標值的形式
    anchors = _mkanchors(ws, hs, x_ctr, y_ctr)  
    return anchors

在這個函數中又調用了一個_whctrs函數,這個函數定義如下,其主要作用是將輸入的anchor的四個座標值轉化成(寬,高,中心點橫座標,中心點縱座標)的形式。

最後該函數輸出的變換了三種寬高比的anchor如下:

ratio_anchors = _ratio_enum(base_anchor, ratios)
'''[[ -3.5,   2. ,  18.5,  13. ],
    [  0. ,   0. ,  15. ,  15. ],
    [  2.5,  -3. ,  12.5,  18. ]]'''

進行完上面的寬高比變換之後,接下來執行的是面積的scale變換:

anchors = np.vstack([_scale_enum(ratio_anchors[i, :], scales)
                         for i in xrange(ratio_anchors.shape[0])])

這裏最重要的是_scale_enum函數,該函數定義如下,對上一步得到的ratio_anchors中的三種寬高比的anchor,再分別進行三種scale的變換,也就是三種寬高比,搭配三種scale,最終會得到9種寬高比和scale 的anchors。這就是論文中每一個點對應的9種anchors。
 

def _scale_enum(anchor, scales):
    """
    Enumerate a set of anchors for each scale wrt an anchor.
    """
 
    w, h, x_ctr, y_ctr = _whctrs(anchor)
    ws = w * scales
    hs = h * scales
    anchors = _mkanchors(ws, hs, x_ctr, y_ctr)
    return anchors

在這個函數中又調用了一個_whctrs函數,這個函數定義如下,其主要作用是將輸入的anchor的四個座標值轉化成(寬,高,中心點橫座標,中心點縱座標)的形式。

def _whctrs(anchor):
    """
    Return width, height, x center, and y center for an anchor (window).
    """
    w = anchor[2] - anchor[0] + 1
    h = anchor[3] - anchor[1] + 1
    x_ctr = anchor[0] + 0.5 * (w - 1)
    y_ctr = anchor[1] + 0.5 * (h - 1)
    return w, h, x_ctr, y_ctr

通過這個函數變換之後將原來的anchor座標(0,0,15,15)轉化成了w:16,h:16,x_ctr=7.5,y_ctr=7.5的形式。

_scale_enum函數中也是首先將寬高比變換後的每一個ratio_anchor轉化成(寬,高,中心點橫座標,中心點縱座標)的形式,再對寬和高均進行scale倍的放大,然後再轉換成四個座標值的形式。最終經過寬高比和scale變換得到的9種尺寸的anchors的座標如下:

anchors = np.vstack([_scale_enum(ratio_anchors[i, :], scales)
                         for i in xrange(ratio_anchors.shape[0])])
'''
[[ -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.]]
'''

 

源碼中,通過width:(0~60)*16,height(0~40)*16建立shift偏移量數組,再和base_ancho基準座標數組累加,得到特徵圖上所有像素對應的Anchors的座標值,是一個[216000,4]的數組。


RPN-分類與迴歸

fastrcnn權重_寬高_06

Feature Map進入RPN後,先經過一次3*3的卷積,同樣,特徵圖大小依然是60*40*512,這樣做的目的應該是進一步集中特徵信息,接着看到兩個全卷積,即kernel_size=1*1,p=0,stride=1。這裏:1*1卷積的意義是改變特徵維度(18,36)。

fastrcnn權重_寬高_07

如上圖中標識:

①   rpn_cls:60*40*512-d ⊕18 ==> 60*40*9*2 

         逐像素對其9個Anchor box進行二分類

②   rpn_bbox:60*40*512-d ⊕36==>60*40*9*4

          逐像素得到其9個Anchor box四個座標信息(其實是偏移量)


RPN-工作原理

Caffe版本下的網絡:

fastrcnn權重_fastrcnn權重_08

<<1>>rpn-data:

生成Anchor box,對Anchor box進行過濾和打標籤(為了softmax),且對Anchor box和Ground Truth計算偏移量(為了SmoothL1)

layer {  
2.      name: 'rpn-data'  
3.      type: 'Python'  
4.      bottom: 'rpn_cls_score'   #僅提供特徵圖的height和width的參數大小
5.      bottom: 'gt_boxes'        #ground truth box
6.      bottom: 'im_info'         #包含圖片大小和縮放比例,可供過濾anchor box
7.      bottom: 'data'  
8.      top: 'rpn_labels'  
9.      top: 'rpn_bbox_targets'  
10.      top: 'rpn_bbox_inside_weights'  
11.      top: 'rpn_bbox_outside_weights'  
12.      python_param {  
13.        module: 'rpn.anchor_target_layer'  
14.        layer: 'AnchorTargetLayer'  
15.        param_str: "'feat_stride': 16 \n'scales': !!python/tuple [8, 16, 32]"  
16.      }  
17.    }

這一層主要是為特徵圖60*40上的每個像素生成9個Anchor box(位置),並且對生成的Anchor box進行過濾和標記,參照源碼。

過濾和標記規則如下:

去除掉超過1000*600這原圖的邊界的anchor box

如果anchor box與ground truth的IoU>0.7,標記為正樣本,label=1

如果anchor box與ground truth的IoU<0.3,標記為負樣本,label=0

剩下的既不是正樣本也不是負樣本,不用於最終訓練,label=-1

除了對anchor box進行標記外,另一件事情就是計算anchor box與ground truth之間的偏移量

令:ground truth:標定的框也對應一箇中心點位置座標x*,y*和寬高w*,h*

     anchor box: 中心點位置座標x_a,y_a和寬高w_a,h_a

所以,偏移量:

△x=(x*-x_a)/w_a   △y=(y*-y_a)/h_a 

     △w=log(w*/w_a)   △h=log(h*/h_a)

    通過ground truth box與預測的anchor box之間的差異來進行學習,從而是RPN網絡中的權重能夠學習到預測box的能力


<<2>>rpn_loss_cls、rpn_loss_bbox、rpn_cls_prob:

其中‘rpn_loss_cls’、‘rpn_loss_bbox’是分別對應softmaxsmooth L1計算損失函數。

  • Softmax公式,計算各分類的概率值
  • Softmax Loss公式,RPN進行分類時,即尋找最小Loss值

RPN訓練設置:在訓練RPN時,一個Mini-batch是由一幅圖像中任意選取的256個proposal組成的,其中正負樣本的比例為1:1。如果正樣本不足128,則多用一些負樣本以滿足有256個Proposal可以用於訓練,反之亦然。

舉個栗子:共5000個proposal進行訓練,其中正例200,反例4800。每個Mini-batch每次分別隨機從正反選出128個。為什麼這麼做?因為反例一般佔據大多數,如果隨機選擇,則反例會佔每個Mini-batch的絕大數,會導致算出的loss不準。

rpn_cls_prob 存放的是每個框的類別得分。是為了進行下一步的nms的操作,眾所周知nms是按照置信度(類別得分)來排序的。


<<3>>proposal

layer {  
2.      name: 'proposal'  
3.      type: 'Python'  
4.      bottom: 'rpn_cls_prob_reshape' #[1,18,40,60]==> [batch_size, channel,height,width]Caffe的數據格式,anchor box分類的概率
5.      bottom: 'rpn_bbox_pred'  # 記錄訓練好的四個迴歸值△x, △y, △w, △h
6.      bottom: 'im_info'  
7.      top: 'rpn_rois'  
8.      python_param {  
9.        module: 'rpn.proposal_layer'  
10.        layer: 'ProposalLayer'  
11.        param_str: "'feat_stride': 16 \n'scales': !!python/tuple [4, 8, 16, 32]"
12.      }  
13.    }

在輸入中我們看到’rpn_bbox_pred’,記錄着訓練好的四個迴歸值△x, △y, △w, △h。

源碼中,會重新生成60*40*9個anchor box,然後累加上訓練好的△x, △y, △w, △h,從而得到了相較於之前更加準確的預測框region proposal。

進一步對預測框進行越界剔除使用nms非最大值抑制,剔除掉重疊的框;

比如,設定IoU為0.7的閾值,即僅保留覆蓋率不超過0.7的局部最大分數的box(粗篩)。最後留下大約2000個anchor,然後再取前N個box(比如300個);這樣,進入到下一層ROI Pooling時region proposal大約只有300個


<<4>>roi_data

layer {  
2.      name: 'roi-data'  
3.      type: 'Python'  
4.      bottom: 'rpn_rois'  
5.      bottom: 'gt_boxes'  
6.      top: 'rois'  
7.      top: 'labels'  
8.      top: 'bbox_targets'  
9.      top: 'bbox_inside_weights'  
10.      top: 'bbox_outside_weights'  
11.      python_param {  
12.        module: 'rpn.proposal_target_layer'  
13.        layer: 'ProposalTargetLayer'  
14.        param_str: "'num_classes': 81"  
15.      }  
16.    }

為了避免定義上的誤解,我們將經過‘proposal’後的預測框稱為region proposal(其實,RPN層的任務其實已經完成,roi_data屬於為下一層準備數據

主要作用:

  1. RPN層只是來確定region proposal是否是物體(是/否),這裏roi_data根據region proposal和ground truth box的最大重疊指定具體的標籤(就不再是二分類問題了,參數中指定的是81類),再次打標籤
  2. 計算region proposal與ground truth boxes的偏移量,計算方法和之前的偏移量計算公式相同

經過這一步後的數據輸入到ROI Pooling層進行進一步的分類和定位。


3.ROI Pooling: 

layer {  
2.      name: "roi_pool5"  
3.      type: "ROIPooling"  
4.      bottom: "conv5_3"   #輸入特徵圖大小
5.      bottom: "rois"      #輸入region proposal
6.      top: "pool5"     #輸出固定大小的feature map
7.      roi_pooling_param {  
8.        pooled_w: 7  
9.        pooled_h: 7  
10.        spatial_scale: 0.0625 # 1/16  
11.      }  
12.    }

把建議框映射到CNN的最後一層faature map上,進行pooling得到固定大小的proposal feature map

從上述的Caffe代碼中可以看到,輸入的是RPN層產生的region proposal(假定有300個region proposal box)VGG16最後一層產生的特徵圖(60*40 512-d),遍歷每個region proposal,將其座標值縮小16倍,這樣就可以將在原圖(1000*600)基礎上產生的region proposal映射到60*40的特徵圖上,從而將在feature map上確定一個區域(定義為RB*)。在feature map上確定的區域RB*,根據參數pooled_w:7,pooled_h:7,將這個RB*區域劃分為7*7,即49個相同大小的小區域,對於每個小區域,使用max pooling方式從中選取最大的像素點作為輸出,這樣,就形成了一個7*7的feature map。

ROI pooling層能實現training和testing的顯著加速,並提高檢測accuracy。

該層有兩個輸入

  1. 從具有多個卷積核池化的深度網絡中獲得的固定大小的feature maps,VGG16最後一層產生的特徵圖(60*40 512-d)
  2. RPN層產生的region proposal(假定有300個region proposal box),表示為所有ROI的N*5的矩陣,其中N(300)表示ROI的數目。第一列表示圖像index,其餘四列表示其餘的左上角和右下角座標;

ROI pooling具體操作如下:參考:

  • 根據輸入image,將ROI映射到feature map對應位置;
  • 將映射後的區域劃分為相同大小的sections(sections數量與輸出的維度相同);
  • 對每個sections進行max pooling操作;

這樣我們就可以從不同大小的方框得到固定大小的相應 的feature maps。值得一提的是,輸出的feature maps的大小不取決於ROI和卷積feature maps大小。ROI pooling 最大的好處就在於極大地提高了處理速度。

example

fastrcnn權重_ios_09

一個8*8大小的feature map,一個ROI,以及輸出大小為2*2。

(1)輸入的固定大小的feature map 

fastrcnn權重_寬高_10

(2)region proposal 投影之後位置(左上角,右下角座標):(0,3),(7,8)

fastrcnn權重_ios_11

(3)將其劃分為(2*2)個sections(因為輸出大小為2*2),我們可以得到:

fastrcnn權重_fastrcnn權重_12

(4)對每個section做max pooling,可以得到:

fastrcnn權重_寬高_13

 


4.Classifier

經過roi pooling層之後,batch_size=300, proposal feature map的大小是7*7*512,對特徵圖進行全連接,參照下圖,最後同樣利用Softmax Loss和L1 Loss完成分類和定位。

fastrcnn權重_寬高_14

PoI Pooling獲取到7x7大小的proposal feature maps後,通過全連接主要做了:

  1. 通過全連接和softmax對region proposals進行具體類別的分類
  2. 再次對region proposals進行bounding box regression,獲取更高精度的rectangle box

LOSS

fastrcnn權重_fastrcnn權重_15

fastrcnn權重_ios_16