Mask RCNN介紹

Mask RCNN是何凱明大神以及Faster RCNN作者Ross B. Girshick等多人發表於ICCV 2017。我們知道對於圖像分類來說

將一張圖片送進神經網絡之後得到其分類的概率。對於目標檢測來說

我們將圖像送進神經網絡之後得到目標邊框和分類概率。這是一個FPN的過程。對於語義分割來說

我們將圖像送進神經網絡之後得到每一個像素的前景和背景。這相當於對像素進行一個二分類。這是一個FCN的過程。

Mask RCNN和Faster RCNN的結構是非常相似的,最大的不同就在於在最後的輸出的時候增加了一個多任務網絡Mask分支的結構,對於這個分支我們就可以對於每一個要檢測的目標去生成Mask分割蒙版。關於這個Mask分支有兩個版本

上圖中左邊的是不帶有特徵金字塔結構的版本,右邊是帶有特徵金字塔結構(FPN)的,我們比較常用的是右邊這種結構。在Faster RCNN中,特徵層通過RPN網絡後,緊跟着是一個ROI Pooling層,但是在Mask RCNN中被替換成了ROI Align。

上圖中的AP是精準率和召回率所圍成的面積,具體可以參考機器學習算法整理(三) 中的精準率和召回率的平衡。通過上圖我們可以看出使用了ROI Align替換了ROI Pooling之後,不論是bounding box的AP值還是Mask的AP值都有所提升,而且幅度還很大。

ROI Pooling的作用是將所有不同尺寸feature map裁剪的目標區域都調整成相同尺寸的輸出,再統一傳遞給FC層。這裏我們假設一個feature map相對原圖的降採樣爲32倍。

我們假設有一個目標,它對應的原圖左上角的座標爲[10,10],它的右下角對應原圖的座標爲[124,124]。因爲是原圖座標,我們要將其映射到feature map上,所以需要對座標值/32。10/32是不能被整除的,在ROI Pooling當中,經過四捨五入,得到feature map上的點是[0,0]。同樣124/32經過四捨五入後得到的座標點爲[4,4]。在上圖中的第一個表格中,我們將0~4的格子用黑色區域給標記出來,就是目標在feature map上的區域。這是第一次取整的情況。

此時我們將這個5*5的區域再進行一次2倍的最大池化降採樣,需要得到一個2*2區域大小的feature map。由於5無法被2整除,所以會將整個區域劃分成一個3*3、3*2、2*3、2*2的區域,最後我們得到的就是[[1.6871,0.4676],[2.0242,2.3571]]的矩陣,也就是上圖中下面表格中藍色區域的數字。

import torch
from torchvision.ops import RoIPool

if __name__ == '__main__':

    torch.manual_seed(1)
    x = torch.randn((1, 1, 6, 6))
    print(f"feature map: \n{x}")

    proposal = [torch.Tensor([[10, 10, 124, 124]])]
    roi_pool = RoIPool(output_size=2, spatial_scale=1/32)
    roi = roi_pool(x, proposal)
    print(f"roi pool: \n{roi}")

運行結果

feature map: 
tensor([[[[-1.5256, -0.7502, -0.6540, -1.6095, -0.1002, -0.6092],
          [-0.9798, -1.6091, -0.7121,  0.3037, -0.7773, -0.2515],
          [-0.2223,  1.6871,  0.2284,  0.4676, -0.6970, -1.1608],
          [ 0.6995,  0.1991,  0.1991,  0.0457,  0.1530, -0.4757],
          [-1.8821, -0.7765,  2.0242, -0.0865,  2.3571, -1.0373],
          [ 1.5748, -0.6298,  2.4070,  0.2786,  0.2468,  1.1843]]]])
roi pool: 
tensor([[[[1.6871, 0.4676],
          [2.0242, 2.3571]]]])

