令我「細思極恐」的Faster-R-CNN

深藍學院 發佈 2021-08-05T16:09:04.533822+00:00

作者簡介CW,廣東深圳人,畢業於中山大學(SYSU)數據科學與計算機學院,畢業後就業於騰訊計算機系統有限公司技術工程與事業群(TEG)從事Devops工作,期間在AI LAB實習過,實操過道路交通元素與醫療病例圖像分割、視頻實時人臉檢測與表情識別、OCR等項目。

作者簡介

CW,廣東深圳人,畢業於中山大學(SYSU)數據科學與計算機學院,畢業後就業於騰訊計算機系統有限公司技術工程與事業群(TEG)從事Devops工作,期間在AI LAB實習過,實操過道路交通元素與醫療病例圖像分割、視頻實時人臉檢測與表情識別、OCR等項目。

目前也有在一些自媒體平台上參與外包項目的研發工作,項目專注於CV領域(傳統圖像處理與深度學習方向均有)。

前言

CW每次回顧Faster R-CNN的相關知識(包括源碼),都會發現之前沒有注意到的一些細節,從而有新的收穫,既驚恐又驚喜,可謂「細思極恐」!

Faster R-CNN可以算是深度學習目標檢測領域的祖師爺了,至今許多算法都是在其基礎上進行延伸和改進的,它的出現,可謂是開啟了目標檢測的新篇章,其最為突出的貢獻之一是提出了 "anchor" 這個東東,並且使用 CNN 來生成region proposal(目標候選區域),從而真正意義上完全使用CNN 來實現目標檢測任務(以往的架構會使用一些傳統視覺算法如Selective Search來生成目標候選框,而 CNN僅用來提取特徵或最後進行分類和回歸)。

Faster R-CNN 由 R-CNN 和 Fast R-CNN發展而來,R-CNN是第一次將CNN應用於目標檢測任務的傢伙,它使用selective search算法獲取目標候選區域(region proposal),然後將每個候選區域縮放到同樣尺寸,接著將它們都輸入CNN提取特徵後再用SVM進行分類,最後再對分類結果進行回歸,整個訓練過程十分繁瑣,需要微調CNN+訓練SVM+邊框回歸,無法實現端到端。

Fast R-CNN則受到 SPP-Net 的啟發,將全圖(而非各個候選區域)輸入CNN進行特徵提取得到 feature map,然後用RoI Pooling將不同尺寸的候選區域(依然由selective search算法得到)映射到統一尺寸。另外,它用Softmax替代SVM用於分類任務,除最後一層全連接層外,分類和回歸任務共享了網絡權重。

而Faster R-CNN相對於其前輩Fast R-CNN的最大改進就是使用RPN來生成候選區域,摒棄了selective search算法,即完全使用CNN解決目標檢測任務,同時整個過程都能跑在GPU上,之前selective search僅在CPU上跑,是耗時的一大瓶頸。

本文從編碼實現的角度來解析 Faster R-CNN,先對網絡的前向(forward)過程進行闡述,再回過頭來看訓練的細節,這樣便於更好地理解。

源碼是一位大佬寫的,基於Pytorch框架,是Faster R-CNN的精鍊版,作為學習和參考來說相當不錯,我自己也擼了一遍,這裡也附上大佬源碼的連結:Faster R-CNN 精鍊版。

Faster R-CNN Network

一. Overview

除去複雜的理論知識不談,從編程的角度來看,Faster R-CNN做的事情其實就是,「窮舉」一張圖片可能出現物體的位置,生成矩形框(計算位置和大小),計算這些框中出現物體的機率,選擇機率高的,然後調整這些矩形框的位置與大小,並去除重疊度高的,最終得到一個個包含物體的矩形框。

如下為整體框架結構,結合上述過程來看,主要是三部分,Extrator進行特徵提取、RPN 生成候選框、RoIHead對候選框進行分類並調整目標預測框的位置與大小。

Faster R-CNN 框架

