第一篇Blog——詳解Faster RCNN網絡全部細節

第一次寫技術Blog,準備走上computer vision的道路,那就必不可少的需要求助,由於在CSDN也得到了太多的幫助,於是決定把自己學到的東西都放在公開平臺上,希望也能幫助到你,也歡迎廣大網友發現問題,及時指正。廢話不多說,開始這篇對在cv領域產生革命性影響的RCNN的進化版Faster RCNN的究極詳解。

1.把總結寫在前面,先說一說Faster RCNN包含那些重點且它們都是幹嘛的,如何進行訓練的?測試的?

先放圖:

網絡模型

1.1基本CNN【例如‘VGG’,'RESnet‘等】

首先由於輸入的圖片可能會存在尺寸不同的問題,例如 900X600 的圖片和 800X500 的圖片無法輸入到同一個基礎CNN中,因此需要將輸入圖片統一,此處爲設置爲 900X600。
最初的圖片在經過尺寸統一處理後,要放入卷積網絡中,併產生Faster RCNN最初的輸入feature map【512X37X50】(由於Conv中有四次pooling導致原圖尺寸變爲原來的1/feat_stride, feat_stride = 16。

1.2RPN卷積網絡層

RPN (Region of proposal network), RPN層是需要進行訓練產生Loss,並進行參數學習的層。要產生所謂的Loss並進行梯度下降更新參數,預測值和真值是必須所在。
在這裏插入圖片描述
1).首先我們將對1.1中產生的feature_map進行卷積操作【3X3,512】,此處3X3的kernel整合以feature_map爲中心的周圍9個像素點的特徵,爲了使得每個特徵點包含更多信息。接着進行【1X1conv,18】產生18X37X50的特徵,每個點默認有9個anchors,每個anchor包含兩個預測值(foreground和background的可能性) 因此每個特徵點(共37X50)有18個預測值(此處不關心具體類別,只用於區分是否包含物體,foreground有物體,background沒有)。
reshape應用於將每個anchor的預測值單獨出一個維度,方便softmax計算和後續製作標籤
softmax將預測值進行歸一化,得到最終預測值
再次reshape返回最初特徵圖的尺寸方便用於Loss計算。
2). 另一個分支進行【1X1,36】的卷積,得到36X37X36的特徵,每個特徵點擁有36維的數據用於表示9(個anchors)X 4(中心點座標和寬高值)=36維。