我們再來看一下ROI Align,條件跟上面一樣。

 此處我們對原圖的座標對feature map的映射不進行取整了,直接得到小數。10/32=0.3125,124/32=3.875。我們將feature map上的每個元素給抽象成下圖中的一個個黑點,左上角的座標就是[0,0],那麼我們的目標框對應的feature map上的座標[0.3125,0.3125]就是藍色框的左上角的點。目標框對應的feature map上的座標[3.875,3.875]就是藍色框右下角的點。此時我們就將目標框給映射到feature map上了,且是沒有進行四捨五入的。此時我們同樣要將該藍色框給降採樣成2*2的區域,所以我們將該藍色區域給四等份。那麼我們該如何對每一個四等份區域進行一個輸出呢?這裏涉及到一個sampling ratio,我們這裏取1。我們可以計算出四個等份區域的中心點的座標,我們以第一個等份區域爲例

再使用雙線性插值,得到第一個四等份區域的降採樣值爲

這裏的f1、f2、f3、f4是離這個中心點最近的四個黑點的值u是中心點距離左邊黑邊的距離v是中心點距離上邊黑邊的距離。最終得到的降採樣值爲-0.8546。同樣第二個四等份中心點座標爲

降採樣值爲

同樣第三個四等份中心點座標爲

降採樣值爲

同樣第四個四等份中心點座標爲

降採樣值爲

import torch
from torchvision.ops import RoIAlign

if __name__ == '__main__':

    torch.manual_seed(1)
    x = torch.randn((1, 1, 6, 6))
    print(f"feature map: \n{x}")

    proposal = [torch.Tensor([[10, 10, 124, 124]])]
    roi_align = RoIAlign(output_size=2, spatial_scale=1/32, sampling_ratio=1)
    roi = roi_align(x, proposal)
    print(f"roi pool: \n{roi}")

運行結果

feature map: 
tensor([[[[-1.5256, -0.7502, -0.6540, -1.6095, -0.1002, -0.6092],
          [-0.9798, -1.6091, -0.7121,  0.3037, -0.7773, -0.2515],
          [-0.2223,  1.6871,  0.2284,  0.4676, -0.6970, -1.1608],
          [ 0.6995,  0.1991,  0.1991,  0.0457,  0.1530, -0.4757],
          [-1.8821, -0.7765,  2.0242, -0.0865,  2.3571, -1.0373],
          [ 1.5748, -0.6298,  2.4070,  0.2786,  0.2468,  1.1843]]]])
roi pool: 
tensor([[[[-0.8546,  0.3236],
          [ 0.2177,  0.0546]]]])

根據這裏,我們可以看到ROI Align中,座標值在降採樣過程中沒有取整,它的定位會更加的準確。作者在論文中提到,關於最終的採樣結果對採樣點位置,以及採樣點的個數並不敏感。在源碼中使用的sampling ratio=2

雙線性插值

線性插值是指插值函數爲一次多項式的插值方式。線性插值的幾何意義即爲利用過A點和B點的直線來近似表示原函數。線性插值可以用來近似代替原函數,也可以用來計算得到查表過程中表中沒有的數值。

那麼如上圖所示,假設已知y1=f(x1),y2=f(x2),現在要通過線性插值的方式得到區間[x1,x2]內任何一點的f(x)值。我們很容易得到以下公式

變換可以得到

我們也可以寫成

雙線性插值,又稱爲雙線性內插。在數學上,雙線性插值是有兩個變量的插值函數的線性插值擴展,其核心思想是在兩個方向分別進行一次線性插值。雙線性插值就是分別在兩個方向上分別進行一次簡單的線性插值即可。

在上圖中,每個點的值是由z=f(x,y)的二元函數決定。已知Q11、Q12、Q22、Q21四個點的值,現在要在這四個點中插入一個點P,並算出P點的值。根據上圖我們已知Q11、Q12、Q22、Q21四個點的值

在求P點的值之前,首先根據線性插值的方法求得R1、R2的值。對於R1點,可以根據Q11、Q21兩個點根據線性插值的辦法得到。而Q11和Q21兩個點的y值是相同的,所以兩點的連線可看作只關於x一個變量的函數。

同理R2的值爲

得到R1、R2的插值後,接着我們去計算P點的值。R1、R2兩個點的x值是相同的,所以兩點的連線可看作只關於y一個變量的函數,通過線性插值公式可得

代入f(R1)、f(R2)後可得

圖像處理中的雙線性插值