二. 特徵提取

最初的Faster R-CNN使用了預訓練的VGG16作為backbone進行特徵提取,實現方法是加載預訓練模型,抽取並分離前面的卷積層和後面的全連接層,固定卷積層中部分層的權重,用作特徵提取,而全連接層則給 RoIHead 用作分類和回歸。

Feature Extractor 實現

三. RPN(Region Proposal Network)

RPN 能夠獲得包含物體的區域,網絡的後續部分便可基於這些區域做進一步的檢測。

1. 使用anchor「窮舉」目標物體所在區域

這裡先介紹下anchor,這東東有點抽象,中文翻譯是「錨」,讓人容易覺得這是一個點,實際它是可能包含目標物體的矩形框。在目標檢測任務中,通常會為每個像素點預設一個或多個面積大小和寬高比例不同的anchor,以此使得圖像上密集鋪滿了許許多多anchor,從而覆蓋到包含物體的所有位置區域。

backbone提取的特徵圖(記作 fm)相對於網絡的輸入圖像尺寸縮小了16倍。因此,fm 中的1個像素點就相當於輸入圖像的16個像素點,或者說,fm中的1x1區域覆蓋了輸入圖像的16x16區域。這樣,fm中的每個像素點都對應地覆蓋了輸入圖像的區域。

不難想像,如果一個像素點僅對應一個anchor,難免會覆蓋不到或者覆蓋不全目標物體。因此,Faster R-CNN 對每個點對應的anchor進行了尺寸縮放和形變,前者對應矩形面積,後者對應矩形長寬比例,每種尺寸對應3種長寬比,共設置3種尺寸,3種尺寸分別是128x128、256x256、512x512,3種長寬比分別是 1:1、1:2、2:1,這樣一來,一個點就對應9個anchor,其中每個 anchor 的形狀和大小都不完全相同。

9個形狀和大小不同的anchor

具體的實現方法是,先計算fm中的一個點(通常是左上角)對應的9個anchor的中心點坐標和長寬,其它點對應的anchor則通過平移計算得出。

特徵圖左上角的像素點對應的9個anchor位置

不知道諸位客觀發現了沒,在上圖計算anchor_base坐標時,有可能出現負數!比如對於特徵圖左上角的那個點(0,0),其作為anchor中心點,由於下採樣了16倍,那麼就對應於輸入圖像16x16的區域,於是映射到輸入圖像上anchor中心點就是(8,8)。

考慮anchor尺寸倍數為8且長寬比為1:1的情況,此時anchor面積為(16x8) x (16x8)=128x128,長寬各為128,但中心點卻是(8,8),按此計算,左上角點坐標就是 (8 - 128/2, 8-128/2) = (-56, -56)。莫方,在以下第3小節講解生成RoI的部分會涉及這部分的處理。

根據位移計算特徵圖所有像素點對應的anchor位置

2. 在每個特徵點上計算分類與回歸結果

這裡的分類是二分類,僅僅區分前景和背景,具體做法是,先將 fm 進行3x3卷積,進一步提取特徵,然後使用1x1卷積將通道數映射到18=9x2,對應9個anchor的兩個類別,然後再將通道這個維度分割多一個維度,使得最後一維是2,代表前景和背景,最後使用softmax計算機率。

對候選區域分類

回歸的做法是使用1x1卷積將通道數映射到36=9x4,對應9個anchor的位置與大小。注意,這裡回歸的4個量分別是矩形框中心點與anchor中心點橫、縱坐標的位移 以及 矩形框長寬與 anchor 長寬的比例。

對候選區域回歸

3.對分類與回歸結果進行後處理,生成 RoI(Region of Interest)


這部分是One-Stage的最後階段,也稱作 Proposal Creator(Proposal Layer),會對RPN輸出的分類和回歸結果進行後處理(如NMS等),得到網絡認為包含物體的區域,稱為感興趣的候選區域——RoI。

