5.17. 標準卷積核目錄

傳統影像處理累積了相當數量的卷積核權重模式,它們反覆出現——邊緣偵測器、銳化器、浮雕、平滑器、運動模糊——而它們每一個都透過 morph() 執行。每一個都很簡短,每一個只做一件事,而且只要理解了權重的基本邏輯,大多數都很容易讀懂。

除非另有說明,以下的卷積核都是 3 乘 3 的,因此呼叫時它們都使用 size=1。每個卷積核的權重結構都會在旁邊說明,因為讀懂權重正是建立直覺、理解為何某個卷積核會產生浮雕而另一個會銳化的關鍵。

5.17.1. 恆等卷積核

最簡單的卷積核是 恆等(identity)卷積核——中心為 1,其餘皆為 0:

identity = [0, 0, 0,
            0, 1, 0,
            0, 0, 0]

img.morph(1, identity)

每個輸出像素的值取自其鄰域的中心,也就是相同位置的輸入像素。影像原封不動地通過。恆等卷積核作為濾鏡並無實際用途,但它是理解其他每一個卷積核的有用基準:任何非恆等卷積核都是恆等卷積核加上某種修改。

中心權重較大、周圍帶有小負權重的卷積核會從中心 減去 周圍的值。中心權重為零的卷積核則忽略像素本身,只對其鄰居之間的差異作出反應。以這種方式讀懂卷積核——中心權重對像素做了什麼,周圍的權重增加或減去了什麼——是預測其效果最快的方法。

5.17.2. 邊緣偵測

邊緣偵測 卷積核會對亮度在特定方向上快速變化的位置作出強烈反應,而在亮度均勻處則產生接近零的輸出。它們屬於權重總和為零的那一類:一塊平坦區域(每個像素值都相同)會產生零輸出,因為每個正權重都恰好被一個等量的負權重抵消。

Sobel-x 是典型的例子。它偵測 垂直 邊緣(左右亮度過渡):

sobel_x = [-1,  0,  1,
           -2,  0,  2,
           -1,  0,  1]

img.morph(1, sobel_x, mul=0.25, add=128)

與之對應的 Sobel-y 是相同模式旋轉 90 度;它偵測水平邊緣(上下亮度過渡):

sobel_y = [-1, -2, -1,
            0,  0,  0,
            1,  2,  1]

Sobel-x 的中間列權重為 -22,而非 -11。中心列上額外的權重讓卷積核在 沿著 邊緣的方向上內建了少量平滑作用,使它比捨棄這些額外幅度的較簡單的 Prewitt 運算子更能抵抗雜訊:

prewitt_x = [-1, 0, 1,
             -1, 0, 1,
             -1, 0, 1]

prewitt_y = [-1, -1, -1,
              0,  0,  0,
              1,  1,  1]

Prewitt 對每一列都施以相同的權重,因此它的反應比 Sobel 略為銳利,代價是對單一像素的雜訊更敏感(執行卷積核的成本是相同的——無論權重為何,卷積所做的運算量都一樣)。在邊緣強烈的乾淨影像上,它是 Sobel 完全可用的替代品。

Scharr 則往另一個方向走。它的權重較大,並針對在較細的角度下準確偵測邊緣方向而調校:

scharr_x = [-3,   0,  3,
            -10,  0, 10,
            -3,   0,  3]

img.morph(1, scharr_x, mul=0.0625, add=128)

mul=0.0625 這個除數(1/16)在較大的乘積和之後,將輸出帶回 0 -- 255 範圍內。當應用需要幾何上最忠實的梯度反應,且願意為此付出稍多的運算時,Scharr 就是正確的選擇。

5.17.3. 拉普拉斯運算子

拉普拉斯(Laplacian)卷積核會同時對 任何 方向的邊緣作出反應。Sobel 各自偵測沿一個軸的亮度變化,而拉普拉斯對稱的權重模式則無論邊緣朝向哪個方向都以相同方式反應:

laplacian_4 = [ 0, -1,  0,
               -1,  4, -1,
                0, -1,  0]

img.morph(1, laplacian_4, add=128)

其結構為:中心權重 4,四個水平/垂直鄰居權重為 -1,四個對角鄰居權重為零。卷積核總和為零,因此平坦區域產生零輸出。在亮度變化處,中心值與其四個正向鄰居的平均值不同,輸出即為該差異的大小。

8 連通變體則包含對角鄰居:

laplacian_8 = [-1, -1, -1,
               -1,  8, -1,
               -1, -1, -1]

每個卷積核偵測的東西略有不同。4 連通版本在水平與垂直邊緣上產生較乾淨的輸出;8 連通版本則更為各向同性——它在每個方向上的反應都同樣良好——但輸出略為雜訊較多。8 連通卷積核也以 outline(輪廓)之名流傳,源自其用於視覺化邊緣的用途。

5.17.4. 銳化

銳化 卷積核是恆等卷積核加上一個邊緣反應卷積核。輸出為原始影像加上一份邊緣的副本,因此高頻特徵相對於平滑的內部區域得到放大。

標準的 4 連通銳化卷積核將 4 連通拉普拉斯加到恆等卷積核上:

sharpen = [ 0, -1,  0,
           -1,  5, -1,
            0, -1,  0]

img.morph(1, sharpen)

讀懂這個卷積核:中心權重為 identity (1) + Laplacian centre (4) = 5,周圍則與拉普拉斯相同。平坦區域產生 5 * 1 - 4 * 1 = 1 倍的中心值——也就是恆等。邊緣則產生原始值加上拉普拉斯反應。權重總和為 1,因此 muladd 保持其預設值。

