更靈活、有個性的卷積——可變形卷積(Deformable Conv)
作者簡介
CW,廣東深圳人,畢業於中山大學(SYSU)數據科學與計算機學院,畢業後就業於騰訊計算機系統有限公司技術工程與事業群(TEG)從事Devops工作,期間在AI LAB實習過,實操過道路交通元素與醫療病例圖像分割、視頻實時人臉檢測與表情識別、OCR等項目。
目前也有在一些自媒體平台上參與外包項目的研發工作,項目專註於CV領域(傳統圖像處理與深度學習方向均有)。
前言
相信大家在看paper的時候或多或少都能見到Deformable操作的身影,這種可變形操作可嵌入到算法中的許多部分,最常見的是可變形卷積,另外還有對候選區域的池化等,它們都是從 Deformable Convolutional Networks(DCN) 中衍生出來的。
作者將DCN中有關可變形卷積的知識梳理了一番,同時基於Pytorch框架進行源碼實現,在加深理解的同時還可方便自己日後使用,感興趣的朋友們也可以共同享用,我不獨食,一起嚼,更香!
本文概述
I. Deformable Conv:我是個會變形(不是變性哦)的個性boy
II. 揭秘可變形卷積大法
III. 還沒吃飽?這就來解析源碼!
Deformable Conv:我是個會變形的個性boy
傳統的卷積操作是將特徵圖分成一個個與卷積核大小相同的部分,然後進行卷積操作,每部分在特徵圖上的位置都是固定的。這樣,對於形變比較複雜的物體,使用這種卷積的效果就可能不太好了。
對於這種情況,傳統做法有豐富數據集,引入更多複雜形變的樣本、使用各種數據增強和trick,以及人工設計一些手工特徵和算法。
基於數據集和數據增強的做法都有點「暴力」,通常收斂慢而且需要較複雜的網絡結構來配合;而基於手工特徵算法就實在是有點「太難了」。特變是物體形變可能千變萬化,這種做法本身難度就很大,而且不靈活,煉丹本身就夠辛苦了,何必這麼折騰呢?
這時候,Deformable Conv 出道了!他站上演講台,說他是個性boy,他會變形,不像常規卷積那樣死板,他更靈活,可以應對上述提到的物體複雜形變的場景。
那 Deformable Conv 是怎麼解決問題的呢?
Deformable Conv 在感受野中引入了偏移量,而且這偏移量是可學習的,我這招可以使得感受野不再是死板的方形,而是與物體的實際形狀貼近,這樣之後的卷積區域便始終覆蓋在物體形狀周圍,無論物體如何形變,我加入可學習的偏移量後通通搞定!」 話音剛落,他還順帶秀出了他這招的效果:

可變形卷積 vs 標準卷積
上圖(a)中綠色的點代表原始感受野範圍,(b)、(c)和(d)中的藍色點代表加上偏移量後新的感受野位置,可以看到添加偏移量後可以應對諸如目標移動、尺寸縮放、旋轉等各種情況。
揭秘可變形卷積大法
Deformable conv 一直遵奉開源協同,共同學習,共同進步,大家好才是真的好,世界才美好!下面他就為大家揭秘了他的大招——可變形卷積大法。
傳統的卷積結構可以定義成公式1,其中
是輸出特徵圖的每個點,與卷積核中心點對應,pn是p0在卷積核範圍內的每個偏移量。

常規卷積公式
而可變形卷積則在上述公式1的基礎上為每個點引入了一個偏移量
,偏移量是由輸入特徵圖與另一個卷積生成的,通常是小數。

可變形卷積公式
由於加入偏移量後的位置非整數,並不對應feature map上實際存在的像素點,因此需要使用插值來得到偏移後的像素值,通常可採用雙線性插值,用公式表示如下:

雙線性插值公式
上述公式的意義就是說將插值點位置的像素值設為其4領域像素點的加權和,領域4個點是離其最近的在特徵圖上實際存在的像素點,每個點的權重則根據它與插值點橫、縱坐標的距離來設置,公式最後一行的max(0, 1-…)就是限制了插值點與領域點不會超過1個像素的距離。

可變形卷積示意圖
Deformable Conv boy給出了可變形卷積操作的簡單示意圖,可以看到offsets是額外使用一個卷積來生成的,與最終要做卷積操作那個卷積不是同一個。

源碼解析
畢竟煉丹本來就夠玄的了,敘述完原理後,如果沒有實實在在的代碼,那就如同痴人說夢話!Deformable Conv boy不愧是大度的boy,他二話不說,立刻就對他的大招進行源碼解析。
常規操作,他使用nn.Module的子類來封裝可變形卷積操作,其中modulation是可選參數,若設置為True,那麼在進行卷積操作時,對應卷積核的每個位置都會分配一個權重。

Deformable Conv 源碼(i)
p_conv是生成offsets所使用的卷積,輸出通道數為卷積核尺寸的平方的2倍,代表對應卷積核每個位置橫縱坐標都有偏移量,因此需要乘2。
conv則是最終實際要進行的卷積操作,注意這裡步長設置為卷積核大小,因為與該卷積核進行卷積操作的特徵圖是由輸出特徵圖中每個點擴展為其對應卷積核那麼多個點後生成的。
比如conv是3×3卷積,輸出特徵圖尺寸2×3(hxw),那麼其每個點都被擴展為9(3×3)個點,對應卷積核的每個位置。於是與conv進行卷積的特徵圖尺寸變為(2×3) x (3×3),將stride設置為3,最終輸出的特徵圖尺寸就剛好是2×3。

Deformable Conv 源碼(ii)
以上還對p_conv和m_conv的權重進行了初始化。p_conv的權重初始化為0,代表初始時沒有偏移量;m_conv的權重初始化為1,代表初始時卷積核每個位置的權重都為1。
下圖這部分對應的就是上一節講到的可變形卷積公式 
,即輸出特徵圖上每點(對應卷積核中心)加上其對應卷積核每個位置的相對(橫、縱)坐標後再加上自學習的(橫、縱坐標)偏移量。
p0就是將輸出特徵圖每點對應到卷積核中心,然後映射到輸入特徵圖中的位置;pn則是p0對應卷積核每個位置的相對坐標。
比如使用3×3卷積,那麼對於卷積核的1個中心點來說,在pn中,橫、縱相對坐標各3個值(-1、0、1),組合起來一共有9(3×3)個值,pn僅與卷積核尺寸相關。p0、pn、offset的形狀都是一樣的,其中N為卷積核尺寸的平方(如果是3×3卷積的話,N就是9)。

Deformable Conv 源碼(iii)
接下來解析p0的計算,p0_y、p0_x就是輸出特徵圖每點映射到輸入特徵圖上的縱、橫坐標值。根據torch.arange()那部分可知,縱、橫坐標分別都有out_h和out_w個,與輸出特徵圖尺寸對應。
kc是卷積核中心位置,比如3×3卷積的話,中心點位置就是(1,1),然後根據卷積的步長和輸出特徵圖尺寸就能得到在每個卷積過程中,中心點對應在輸入特徵圖上的位置。
最後,這裡將p0_y和p0_x進行reshape是為了在後續操作時和pn以及offset的形狀對應上。

Deformable Conv 源碼(iv)
搞定完p0,接下來擼一擼pn。與p0的計算類似,只不過其僅由卷積核尺寸決定,由於卷積核中心點位置是其尺寸的一半,於是中心點向左(上)方向移動尺寸的一半就得到起始點,向右(下)方向移動另一半就得到終止點,這就是以下torch.arange()部分對應的內容。

Deformable Conv 源碼(v)
OK,至此我們得到了卷積時每點偏移後的位置,但是如上一節所述,這些位置通常是小數,並不對應特徵圖上實際的像素點,Deformable Conv boy 使用雙線性插值來計算這些位置的像素值,使用這招首先需要知道每個位置點最近的4領域點,它們是特徵圖上實際的像素點,再根據4領域點與插值點位置的距離設置權重,最終由這些權重和像素值進行加權求和得到插值點的像素。
以下就是得到4領域點位置的計算過程,同時需要注意將這些位置限制在輸入特徵圖尺寸範圍內。

Deformable Conv 源碼(vi)

Deformable Conv 源碼(vii)
計算出4領域點的位置後,理所當然地,我們需要知道它們的像素值,下圖中由_get_x_q()這個方法得到。另外,g_xx部分計算的是4領域點對應的權重。

Deformable Conv 源碼(viii)
最後,將4領域點的權重和像素值進行加權求和得到插值點像素值。

Deformable Conv 源碼(ix)
以上雙線性插值的計算過程可結合下面幾張圖來理解。

雙線性插值示意圖(i)

雙線性插值示意圖(ii)
由於領域間像素點的橫、縱坐標之差都是1,於是可以簡化為下面的公式。

雙線性插值示意圖(iii)
搞定這部分,我們已經可以得到偏移後位置的像素值了,但是還沒有解釋如何得到4領域點的像素值,現在就來一探究竟。
q是領域點在輸入特徵圖上的位置,其最後一維2N的前半部分代表縱坐標,後半部分代表橫坐標,將輸入特徵圖x進行reshape後,最後一維是高和寬的乘積,於是q[.., :N] * in_w + q[…, N:]就是為了和x的位置對應上。
最後使用Pytorch內置的gather方法,將對應位置的張量值取出。

Deformable Conv 源碼(x)
知道了如何得到領域點的像素值,之前的困惑也即煙消雲散,我們終於可以進行真正的卷積操作了!也不容易吶~
首先,如果設置了modulation,那麼就對每個位置乘上其對應的權重,在這之前注意要將權重m的形狀變為和x_offset一致。然後需要對x_offset也進行reshape操作,在這一節開頭時談到self.conv的步長設置時就提到了,最終進行卷積時特徵圖的尺寸寬、高是輸出特徵圖寬、高的卷積核尺寸倍數,結合卷積核大小及步長(同樣為卷積核尺寸),最終輸出的特徵圖尺寸便剛剛好。

Deformable Conv 源碼(xi)
Deformable Conv boy 也是夠「盡心儘力」,連最後一點私藏都要揭秘,他還秀出是如何對x_offset進行reshape的。

Deformable Conv 源碼(xii)
但是他不再多言,最後這部分留給大家細細品味,他已算是「仁至義盡」了,台下眾人也都服了,在心裏傳送出「6666666…」電波。
總結
每當寫文章涉及到源碼解析的內容時我都覺得真心不容易,有些代碼的實現很難用文字去敘述明白(所謂「道可道,非常道」..),幾乎每句話我都會斟酌好幾番才輸出,寫完後也會反覆閱讀是否有不妥,我要求自己在保證準確性的同時能敘述得簡單易懂。另外,為了避免文章讀起來晦澀無趣,我有時也會使用些網絡詞語營造一個輕鬆活躍的氣氛。
儘管代碼解析的內容讀起來可能有點繞,但我始終偏向於在文章中將這部分內容囊括進來,因為只講原理實在太空洞,只有結合了代碼進行實踐,才是把算法知識落實了下去。
如果各位對文中內容有疑惑或者發現文中內容有誤,請積極反饋,謝謝!