至此,其實已經完成了檢測任務,因為已經得到了包含物體的區域(bbox),只不過沒有對物體類別進行細分,僅區分了前、背景。另外,由於anchor的位置和大小是人工預設的,且用於訓練的樣本有限,因此此處得到的檢測結果可能並不夠精準。

具體做法是,先將回歸得到的候選區域的寬、高限制在輸入圖像尺寸範圍內以及剔除尺寸過小(小於16x16,因為特徵圖中一個像素點就已經代表了輸入圖像16x16的區域。

下圖中的scale是輸入網絡中的圖像與原圖之間的縮放係數)的,然後將它們按前景機率排序,保留前面的一批(訓練時是12000,預測時是6000),接著使用非極大值抑制進一步剔除掉可能重複的,最後從剔除後的結果中保留剩下的一批(訓練時是2000,預測時是300)。

生成RoI(i)

生成RoI(ii)

四. RolHead

RPN 生成的 RoI 僅僅區分了前景和背景,並沒有區分出物體的具體類別。因此,RoIHead 就是對 RoI 進一步分類,並且調整矩形框的位置和大小,使得預測結果更精細。

1. RoI Pooling

顧名思義,就是對 RoI 進行池化操作,具體做法是將每個RoI縮放到特徵圖尺寸範圍內對應的區域,然後將RoI平均劃分為同樣數量的子區域(bin),對每個bin實施(最大/平均)池化操作,這樣就使得每個bin都映射為一個像素值,由於不同尺寸的RoI都劃分了同樣數量的bin,因此最終使得所有RoI都變為同樣大小,這裡是7x7(也就是對每個RoI都劃分了7x7個bin)。

RPN 生成的 RoI 尺寸是對應於輸入圖像的,為了後面接全連接層生成預測結果,因此需要使用RoI Pooling將不同尺寸的各個RoI都映射至相同大小。

7x7大小的RoI經過RoI Pooling變成2x2大小

這裡提出 RoI Pooling 會產生的問題:

1.1 兩次量化損失

在將RoI映射至特徵圖尺寸範圍的過程中,下採樣取整操作(比如200x200的區域經16倍下採樣後映射為12 x 12)會產生一次量化損失;接著,假設最終需要生成的尺寸大小為nxn,則需將RoI劃分nxn個bin,在這個劃分過程中又會產生一次量化損失(比如對12x12大小的RoI劃分成7x7個bin,每個bin的平均尺寸是

,那麼就會造成有些bin的大小是

,而另一些bin的大小則是

),於是後來就有人提出如RoI Align和Precise RoI Pooling等方法進行改進,這裡就不展開敘述了。

RoI Pooling的兩次量化損失

1.2 只有少數點的loss在反向傳播中貢獻了梯度

由於每個bin都由其中像素值最大的一點代表,因此在這部分的反向傳播中,每個bin只有一個點的loss貢獻了梯度,忽略了大部分點。

RoI Pooling的反向傳播

2. RoI 分類與回歸

將RoI Pooling後的結果展開(flatten)成 vector,輸入全連接層進行分類和回歸,對應輸出的神經元個數分別為物體類別數(記為n_classes)以及每個類別物體對應的bbox(n_classes x 4)。

注意,這裡回歸的結果是預測框中心點相對於正樣本RoIs(在後文訓練部分會講解如何篩選正樣本)中心點坐標的位移以及兩者長寬的比例,並且是歸一化(減去均值除以標準差)後的結果。

RoI分類與回歸

五. 後處理生成預測結果

RoIHead的輸出還不是預測結果的最終形態,為了產生最終的預測結果,還需要做一些後處理。

具體做法是,將網絡輸出縮放至原圖尺寸,注意是原圖,不是輸入網絡的圖像,在原圖與輸入圖像之間是有縮放操作的。

接著對回歸的結果去歸一化(乘標準差加均值),結合RoIs的位置和大小計算出bbox的位置(左上角坐標和右下角坐標),並且裁剪到原圖尺寸範圍內。