由於Q11、Q12、Q22、Q21四個點是圖像中相鄰的像素,故有

代入公式可得

我們令

那麼有

進一步代入公式得

這就是我們在ROI Align中得到的降採樣值的計算公式。

Mask分支

在這張圖的右邊部分,我們可以看到它進行多任務網絡的處理,雖然在進行目標框迴歸、分類任務和Mask任務都使用了Roi Align,但是這兩個ROI是兩個不同的ROI,它們不共用ROI Align。在上面的分支中通過ROI Align得到的ROI是7*7的,而下面的分支則是14*14的。因爲對於分割任務而言,要求的分割結果精度要更高一些,所以我們需要保留更多的細節信息。所以這裏沒有池化到7*7大小,而是池化到了14*14,這有助於保留分割更好的結果。

假設這裏輸入的目標區域大小爲H*W*256,那麼通過RoiAlign之後被池化成了14*14*256,再依次通過四個卷積層,再通過一個反捲積,得到一個28*28*256的輸出,再通過一個1*1的卷積層,得到一個28*28*numclass的數據。也就是說對於每一個類別,我們都去預測了一個蒙版,並且這個蒙版都是一個28*28大小的。作者在論文中提到過,在Mask RCNN中,對預測Mask以及Class進行了解耦。

FCN中針對每一個像素對每一個類別都會預測一個概率分數,我們會針對每一個像素沿它的通道方向去做一個softmax處理,通過softmax處理之後,我們就能得到每一個像素它歸屬每一個類別的概率分數,這樣不同類別之間是存在一個競爭關係的。因爲通過softmax之後,每一個像素在通道方向的概率之和爲1,對於某一個類別的概率分數大的話,那麼其他類別的概率分數肯定就要變小,所以它們是存在競爭關係的。這裏Mask與Class是一個耦合的狀態。但是在Mask RCNN中是將它們進行了解耦,在之前我們說了會對每一個類別都預測出一個蒙版,但是不會針對每一個數據沿通道方向去做softmax處理,而是根據Faster RCNN分支中預測針對該目標的類別信息將Mask分支當中針對該類別的蒙版給提取出來,然後拿去直接使用。這樣類別與類別之間是不存在競爭關係的,因爲這裏運用的是Faster RCNN分支當中預測的類別信息。這樣就實現了Mask與Class解耦的過程。

這裏我們可以看到在FCN中,直接使用softmax,也就是Mask與Class是一個耦合的狀態,它的AP值只有24.8。而採用解耦的方式,也就是對應sigmoid這一行,它能達到的AP能達到30.3,提升了5.5之多,故在Mask RCNN中對其解耦是非常必要的。

還有一點非常重要的就是,在訓練網絡的時候,輸入Mask分支的目標是由RPN網絡提供的,即Proposals,它們全部都是正樣本。它是在Faster RCNN分支進行正負樣本匹配的時候得到的,也就是說將Proposals輸入到Faster RCNN分支中會進行正負樣本的匹配,此時就會得知每一個Proposal到底是屬於正樣本還是負樣本,以及這個Proposal所對應的ground truth的類別是什麼。這之後纔將所有的正樣本傳遞給Mask分支。但是在預測過程當中,輸入Mask的目標是由Faster RCNN提供的,不再是RPN網絡提供的。

由RPN網絡提供的目標邊界邊框可能並不是那麼準確,可能對於一個目標,可能提供了多個目標邊界框。由於輸入給Mask分支的目標邊界框都是正樣本,所以它必定跟目標是有交集的。那麼這些Proposals都可以提供給Mask分支進行訓練。這就相當於擴充了Mask分支訓練的樣本的個數,有點像隨機裁剪數據增強。但是在直接預測的時候是直接採用Faster RCNN的直接輸出了,對於最終預測的時候我們只需要最準確的目標邊界框,對於Faster RCNN得到的目標可能就只有一個目標邊界框,將這個目標輸入給Mask分支之後就可以得到一個更加精準的目標分割圖了。通過Faster RCNN之後是通過nms(非極大值抑制)過濾掉很多重合的目標邊界框,輸入給Mask分支的目標也會更少一些,目標少就意味着計算量會更小。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章