RPN部分的部分代碼如下(源代碼

此處包含兩個網絡,1.分類網絡:得到每個anchor前背景得分和概率;2.迴歸網絡:得到每個anchor的座標預測偏移值(此偏移值爲相對於後面會講的真值anchors的偏移值,並不是相對於ground truth的)

def forward(self, base_feat, im_info, gt_boxes, num_boxes):
	#輸入數據的第一維是batch尺寸
        batch_size = base_feat.size(0)
        #先利用3 X 3卷積進一步融合特徵       
        rpn_conv1 = F.relu(self.RPN_Conv(base_feat), inplace=True)        
        # get rpn classification score(1 X 1得到分類網絡,每個點代表anchor的前背景得分)
        rpn_cls_score = self.RPN_cls_score(rpn_conv1)
        #利用reshape和softmax得到前背景概率
        rpn_cls_score_reshape = self.reshape(rpn_cls_score, 2)        
        rpn_cls_prob_reshape = F.softmax(rpn_cls_score_reshape, 1)        
        rpn_cls_prob = self.reshape(rpn_cls_prob_reshape, self.nc_score_out)
        # get rpn offsets to the anchor boxes  得到迴歸網絡
        rpn_bbox_pred = self.RPN_bbox_pred(rpn_conv1

至此我們的預測值已經就位,待我們補全真值,便可以進行RPN的Loss的計算和梯度更新參數。

需要注意的事(理解誤區):
這裏兩條分支所產生的分別是anchor類別和座標預測值,這裏的anchor都是假想出來的,在圖像中並不真實存在,是人爲說的這18和36個數分別屬於9個不同尺寸的anchor,但後續會製作真實的anchor,假想的anchor和真實的anchor進行Loss的值計算時,用於para(參數)的更新的同時,也就進行了假想和真實anchor的一一對應。這裏很容易導致誤解,筆者在思考過程中也困惑於爲何這個卷積的輸出即分別屬於9個anchor?這9個anchors哪來的這些問題。

1.3RPN真值的求取

本階段,我們進行所有anchor的真值製作,與anchor的預測值(1.2中)進行Loss計算,並用於參數學習。
上圖:
1.3 content

1.全部anchor生成
anchor生成部分代碼解釋整體可參照此鏈接,博主覺得這篇代碼解讀對我的幫助最大,希望對你亦是如此
附上鍊接:Anchor_target_layer.py
下面從代碼角度簡單講解一下生成過程:
——————————————————

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.    """
#首先創建一個基本anchor【0,0,15,15】
base_anchor = np.array([1, 1, base_size, base_size]) - 1    
#將基本anchor進行寬高變化,生成三種寬高比的anchors
ratio_anchors = _ratio_enum(base_anchor, ratios)    
#將上述anchors再進行尺度scale的變化,最終得到9種anchors       
anchors = np.vstack([_scale_enum(ratio_anchors[i, :], scales) for i in xrange(ratio_anchors.shape[0])])    

return anchors
————————————————————————————————

在自行閱讀代碼之前,解釋一下anchor生成機制,可能會幫助理解:
1).generate_anchors(base_size=16, ratio = 【0.5, 1, 2】, scale = 2**np.arange(3,6))
generate_anchors代碼理解

三個參數含義:
a).base_size表示從原圖到特徵圖的縮放尺寸(16倍)
b).ratio表示三種邊長比例,注意:三種邊長比例是在在相同面積下(例如:面積同樣爲256,三種邊長比的anchor尺寸分別爲:22 X 11(2:1), 16 X 16(1:1), 12 X 23(1:2), 他們面積都爲256,但邊長比例不同,故原文中設置的ratio用於相同面積下,不同比例邊長的anchor的產生。
c).scale = 【8,16,32】,用於產生不同面積的anchor,面積分別爲16X8 X 16X8 = 128 X128, 16X16 X 16X16 = 256 X 256, 16X32 X 16X32 = 512 X 512.

2).文中從feature_map中的第一個點(0,0)點進行初始化,特徵圖上的(0,0)點對應原圖上左上(0,0),右下(15,15)的範圍

在經過三組面積scale和邊長ratio處理後結果如圖:
在這裏插入圖片描述

3)在得到初始的anchor如上圖,下一步進行平移變換,得到所有特徵點對應的總37 X 50 X 9個anchors,代碼中的shift變量存有全部0~特徵圖尺寸的數據,用於將初始化的anchor平移得到全部anchors
4)在得到全部anchors後要將超出邊界範圍的anchor刪掉,即那些左上角座標<0,右上角座標>原圖尺寸的anchors。
這樣我們得到【M,4】尺寸的數據,M表示全部內部anchors的數量,4是每個anchor的座標。

2.anchor真值的求取
基礎原理上圖:
在這裏插入圖片描述

1)標籤原則
對於真值的獲取,採用bbox_overlaps_batch()函數,用於計算每個anchor與所有ground truth的IoU值(intersection of Union)上圖陰影面積。如圖,anchor A 和anchor C與真值重合滿足要求,則標籤爲1(前景);anchor C不滿足,標籤爲0(背景)。
具體要求
與gt_box有IoU最大的anchor標記1,每個anchor若與某個gt_box IoU值大於threshold,標記爲1;小於某個設定值時,標記爲0;在區間的anchor爲無效anchor。標記-1。
結果
上述過程產生的overlap是【1,M,N】的數據,1表示batch_size = 1(一個批次一張圖片),M表示anchor數量,N表示gt_box數量。每個數據表示此anchor與此gt_box的IoU值。
最後返回anchors和Label,尺寸爲【M有效,4】,【M有效,1】,‘M有效’ 表示經過篩選後的anchor數量。

2)降採樣
對於這些樣本,仍然數目太多,並且絕代部分都是負樣本(背景),所以我們需要篩選部分有效的正樣品和負樣品。
具體代碼解釋見: 文中 6.降採樣
簡單代碼幫助理解:

————————————————————————

def forward(self, input):
	......
	for i in range(batch_size):            
	# subsample positive labels if we have too many
	#進行下采樣選取            
	if sum_fg[i] > 128:                
		fg_inds = torch.nonzero(labels[i] == 1).view(-1)                
		rand_num = torch.from_numpy(np.random.permutation
						(fg_inds.size(0))).type_as(gt_boxes).long()                
		disable_inds = fg_inds[rand_num[:fg_inds.size(0)-num_fg]]                
		labels[i][disable_inds] = -1  #-1代表無效值
	#負樣本同上
	......

—————————————————————————

3)最後輸出我們根據ground truth製作的anchor真值(包括:標籤,anchor相對gt_box的座標偏移值,bbox_inside_weight, bbox_outside_weight)
下面要說的很重要!!
思想誤區:這裏有筆者在學習過程中遇到的思想誤區,一定要分清網上解析和書中所說的anchor真值和ground truth下面縮寫成gt的區別,兩者都是真值,那他們是一樣的嗎?

當然不一樣!!! 假設一張圖片中,我們人爲製作標籤時框選出的目標框爲gt,一張圖片可能就那麼兩三個物體和他們的框,即一張圖片2,3個gt。相反,每張圖片的anchor真值,是我們根據我們產生的10000+個anchor與ground truth的IoU值大小的判定以及對於距離gt近的anchor進行偏移後得到的。最後經過篩選,我們選出256個anchor真值(128正,128負),即每張圖片相應有256個anchor真值,作爲新的標籤,輸入RPN網絡中的Loss計算中,目的學習參數,使得我們預測的anchor與我們製作的anchor能夠儘可能接近。

下面從代碼的角度,講解一些代碼內容中不好理解的部分:
先列出幫助理解的數據和操作:

  1. offset參數: 由於batch的存在,導致index會出現混亂,例如argmax_overlaps數據爲【B,M】,第一維是各個batch,第二維,它的意義是每個anchors的最大IoU對應的gt的index(M可能爲【0,2,1,3,2,…】,我們的輸入argmax_overlaps.view(-1),是一個【BXM,1】的數據,如果我們不進行offset的座標變換操作,會導致所有除了第一批次的anchors,都僅僅使用第一批次的gt,導致錯誤。原因在於,我們對gt_box進行了view操作,把它變成了【BXK,1】的數據結構,前K個數據是第一批次的gt數據,K到2K的數據屬於batch2,以此類推。
    offset進行擴維以前爲:【0,0,0,…(K個),K,K,K,…(K個),2K,2K,2K,…(K個),…】,由此,我們將第二批次的數據平移K個單位,是其anchor於gt保證正確的對應關係。
  2. box_target: 存於anchors與gt_box一一對應後的偏移值(dx,dy,dw,dh)
  3. bbox_inside_weights: 是正負樣本在計算Loss時會用到的係數。正樣本爲1,負爲0,相當於Loss公式中的Pi*。它其實起到一個mask的作用,當我們在計算迴歸隨時的時候,我們是不需要考慮負樣本的迴歸損失的,但由於我們使用矩陣相乘的方式,矢量化來求取我們的Loss實際上我們將負樣本的迴歸損失也是進行計算了的,因此我們在進行相加之前用一個mask來將所有的負樣本的迴歸損失全部過濾掉。
  4. bbox_outside_weights: 與3同理,它相當於式中的λ/Nreg係數。

損失函數計算公式如下:Loss函數計算公式

——————————————————

 	# gt_boxes (B,K,5)         
 	offset = torch.arange(0, batch_size)*gt_boxes.size(1)#offset(batch_size,)       
 	 # argmax_overlaps(B,M)         +  (B,1)    !!索引需要        
 	argmax_overlaps = argmax_overlaps + offset.view(batch_size, 1).type_as(argmax_overlaps)        		
 	# gt_boxes.view(-1,5) (B*K,5)所以需要offset        
 	# bbox_targets (batch_size, -1, 5)        	
		
	bbox_targets = _compute_targets_batch(anchors, gt_boxes.view(-1,5)[argmax_overlaps.view(-1), :].view(batch_size, -1, 5))         
 	# use a single value instead of 4 values for easy index.          
 	#求出的偏移,是要看這個anchor離哪一個gt最近【那如果都不近呢,返回的應該是第一個gt的偏移】   
      	bbox_inside_weights[labels==1] = cfg.TRAIN.RPN_BBOX_INSIDE_WEIGHTS[0]        
      	#默認RPN_POSITIVE_WEIGHT=-1        
	if cfg.TRAIN.RPN_POSITIVE_WEIGHT < 0:            
      	#num_examples = torch.sum(labels[i] >= 0)            
      	# #樣本權重歸一化            
 		num_examples = torch.sum(labels[i] >= 0).item()#正負的樣本總數目            
      		positive_weights = 1.0 / num_examples#正樣本權重 1/總樣本,感覺可以直接256上啊            
      		negative_weights = 1.0 / num_examples         
    	else:            
      		assert ((cfg.TRAIN.RPN_POSITIVE_WEIGHT > 0) & (cfg.TRAIN.RPN_POSITIVE_WEIGHT < 1))         
      	bbox_outside_weights[labels == 1] = positive_weights        
      	bbox_outside_weights[labels == 0] = negative_weights

好處:
用256個anchor真值作爲學習對象比用滑窗和RCNN中的2000個窗口,節省了太多空間和時間,這也是anchor的創新所在。

                      終:由此基於三種面積尺寸,三種邊長比,可產生共3X3 = 9個anchor,故每個特徵點有9個真實存在的anchor,到此9個真實的anchor和9個虛構anchor都已經產生,下面就是Loss計算,使得假想的anchor通過學習與anchor的真值更加接近,最好的結果是兩者重合(不太可能),到此我們的RPN訓練可以開始了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章