然後,選擇置信度大於閥值的矩形框,最後再使用非極大值抑制(NMS)剔除重疊度高的bbox得到最終的結果。

生成預測結果(i)

注意,這裡在進行置信度篩選以及NMS時是分別對每個物體單獨類別實施的,不包括背景類(下圖range()從1開始)。

那麼就可能會發生這樣的情況:一個RoI對應不同類別的預測結果都被保留下來(要知道RoIHead的輸出是每個RoI在不同類別上的分類和回歸結果),這裡可以說是Faster R-CNN優於YOLOv1的地方,因為YOLOv1的一個格子僅能預測一個物體,但同時Faster R-CNN單獨在各個類別進行NMS勢必會影響推斷速度,所以說從這方面看這裡也是弱於YOLOv1的地方。

速度與質量,魚與熊掌皆不可得~

生成預測結果(ii)

六. 訓練

通過以上部分,相信朋友們已經清楚了Faster R-CNN是如何進行預測的了,但是,我們還沒有開始將它是如何訓練的,只有進行了有效的訓練,模型才能產生可靠的預測結果,重頭戲來咯!

訓練的部分主要包含三個:Backbone、RPN 以及 RoIHead。Backbone 通常會採用在ImageNet上預訓練的權重然後進行微調,因此這裡主要解析RPN和RoIHead的訓練過程,最初的實現將這兩部分開訓練,後來的大多數實現已使用聯合訓練的方式。

1. 篩選anchor樣本,指導RPN訓練

由於anchor數量太多,因此需要篩選部分anchor樣本用於指導RPN訓練。anchor總樣本是Backbone輸出特徵圖上每點對應的9個anchor,從中進行篩選目標樣本,具體做法是:

1). 將坐標值不在輸入圖像尺寸範圍內的anchor的標籤記為-1;

2). 將與ground truth(gt)的IoU小於0.3的anchor作為負樣本,標籤記為0;

3). 將與每個gt的IoU最大的anchor作為正樣本,標籤記為1;

4). 將與gt的IoU不小於0.7的anchor作為正樣本,標籤記為1;

5). 限制正樣本與負樣本總數為256個,正負樣本比為1:1,若其中某一類樣本超過128個,則隨機從中選擇多出的樣本將其標籤記為-1;

6). 僅將標籤為0和1的樣本用於訓練,忽略標籤為-1的anchor

生成目標anchor用於指導訓練

對anchor樣本進行篩選得到目標anchor

正樣本和負樣本用作計算分類損失,而回歸的損失僅對正樣本計算。注意,這裡回歸的目標是gt相對於正樣本anchor中心點坐標的位移以及兩者長寬的比例,正因如此,前面部分談到過RPN回歸的結果是候選區域相對於anchor中心點坐標的位移以及兩者長寬的比例。

這種方式是將預測結果和gt都與anchor做比較,訓練目標是讓預測結果與anchor的差別和gt與anchor的差別一致。

Anchor Target Creator

最後總結下,RPN會在特徵圖每點都輸出9x2個分類結果和9x4個回歸結果,分別與每點的9個anchor的分類標籤和回歸標籤對應(RPN是二分類,僅區分前、背景),但並不是會對每個點都計算損失,最多僅有256個點會參與損失計算。

因為通過上述可知,僅有256個anchor樣本供訓練使用,而其中還可能有多個anchor對應到一個特徵像素點上。注意下,這部分的訓練是與ProposalCreator並行的分支,並不是拿ProposalCreator的輸出進行訓練!

另外,這裡有個問題引發了我的思考:在前文講RPN部分的第1節中,我們提到計算anchor坐標時可能出現負數,那麼在篩選訓練樣本時它們就勢必會被剔除掉。

如果我們將計算時anchor的坐標clip到輸入圖像尺寸範圍內,那麼就有可能引入更多有效的訓練樣本,甚至是優質樣本,提高召回率是肯定的,精確率的話是不是也有可能提高?

