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

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

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

由于加入偏移量后的位置非整數(shù),并不對應(yīng)feature map上實際存在的像素點,因此需要使用插值來得到偏移后的像素值,通??刹捎秒p線性插值,用公式表示如下:

上述公式的意義就是說將插值點位置的像素值設(shè)為其4領(lǐng)域像素點的加權(quán)和,領(lǐng)域4個點是離其最近的在特征圖上實際存在的像素點,每個點的權(quán)重則根據(jù)它與插值點橫、縱坐標的距離來設(shè)置,公式最后一行的max(0, 1-...)就是限制了插值點與領(lǐng)域點不會超過1個像素的距離。

Deformable Conv boy給出了可變形卷積操作的簡單示意圖,可以看到offsets是額外使用一個卷積來生成的,與最終要做卷積操作那個卷積不是同一個。
源碼解析
畢竟煉丹本來就夠玄的了,敘述完原理后,如果沒有實實在在的代碼,那就如同癡人說夢話!Deformable Conv boy不愧是大度的boy,他二話不說,立刻就對他的大招進行源碼解析。
常規(guī)操作,他使用nn.Module的子類來封裝可變形卷積操作,其中modulation是可選參數(shù),若設(shè)置為True,那么在進行卷積操作時,對應(yīng)卷積核的每個位置都會分配一個權(quán)重。

p_conv是生成offsets所使用的卷積,輸出通道數(shù)為卷積核尺寸的平方的2倍,代表對應(yīng)卷積核每個位置橫縱坐標都有偏移量,因此需要乘2。
conv則是最終實際要進行的卷積操作,注意這里步長設(shè)置為卷積核大小,因為與該卷積核進行卷積操作的特征圖是由輸出特征圖中每個點擴展為其對應(yīng)卷積核那么多個點后生成的。
比如conv是3x3卷積,輸出特征圖尺寸2x3(hxw),那么其每個點都被擴展為9(3x3)個點,對應(yīng)卷積核的每個位置。于是與conv進行卷積的特征圖尺寸變?yōu)?2x3) x (3x3),將stride設(shè)置為3,最終輸出的特征圖尺寸就剛好是2x3。

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

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

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

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


計算出4領(lǐng)域點的位置后,理所當然地,我們需要知道它們的像素值,下圖中由_get_x_q()這個方法得到。另外,g_xx部分計算的是4領(lǐng)域點對應(yīng)的權(quán)重。

最后,將4領(lǐng)域點的權(quán)重和像素值進行加權(quán)求和得到插值點像素值。

以上雙線性插值的計算過程可結(jié)合下面幾張圖來理解。


由于領(lǐng)域間像素點的橫、縱坐標之差都是1,于是可以簡化為下面的公式。

搞定這部分,我們已經(jīng)可以得到偏移后位置的像素值了,但是還沒有解釋如何得到4領(lǐng)域點的像素值,現(xiàn)在就來一探究竟。
q是領(lǐng)域點在輸入特征圖上的位置,其最后一維2N的前半部分代表縱坐標,后半部分代表橫坐標,將輸入特征圖x進行reshape后,最后一維是高和寬的乘積,于是q[.., :N] * in_w + q[..., N:]就是為了和x的位置對應(yīng)上。
最后使用Pytorch內(nèi)置的gather方法,將對應(yīng)位置的張量值取出。

知道了如何得到領(lǐng)域點的像素值,之前的困惑也即煙消云散,我們終于可以進行真正的卷積操作了!也不容易吶~
首先,如果設(shè)置了modulation,那么就對每個位置乘上其對應(yīng)的權(quán)重,在這之前注意要將權(quán)重m的形狀變?yōu)楹蛒_offset一致。然后需要對x_offset也進行reshape操作,在這一節(jié)開頭時談到self.conv的步長設(shè)置時就提到了,最終進行卷積時特征圖的尺寸寬、高是輸出特征圖寬、高的卷積核尺寸倍數(shù),結(jié)合卷積核大小及步長(同樣為卷積核尺寸),最終輸出的特征圖尺寸便剛剛好。

Deformable Conv boy 也是夠“盡心盡力”,連最后一點私藏都要揭秘,他還秀出是如何對x_offset進行reshape的。

但是他不再多言,最后這部分留給大家細細品味,他已算是“仁至義盡”了,臺下眾人也都服了,在心里傳送出“6666666...”電波。
總結(jié)
每當寫文章涉及到源碼解析的內(nèi)容時我都覺得真心不容易,有些代碼的實現(xiàn)很難用文字去敘述明白(所謂“道可道,非常道”..),幾乎每句話我都會斟酌好幾番才輸出,寫完后也會反復(fù)閱讀是否有不妥,我要求自己在保證準確性的同時能敘述得簡單易懂。另外,為了避免文章讀起來晦澀無趣,我有時也會使用些網(wǎng)絡(luò)詞語營造一個輕松活躍的氣氛。
盡管代碼解析的內(nèi)容讀起來可能有點繞,但我始終偏向于在文章中將這部分內(nèi)容囊括進來,因為只講原理實在太空洞,只有結(jié)合了代碼進行實踐,才是把算法知識落實了下去。

覺得文章很贊的話,可以點贊關(guān)注我們~
歡迎大家在評論區(qū)討論交流!