1. 准備工作
1) 首先從GitHub上下載本章所用的代碼, 地址如下:
git clone git@github.com:dongdonghy/Detection-PyTorch-Notebook.git
2) 然后創建data文件夾
cd Detection-PyTorch-Notebook/chapter4/faster-rcnn-pytorch && mkdir data
3) 下載PASCAL VOC 2012數據集, 在data文件夾下創建軟連接
cd data && ln -s "your data directory" VOC2012
4) 從下面網址下載預訓練模型並放到data/pretrained_model/文件夾下
https://www.dropbox.com/s/s3brpk0bdq60nyb/vgg16_caffe.pth?dl=0
5) 安裝所需依賴包
pip install -r requirements.txt
6) 根據自己的GPU型號, 修改lib/make.sh文件中的 CUDA_ARCH
7) 由於在NMS與RoI Pooling中使用了CUDA進行加速, 因此還需要對此進行編譯
cd lib && sh make.sh
2. Faster RCNN總覽
如圖4.3所示為Faster RCNN算法的基本流程, 從功能模塊來講, 主要包括4部分: 特征提取網絡、 RPN模塊、 RoI Pooling(Region of Interest) 模塊與RCNN模塊, 虛線表示僅僅在訓練時有的步驟。 Faster RCNN延續了RCNN系列的思想, 即先進行感興趣區域RoI的生成, 然后再把生成的區域分類, 最后完成物體的檢測, 這里的RoI使用的即是RPN模塊, 區域分類則是RCNN網絡。
特征提取網絡Backbone: 輸入圖像首先經過Backbone得到特征圖,在此以VGGNet為例, 假設輸入圖像的維度為3×600×800, 由於VGGNet包含4個Pooling層(物體檢測使用VGGNet時, 通常不使用第5個Pooling層) , 下采樣率為16, 因此輸出的feature map的維度為512×37×50。
RPN模塊: 區域生成模塊, 如圖4.3的中間部分, 其作用是生成較好的建議框, 即Proposal, 這里用到了強先驗的Anchor。 RPN包含5個子模塊:
·Anchor生成: RPN對feature map上的每一個點都對應了9個Anchors, 這9個Anchors大小寬高不同, 對應到原圖基本可以覆蓋所有可能出現的物體。 因此, 有了數量龐大的Anchors, RPN接下來的工作就是從中篩選, 並調整出更好的位置, 得到Proposal。
·RPN卷積網絡: 與上面的Anchor對應, 由於feature map上每個點對應了9個Anchors, 因此可以利用1×1的卷積在feature map上得到每一個Anchor的預測得分與預測偏移值。
·計算RPN loss: 這一步只在訓練中, 將所有的Anchors與標簽進行匹配, 匹配程度較好的Anchors賦予正樣本, 較差的賦予負樣本, 得到分類與偏移的真值, 與第二步中的預測得分與預測偏移值進行loss的計算。
·生成Proposal: 利用第二步中每一個Anchor預測的得分與偏移量,可以進一步得到一組較好的Proposal, 送到后續網絡中。
·篩選Proposal得到RoI: 在訓練時, 由於Proposal數量還是太多(默認是2000) , 需要進一步篩選Proposal得到RoI(默認數量是256) 。 在測試階段, 則不需要此模塊, Proposal可以直接作為RoI, 默認數量為300。
RoI Pooling模塊: 這部分承上啟下, 接受卷積網絡提取的feature map和RPN的RoI, 輸出送到RCNN網絡中。 由於RCNN模塊使用了全連接網絡, 要求特征的維度固定, 而每一個RoI對應的特征大小各不相同, 無法送入到全連接網絡, 因此RoI Pooling將RoI的特征池化到固定的維度, 方便送到全連接網絡中。
RCNN模塊: 將RoI Pooling得到的特征送入全連接網絡, 預測每一個RoI的分類, 並預測偏移量以精修邊框位置, 並計算損失, 完成整個
Faster RCNN過程。 主要包含3部分:
·RCNN全連接網絡: 將得到的固定維度的RoI特征接到全連接網絡中, 輸出為RCNN部分的預測得分與預測回歸偏移量。
·計算RCNN的真值: 對於篩選出的RoI, 需要確定是正樣本還是負樣本, 同時計算與對應真實物體的偏移量。 在實際實現時, 為實現方便, 這一步往往與RPN最后篩選RoI那一步放到一起。
·RCNN loss: 通過RCNN的預測值與RoI部分的真值, 計算分類與回歸loss。
從整個過程可以看出, Faster RCNN是一個兩階的算法, 即RPN與RCNN, 這兩步都需要計算損失, 只不過前者還要為后者提供較好的感興趣區域。 具體每一個模塊的細節實現, 將在后面章節中詳細講解。
3. 詳解RPN
RPN部分的輸入、 輸出如下
·輸入: feature map、 物體標簽, 即訓練集中所有物體的類別與邊框位置。
·輸出: Proposal、 分類Loss、 回歸Loss, 其中, Proposal作為生成的區域, 供后續模塊分類與回歸。 兩部分損失用作優化網絡。
RPN模塊的總體代碼邏輯如下, 源代碼文件見lib/model/faster_rcnn/faster_rcnn.py
3.1 理解Anchor
理解Anchor是理解RPN乃至Faster RCNN的關鍵。 Faster RCNN先提供一些先驗的邊框, 然后再去篩選與修正, 這樣在Anchor的基礎上做物體檢測要比從無到有的直接擬合物體的邊框容易一些。
Anchor的本質是在原圖大小上的一系列的矩形框, 但Faster RCNN將這一系列的矩形框和feature map進行了關聯。 具體做法是, 首先對feature map進行3×3的卷積操作, 得到的每一個點的維度是512維, 這512維的數據對應着原始圖片上的很多不同的大小與寬高區域的特征,這些區域的中心點都相同。 如果下采樣率為默認的16, 則每一個點的坐標乘以16即可得到對應的原圖坐標。
為適應不同物體的大小與寬高, 在作者的論文中, 默認在每一個點上抽取了9種Anchors, 具體Scale為{8,16,32}, Ratio為{0.5,1,2}, 將這9種Anchors的大小反算到原圖上, 即得到不同的原始Proposal, 如圖4.4所示。 由於feature map大小為37×50, 因此一共有37×50×9=16650個Anchors。 而后通過分類網絡與回歸網絡得到每一個Anchor的前景背景概率和偏移量, 前景背景概率用來判斷Anchor是前景的概率, 回歸網絡則是將預測偏移量作用到Anchor上使得Anchor更接近於真實物體坐標。
在具體的代碼實現時, lib/model/rpn下的anchor_target_layer.py與proposal_layer.py在類的初始化中均生成了所需的Anchor, 下面從代碼角度簡單講解一下生成過程, 源代碼文件見lib/model/rpn/generate_anchors.py。
3.2 RPN的真值與預測量
理解RPN的預測量與真值分別是什么, 也是理解RPN原理的關鍵。對於物體檢測任務來講, 模型需要預測每一個物體的類別及其出現的位置, 即類別、 中心點坐標x與y、 寬w與高h這5個量。 由於有了Anchor這個先驗框, RPN可以預測Anchor的類別作為預測邊框的類別, 並且可以預測真實的邊框相對於Anchor的偏移量, 而不是直接預測邊框的中心點坐標x與y、 寬高w與h。
舉個例子, 如圖4.5所示, 輸入圖像中有3個Anchors與兩個標簽, 從位置來看, Anchor A、 C分別和標簽M、 N有一定的重疊, 而Anchor B位置更像是背景。
首先介紹模型的真值。 對於類別的真值, 由於RPN只負責區域生成, 保證recall, 而沒必要細分每一個區域屬於哪一個類別, 因此只需要前景與背景兩個類別, 前景即有物體, 背景則沒有物體。
RPN通過計算Anchor與標簽的IoU來判斷一個Anchor是屬於前景還是背景。 IoU的含義是兩個框的公共部分占所有部分的比例, 即重合比例。 在圖4.5中, Anchor A與標簽M的IoU計算公式如式(4-1) 所示。
當IoU大於一定值時, 該Anchor的真值為前景, 低於一定值時, 該Anchor的真值為背景。
然后是偏移量的真值。 仍以圖4.5中的Anchor A與標簽M為例, 假設Anchor A的中心坐標為xa與ya, 寬高分別為wa與ha, 標簽M的中心坐標為x與y, 寬高分別為w與h, 則對應的偏移真值計算公式如式(4-2) 所示。
從式(4-2) 中可以看到, 位置偏移tx與ty利用寬與高進行了歸一化, 而寬高偏移tw與th進行了對數處理, 這樣的好處是進一步限制了偏 移量的范圍, 便於預測。
有了上述的真值, 為了求取損失, RPN通過卷積網絡分別得到了類別與偏移量的預測值。 具體來講, RPN需要預測每一個Anchor屬於前景與背景的概率, 同時也需要預測真實物體相對於Anchor的偏移量, 記為t*x、 t*y、 t*w和t*h。 具體的網絡下一節細講。另外, 在得到預測偏移量后, 可以使用式(4-3) 的公式將預測偏移量作用到對應的Anchor上, 得到預測框的實際位置x*、 y*、 w*和h*。
如果沒有Anchor, 做物體檢測需要直接預測每個框的坐標, 由於框的坐標變化幅度大, 使網絡很難收斂與准確預測, 而Anchor相當於提供了一個先驗的階梯, 使得模型去預測Anchor的偏移量, 即可更好地接近真實物體。
實際上, Anchor是我們想要預測屬性的先驗參考值, 並不局限於矩形框。 如果需要, 我們也可以增加其他類型的先驗, 如多邊形框、 角度和速度等。
3.3 RPN卷積網絡
為了實現上述的預測, RPN搭建了如圖4.6所示的網絡結構。 具體實現時, 在feature map上首先用3×3的卷積進行更深的特征提取, 然后利用1×1的卷積分別實現分類網絡和回歸網絡。
在物體檢測中, 通常我們將有物體的位置稱為前景, 沒有物體的位置稱為背景。 在分類網絡分支中, 首先使用1×1卷積輸出18×37×50的特征, 由於每個點默認有9個Anchors, 並且每個Anchor只預測其屬於前景還是背景, 因此通道數為18。 隨后利用torch.view()函數將特征映射到2×333×75, 這樣第一維僅僅是一個Anchor的前景背景得分, 並送到Softmax函數中進行概率計算, 得到的特征再變換到18×37×50的維度,最終輸出的是每個Anchor屬於前景與背景的概率。
在回歸分支中, 利用1×1卷積輸出36×37×50的特征, 第一維的36包含9個Anchors的預測, 每一個Anchor有4個數據, 分別代表了每一個Anchor的中心點橫縱坐標及寬高這4個量相對於真值的偏移量。 RPN的網絡部分代碼如下, 源代碼文件見lib/model/rpn/rpn.py。
3.4 RPN真值的求取
上一節的RPN分類與回歸網絡得到的是模型的預測值, 而為了計算預測的損失, 還需要得到分類與偏移預測的真值, 具體指的是每一個Anchor是否對應着真實物體, 以及每一個Anchor對應物體的真實偏移值。 求真值的具體實現過程如圖4.7所示, 主要包含4步, 下面具體介紹。
1) Anchor生成
這部分與前面Anchor的生成過程一樣, 可以得到37×50×9=16650個Anchors。 由於按照這種方式生成的Anchor會有一些邊界在圖像邊框外, 因此還需要把這部分超過圖像邊框的Anchors過濾掉, 具體生成過程如下, 源代碼文件見lib/model/rpn/anchor_target_layer.py。
2) Anchor與標簽的匹配
為了計算Anchor的損失, 在生成Anchor之后, 我們還需要得到每個Anchor的類別, 由於RPN的作用是建議框生成, 而非詳細的分類, 因此只需要區分正樣本與負樣本, 即每個Anchor是屬於正樣本還是負樣本。
前面已經介紹了通過計算Anchor與標簽的IoU來判斷是正樣本還是負樣本。 在具體實現時, 需要計算每一個Anchor與每一個標簽的IoU,因此會得到一個IoU矩陣, 具體的判斷標准如下:
·對於任何一個Anchor, 與所有標簽的最大IoU小於0.3, 則視為負樣本。
·對於任何一個標簽, 與其有最大IoU的Anchor視為正樣本。
·對於任何一個Anchor, 與所有標簽的最大IoU大於0.7, 則視為正樣本。
匹配與篩選的代碼示例如下, 源代碼文件見lib/model/rpn/anchor_target_layer.py
需要注意的是, 上述三者的順序不能隨意變動, 要保證一個Anchor既符合正樣本, 也符合負樣本時, 賦予正樣本。 並且為了保證這一階段的召回率, 允許多個Anchors對應一個標簽, 而不允許一個標簽對應多個Anchors。
3) Anchor的篩選
由於Anchor的總數量接近於2萬, 並且大部分Anchor的標簽都是背景, 如果都計算損失的話則正、 負樣本失去了均衡, 不利於網絡的收斂。 在此, RPN默認選擇256個Anchors進行損失的計算, 其中最多不超過128個的正樣本。 如果數量超過了限定值, 則進行隨機選取。 當然,這里的256與128都可以根據實際情況進行調整, 而不是固定死的。
Anchor篩選的代碼示例如下, 源代碼文件見lib/model/rpn/anchor_target_layer.py。
4) 求解回歸偏移真值
上一步將每個Anchor賦予正樣本或者負樣本代表了預測類別的真值, 而回歸部分的偏移量真值還需要利用Anchor與對應的標簽求解得到, 具體公式見式4-2。
得到偏移量的真值后, 將其保存在bbox_targets中。 與此同時, 還需要求解兩個權值矩陣bbox_inside_weights和bbox_outside_weights, 前者是用來設置正樣本回歸的權重, 正樣本設置為1, 負樣本設置為0, 因為負樣本對應的是背景, 不需要進行回歸; 后者的作用則是平衡RPN分類損失與回歸損失的權重, 在此設置為1/256。
求解回歸的偏移真值示例如下, 源代碼文件見lib/model/rpn/anchor_target_layer.py。
真值的求取部分最后的輸出包含了分類的標簽label、 回歸偏移的真值bbox_targets, 以及兩個權重向量bbox_inside_weights與bbox_outside_weights。
5)損失函數設計
有了網絡預測值與真值, 接下來就可以計算損失了。 RPN的損失函數包含分類與回歸兩部分, 具體公式如式(4-4) 所示。
代表了256個篩選出的Anchors的分類損失, Pi為每一個Anchor的類別真值, p*i為每一個Anchor的預測類別。 由於RPN的作用是選擇出Proposal, 並不要求細分出是哪一類前景, 因此在這一階段是二分類, 使用的是交叉熵損失。 值得注意的是, 在F.cross_entropy()函數中集成了Softmax的操作, 因此應該傳入得分, 而非經過Softmax之后的預測值
代表了回歸損失, 其中bbox_inside_weights實際上起到了p*i進行篩選的作用, bbox_outside_weights起到了
來平衡兩部分損失的作用。 回歸損失使用了smoothL1函數, 具體公式如式(4-5) 與式(4-6) 所示。
從式(4-6) 中可以看到, smoothL1函數結合了1階與2階損失函數,原因在於, 當預測偏移量與真值差距較大時, 使用2階函數時導數太 大, 模型容易發散而不容易收斂, 因此在大於1時采用了導數較小的1階損失函數。
損失函數的代碼接口如下, 源代碼文件見ib/model/rpn/rpn.py。
6) NMS與生成Proposal
完成了損失的計算, RPN的另一個功能就是區域生成, 即生成較好的Proposal, 以供下一個階段進行細分類與回歸。
NMS生成Proposal的主要過程如圖4.8所示, 首先生成大小固定的全部Anchors, 然后將網絡中得到的回歸偏移作用到Anchor上使Anchor更加貼近於真值, 並修剪超出圖像尺寸的Proposal, 得到最初的建議區域。 在這之后, 按照分類網絡輸出的得分對Anchor排序, 保留前12000個得分高的Anchors。 由於一個物體可能會有多個Anchors重疊對應, 因此再應用非極大值抑制(NMS) 將重疊的框去掉, 最后在剩余的Proposal中再次根據RPN的預測得分選擇前2000個, 作為最終的Proposal, 輸出到下一個階段。
NMS與Proposal的篩選過程如下, 源代碼文件見lib/model/rpn/proposal_layer.py
7) 篩選Proposal得到RoI
在訓練時, 上一步生成的Proposal數量為2000個, 其中仍然有很多背景框, 真正包含物體的仍占少數, 因此完全可以針對Proposal進行再一步篩選, 過程與RPN中篩選Anchor的過程類似, 利用標簽與Proposal構建IoU矩陣, 通過與標簽的重合程度選出256個正負樣本。 這一步有3個作用:
·篩選出了更貼近真實物體的RoI, 使送入到后續網絡的物體正、 負樣本更均衡, 避免了負樣本過多, 正樣本過少的情況。
·減少了送入后續全連接網絡的數量, 有效減少了計算量。
·篩選Proposal得到RoI的過程中, 由於使用了標簽來篩選, 因此也為每一個RoI賦予了正、 負樣本的標簽, 同時可以在此求得RoI變換到對應標簽的偏移量, 這樣就求得了RCNN部分的真值。
具體實現時, 首先計算Proposal與所有的物體標簽的IoU矩陣, 然后根據IoU矩陣的值來篩選出符合條件的正負樣本。 篩選標准如下:
·對於任何一個Proposal, 其與所有標簽的最大IoU如果大於等於0.5, 則視為正樣本。
·對於任何一個Proposal, 其與所有標簽的最大IoU如果大於等於0且小於0.5, 則視為負樣本。
經過上述標准的篩選, 選出的正、 負樣本數量不一, 在此設定正、負樣本的總數為256個, 其中正樣本的數量為p個。 為了控制正、 負樣本的比例基本滿足1:3, 在此正樣本數量p不超過64, 如果超過了64則從正樣本中隨機選取64個。 剩余的數量256-p為負樣本的數量, 如果超過了 256-p則從負樣本中隨機選取256-p個。經過上述操作后, 選出了最終的256個RoI, 並且每一個RoI都賦予了正樣本或者負樣本的標簽。 在此也可以進一步求得每一個RoI的真值, 即屬於哪一個類別及對應真值物體的偏移量。
篩選Proposal過程的代碼示例如下, 源代碼文件見lib/model/rpn/proposal_target_layer_cascade.py。
最終返回分類的真值label, 回歸的偏移真值bbox_targets, 以及每一個Proposal對應的權重bbox_inside_weights與bbox_outside_weights。
5. RoI Pooling層
上述步驟得到了256個RoI, 以及每一個RoI對應的類別與偏移量真值, 為了計算損失, 還需要計算每一個RoI的預測量。
前面的VGGNet網絡已經提供了整張圖像的feature map, 因此自然聯想到可以利用此feature map, 將每一個RoI區域對應的特征提取出來, 然后接入一個全連接網絡, 分別預測其RoI的分類與偏移量。
然而, 由於RoI是由各種大小寬高不同的Anchors經過偏移修正、 篩選等過程生成的, 因此其大小不一且帶有浮點數, 然而后續相連的全接網絡要求輸入特征大小維度固定, 這就需要有一個模塊, 能夠把各種維度不同的RoI變換到維度相同的特征, 以滿足后續全連接網絡的要求, 於是RoI Pooling就產生了。
對RoI進行池化的思想在SPPNet中就已經出現了, 只不過在FastRCNN中提出的RoI Pooling算法利用最近鄰差值算法將池化過程進行了簡化, 而在隨后的Mask RCNN中進一步提出了RoI Align的算法, 利用雙線性插值, 進一步提升了算法精度。
在此我們舉一個例子來講解這幾種算法的思想, 假設當前RoI大小為332×332, 使用VGGNet的全連接層, 其所需的特征向量維度為512×7×7, 由於目前的特征圖通道數為512, Pooling的過程就是如何獲得7×7大小區域的特征。
5.1)RoI Pooling簡介
RoI Pooling的實現過程如圖4.9所示, 假設當前的RoI為圖4.9中左側圖像的邊框, 大小為332×332, 為了得到這個RoI的特征圖, 首先需要將 該區域映射到全圖的特征圖上, 由於下采樣率為16, 因此該區域在特征圖上的坐標直接除以16並取整, 而對應的大小為332/16=20.75。 在此,RoI Pooling的做法是直接將浮點數量化為整數, 取整為20×20, 也就得到了該RoI的特征, 即圖4.9中第3步的邊框。
下一步還要將該20×20區域處理為7×7的特征, 然而20/7≈2.857, 次出現浮點數, RoI Pooling的做法是再次量化取整, 將2.857取整為2,然后以2為步長從左上角開始選取出7×7的區域, 這樣每個小方格在特征圖上都對應2×2的大小, 如圖4.9中第4步所示。
最后, 取每個小方格內的最大特征值, 作為這個小方格的輸出, 最終實現了7×7的輸出, 也完成了池化的過程, 如圖4.9中第5步所示。
從實現過程中可以看到, RoI本來對應於20.75×20.75的特征圖區域, 最后只取了14×14的區域, 因此RoI Pooling算法雖然簡單, 但量化取整帶來的偏差勢必會影響網絡, 尤其是回歸物體位置的准確率.
5.2) RoI Align簡介
RoI Align的思想是使用雙線性插值獲得坐標為浮點數的點的值, 主要過程如圖4.10所示, 依然將RoI對應到特征圖上, 但坐標與大小都保留着浮點數, 大小為20.75×20.75, 不做量化。
接下來, 將特征圖上的20.75×20.75大小均勻分成7×7方格的大小中間的點依然保留浮點數。 在此選擇其中2×2方格為例, 如圖4.11所示, 在每一個小方格內的特定位置選取4個采樣點進行特征采樣, 如圖4.11中每個小方格選擇了4個小黑點, 然后對這4個黑點的值選擇最大值, 作為這個方格最終的特征。 這4個小黑點的位置與值該如何計算呢?
對於黑點的位置, 可以將小方格平均分成2×2的4份, 然后這4份更小單元的中心點可以作為小黑點的位置。
至於如何計算這4個小黑點的值, RoI Align使用了雙線性插值的方法。 小黑點周圍會有特征圖上的4個特征點, 利用這4個特征點雙線性插值出該黑點的值。
由於Align算法最大可能地保留了原始區域的特征, 因此Align算法對檢測性能有顯著的提升, 尤其是對於受RoI Pooling影響大的情形, 如本身特征區域較小的小物體, 改善更為明顯。
6. 全連接RCNN模塊
在經過RoI Pooling層之后, 特征被池化到了固定的維度, 因此接下來可以利用全連接網絡進行分類與回歸預測量的計算。 在訓練階段, 最后需要計算預測量與真值的損失並反傳優化, 而在前向測試階段, 可以直接將預測量加到RoI上, 並輸出預測量。
6.1) RCNN全連接網絡
RCNN全連接網絡如圖4.12所示, 4.5節中256個RoI經過池化之后得到固定維度為512×7×7的特征, 在此首先將這三個維度延展為一維, 因為全連接網絡需要將一個RoI的特征全部連接起來。
接下來利用VGGNet的兩個全連接層, 得到長度為4096的256個RoI特征。 為了輸出類別與回歸的預測, 將上述特征分別接入分類與回歸的全連接網絡。 在此默認為21類物體, 因此分類網絡輸出維度為21, 回歸網絡則輸出每一個類別下的4個位置偏移量, 因此輸出維度為84。
值得注意的是, 雖然是256個RoI放到了一起計算, 但相互之間是獨立的, 並沒有使用到共享特征, 因此造成了重復計算, 這也是FasterRCNN的一個缺點。 RCNN全連接部分的代碼接口如下, 源代碼文件見lib/model/faster_rcnn/faster_rcnn.py。
6.2 損失函數設計
RCNN部分的損失函數計算方法與RPN部分相同, 不再重復。 只不過在此為21個類別的分類, 而RPN部分則是二分類, 需要注意回歸時至多有64個正樣本參與回歸計算, 負樣本不參與回歸計算。
計算RCNN損失的代碼接口如下, 源代碼文件見lib/model/faster_rcnn/faster_rcnn.py。