2. 篩選RoI樣本,指導檢測器訓練

Proposal Target Creator

這部分是從Proposal Creator (RPN中的Proposal Layer)產生的RoIs中篩選出128個目標樣本,其中正負樣本比為1:3,用於指導檢測器(RoIHead)的訓練。

具體方法是,計算每個RoI與每個gt的IoU,若某個RoI與所有gt計算所得的最大IoU不小於0.5,則為正樣本,並記錄下與此對應的gt,打上相應的類別標籤,同時限制正樣本數量不超過32個。

相對地,若某個RoI與所有gt的最大IoU小於0.5,則標記為負樣本,類別標籤為0,同時限制負樣本數量不超過96個,正負樣本的類別標籤用作指導分類訓練。最後,計算gt相對於RoI樣本的中心點坐標位移和兩者長寬比,並且歸一化(減均值除標準差),用於指導回歸訓練。

篩選目標RoI

生成分類與回歸的目標

在實際的代碼實現中,將GT也一併加入了RoIs樣本中:

將GT加入RoIs樣本中

仔細想想,感覺挺有道理,因為RoIs來源於RPN的輸出,而RPN的結果並不一定可靠,特別是在訓練初期,幾乎就是隨機輸出,可能連一個正樣本都沒有,加入GT一方面彌補了正樣本數量的不足,另一方面還提供了更優質的正樣本,怎麼說它也是GT啊,還有比它更正的麼!?

另外,雖然RoIs眾多,但僅有128個樣本進行了訓練,訓練時僅將這128個訓練樣本(Proposal Target Creator的輸出)輸入到RoIHead,而測試時則是將RPN(Proposal Creator)的輸出直接輸入到RoIHead。

最後注意下,RPN的回歸目標是沒有歸一化的,而RoIHead的有。

3. Loss函數的設計

這裡使用了兩種loss函數,CrossEntropy Loss(交叉熵損失) 用於分類,Smooth L1 Loss 用於回歸,注意在RPN和RoIHead中,回歸損失均只針對正樣本計算。

這裡,Smooth L1 Loss 的實現有個技巧,通過給正負樣本設置不同的損失權重,其中負樣本權重為0,正樣本權重為1,這樣就可以忽略負樣本的回歸損失。

Smooth L1 Loss with anchor

Smooth L1 Loss

通過上圖可看到,在計算回歸損失的均值時,分母將負樣本(標籤為0)數量也算上了,為何呢?明明只計算了正樣本的回歸損失啊.. 現在,「明明」就來告訴你!

可以拿RPN中loss的計算舉例,其實,loss的原公式是這樣的:

loss

其中,

表示mini-batch中採集的樣本數量(RPN中默認為256個),

表示anchor位置的數量,即feature map中特徵點的數量(約2400個),λ是平衡參數,相當於加權(大於一時給回歸loss加權,小於1時給分類加權),論文中默認為10。

這麼一來,

,於是就用

代替

了。

所以,在計算回歸損失的時候,係數的分母就使用正負樣本的總數了。

七. KeyPoints

1. Anchor 與 RoI 傻傻分不清楚?

它們都是矩形框,通常以(左上角坐標、右下角坐標)或者(中心點坐標、長、寬)表示。不同的是,anchor是預設的可能覆蓋目標物體的區域,而RoI是網絡產生的更為可靠的目標候選區域。

可以這麼看,anchor 是「死」的,是人為設置的(當然,後來的一些算法框架能夠通過聚類得出anchor,如YOLO),通過窮舉它來儘可能覆蓋目標物體。因此需要網絡通過訓練來進一步篩選和調整,產生RoI。在RoIHead部分,可認為RoIs充當了anchor的作用。

另外,在Faster R-CNN的實現中,anchor和RoI的尺寸對應的是網絡的輸入圖像,而原圖像和輸入圖像之間做了尺寸縮放,如以下代碼部分可看到一個'scale'變量,在預測的時候注意需要把結果根據縮放係數轉換對應到原圖上。