若要更強的銳化,8 連通變體則走得更遠:

sharpen_strong = [-1, -1, -1,
                  -1,  9, -1,
                  -1, -1, -1]

img.morph(1, sharpen_strong)

中心權重 9identity (1) + Laplacian-8 centre (8)。邏輯相同,放大更多,但也更有可能放大 sensor 雜訊。

強銳化卷積核本質上就是帶有 unsharp=Truegaussian(),只是直接以卷積核的形式表達,而非透過 unsharp-mask 旗標。像素層級的行為是相同的;選擇取決於具名方法的便利性與手動調校卷積核的精細控制之間的取捨。

5.17.5. 浮雕

浮雕(emboss)卷積核會產生傳統影像編輯器中那種側面打光的效果。輸出看起來就像影像被擠壓成浮雕,然後從某個角落打光:

emboss = [-2, -1,  0,
          -1,  1,  1,
           0,  1,  2]

img.morph(1, emboss, add=128)

其訣竅在於 跨對角線的不對稱性。左上角具有最負的權重,右下角具有最正的權重,而從一角到另一角的對角線則由負經過 1 變化到正。在每個像素處,卷積核本質上計算的是「我右下方的亮度減去我左上方的亮度」,在影像於該方向上變亮處為正,變暗處為負。加上 128 將帶符號的輸出重新置中至中灰,使效果可見。

將不對稱性繞另一條對角線旋轉,則會從相反方向產生浮雕:

emboss_alt = [ 0,  1,  2,
              -1,  1,  1,
              -2, -1,  0]

img.morph(1, emboss_alt, add=128)

當應用需要偵測方向時,這兩個浮雕方向組合起來很有用——將其中一個減去另一個,或在同一影像上各自執行並比較其反應。

5.17.6. 平滑

平滑卷積核屬於權重總和為一(且全部非負)的那一類。一塊平坦區域通過這樣的卷積核會產生相同的平坦亮度,因為卷積核是將像素值平均在一起,而非放大它們之間的差異。

最簡單的是 box blur(方框模糊),它正是 mean() 所計算的:

box_blur = [1, 1, 1,
            1, 1, 1,
            1, 1, 1]

img.morph(1, box_blur)

這個卷積核的總和為 9,因此以卷積核總和自動相除,會將乘積和轉換成對九個鄰域像素的真正平均。實務上 mean() 是執行這個卷積核的更佳方式——它透過一條專為計算平均(別無其他)而最佳化的路徑,更快地產生相同的輸出,而 morph 則執行通用的卷積機制。box blur 之所以列入目錄,是因為它是理解其他每一個平滑卷積核的正確基準。

Gaussian(高斯)的 3 乘 3 近似對中心與正向鄰居的加權高於對角:

gaussian = [1, 2, 1,
            2, 4, 2,
            1, 2, 1]

img.morph(1, gaussian)

這些權重是帕斯卡三角形的某一列 1, 2, 1 與自身做外積的結果。中心權重 4 最大,因為中心像素對其自身輸出的貢獻最多;角落為 1,因為它們離中心最遠。卷積核總和為 16,以卷積核總和自動相除即處理了正規化——不需要 mul 引數。3 乘 3 的形式是真正高斯的粗略近似,在 size=1 時與 gaussian() 難以區分;morph 形式主要在應用希望於同一遍處理中將平滑與另一操作組合起來時才有用。

5.17.7. 運動模糊

motion-blur(運動模糊)卷積核會沿 一個方向 平均像素,而讓垂直方向保持不模糊。最簡單的情況是水平方向:

motion_h = [0, 0, 0,
            1, 1, 1,
            0, 0, 0]

img.morph(1, motion_h)

中間列沿水平軸平均三個像素;頂列與底列為零。卷積核總和為 3,因此以卷積核總和自動相除會產生真正的三像素平均,而不需要任何 mul。輸出為輸入的水平塗抹副本——也就是相機在曝光期間被攝主體橫向移動時所捕捉到的效果。垂直運動模糊則是相同模式旋轉而成:

motion_v = [0, 1, 0,
            0, 1, 0,
            0, 1, 0]

對角運動模糊使用主對角線:

motion_diag = [1, 0, 0,
               0, 1, 0,
               0, 0, 1]

img.morph(1, motion_diag)

運動模糊卷積核作為一種 效果(為了視覺目的而刻意模糊一個影格)很有用,也可作為演算法的 測試模式,用於那些需要抵抗運動偽影的演算法(在運動模糊的輸入上執行演算法,並檢查它是否仍能產生正確的答案)。

5.17.8. 一眼讀懂卷積核

幾條經驗法則能讓新的卷積核更容易一眼看懂:

  • 總和為一 且權重非負 ⇒ 平滑(保持平均亮度)。

  • 總和為零 且同時具有正負權重 ⇒ 邊緣反應(在平坦區域上為零)。

  • 總和為一 且具有較大的正中心與較小的負周圍 ⇒ 銳化(恆等加上邊緣反應)。

  • 跨對角線不對稱 且總和為一 ⇒ 浮雕(凸顯每個亮度過渡的一側)。

  • 集中於一個軸上 且總和為一 ⇒ 方向性模糊。

卷積核所符合的這些規則中的第一條,通常就是猜測它作用的正確答案。大多數有用的卷積核僅憑其權重模式的佈局就能辨識出來。

沒有任何 標準卷積核能達到應用所需的效果時,下一步就是手動調校一個。上述規則與 mul / add 控制的組合,幾乎涵蓋了傳統機器視覺管線曾經想要的每一種線性處理;從那裡開始,就是嘗試權重、查看輸出、反覆迭代的問題了。