輸入圖像與原圖之間進行了縮放

2. 回歸結果為何不是bbox的坐標?

本文一直強調,無論是在RPN還是RoIHead中,回歸結果都不是bounding box的坐標,而是相對(正樣本anchor、RoI)中心點坐標的位移和長寬比。為方便敘述,這裡把兩者分別稱之為offset和scale。

直觀地看,直接回歸bounding box的坐標更方便,免去了傳參(RPN中需要傳入anchor,RoIHead中需要傳入RoI)與坐標計算。

但是,如果回歸的是坐標,那麼在計算損失時,大尺寸bbox的坐標誤差占的比重可能就會比小尺寸bbox之間的坐標誤差大得多,從而使得模型更偏向於學習大bbox,從而導致小目標的檢測效果不佳。

Regress

那麼如何計算offset和scale呢?拿上圖RPN的例子來說,對於預測框(藍色框),offset等於其中心點與anchor(紅色框)中心點坐標差除以anchor邊長,scale等於兩者的長寬比,並且使用log函數,log函數的作用也是一定程度上抑制了大bbox之間的誤差占比蓋過小bbox(拉近了大、小bbox之間的誤差水平),gt(綠色框)的計算方法類似。

在訓練過程中,如果我們希望預測框接近於gt,那麼它們與anchor之間的offset和scale都應該儘可能接近,於是將gt與anchor的offset與scale作為回歸的目標。

轉換成坐標的時候,基於上述公式逆向計算即可。

RPN預測框的計算

3.3 個 Creator 分別做了什麼?

Anchor Target Creator:對特徵圖上每點對應的anchor樣本進行篩選(尺寸、IoU、樣本數量),為 RPN提供256個訓練樣本,正負樣本比為1:1,此處是二分類;

Proposal Creator:對RPN產生的RoI進行篩選(尺寸、置信度、數量、NMS),產生大約2000個RoIs;

Proposal Target Creator:從Proposal Creator產生的RoIs中篩選(數量和IoU)128個目標樣本以指導檢測頭部(RoIHead)訓練,正負樣本比為1:3,此處是多分類。

Anchor Target Creator 和 Proposal Target Creator 僅在訓練過程中使用,而 Proposal Creator 在訓練和測試過程中都會用到,但它們都不涉及反向傳播,因此這部分在不同深度學習框架上可以方便地通過numpy遷移實現。

4.4 個損失的作用是什麼?

• RPN分類損失:區分anchor是前景還是背景,從而讓模型能夠學會區分前景和背景;

• RPN回歸損失:調整anchor的位置和形狀,使其更接近於gt;

• RoI分類損失:區分RoI屬於哪個物體類別(這裡是21類,包括背景);

• RoI回歸損失:調整RoI的位置和形狀,使其更接近於gt、預測結果更精細。

由上述可知,其實在RPN輸出的時候,就已經完成了「檢測」任務,即能夠把目標物體框出來,只不過沒有對這些物體類別進行細分而已,並且框出來的位置可能不夠精準。而RoIHead可看作是對RPN結果的調優。

結語

從整體框架上來看,Faster R-CNN主要包含Feature Extractor(特徵提取)、RPN(產生候選區域)、RoIHead(檢測器)三部分,理論看似簡單,但是代碼實現起來真不容易。

自己在學習Faster R-CNN的時候,看了不少資料,也做了相關筆記,但覺得沒有真正學懂,有些點總是記不牢,不能夠在腦海里很好地復現。於是就決定擼一遍源碼,這樣之後總算是踏實下來了。

作為深度學習目標檢測領域中具有重大意義的算法,手擼一遍源碼還是很有必要的,如果只是知道它的原理,那麼並不真正代表會了,一個知識點你聽懂了和你能夠把它復現甚至改進是完全兩碼事,實踐才是檢驗成果的硬道理!

關鍵字: