如果你有看過 OpenCV 的文件,或是搜尋過影像處理相關的教學,應該很容易看到這些功能:
看起來都是不同的演算法。
但其實,它們的背後都有一個共同點,就是他們大部分都建立在 Convolution(卷積)之上。
也因為如此,Convolution 幾乎可以說是數位影像處理最重要的核心概念之一。
第一次看到「卷積」這個名詞是在工程數學中,所以我一直想說是一個很複雜的東西。
後來發現,如果先不要看公式,它其實可以理解成一件很單純的事情。
觀察一個 Pixel 周圍的鄰居,再重新決定這個 Pixel 的值。
也就是說,影像處理很多時候,不會只看某一個 Pixel,而是會一起參考它周圍的 Pixel。
例如下面這個 3×3 的區域。
20 30 40
25 35 45
30 40 50
假設現在要更新中間的 35,我們可以利用周圍八個 Pixel 一起計算。
新的值,可能變成:
34
也可能變成:
42
而會變成什麼數值,就看我們使用什麼規則。
這個規則,就是今天另一個重要的東西。
Kernel(卷積核)。
Kernel 可以想成一張很小的表格。
例如:
1 1 1
1 1 1
1 1 1
或是:
0 -1 0
-1 5 -1
0 -1 0
它本身不是圖片,是一組權重。
Convolution 就是利用這組權重,去重新計算圖片中的每一個 Pixel。
不同的 Kernel,就會得到不同的效果。
例如:
都是因為 Kernel 不同。
假設圖片的一小部分是:
20 30 40
25 35 45
30 40 50
現在使用下面這個 Kernel。
1 1 1
1 1 1
1 1 1
首先,把兩個矩陣對齊。
接著對應位置相乘。
最後全部加起來。
20×1 + 30×1 + 40×1
+25×1 +35×1 +45×1
+30×1 +40×1 +50×1
得到:
315
如果這是一個平均濾波器(Mean Filter)。
最後還要除以 9,因為有九個格子。
315 ÷ 9 = 35
這就是新的 Pixel 值。
也就是說,Convolution 的流程其實就是:
接著 Kernel 再往右移一格。
整張圖片都重複這個流程。
第一個練習,我們先不要使用 OpenCV。
我們自己寫一個最簡單的版本。
import numpy as np
image = np.array([
[20, 30, 40],
[25, 35, 45],
[30, 40, 50]
], dtype=np.float32)
kernel = np.ones((3,3))
result = np.sum(image * kernel)
print(result)
執行後可以看到:
315.0
如果改成平均濾波。
print(result / 9)
就會得到:
35.0
這就是我們剛剛手算得到的結果。
OpenCV 已經幫我們實作好 Convolution。
使用 filter2D() 就可以完成。
import cv2
import numpy as np
kernel = np.ones((3,3), np.float32) / 9
result = cv2.filter2D(img, -1, kernel)
其中最重要的就是第三個參數。
kernel
只要換掉 Kernel。
得到的影像效果也會完全不同。
後面介紹的 Mean Filter、Sharpen、Sobel,其實都只是換了一組不同的 Kernel 而已。
今天我們認識了一個貫穿了整個數位影像處理的重要概念:
Convolution
它不是一種演算法,比較像是一種運算方式。
透過不同的 Kernel,可以讓圖片變模糊、變銳利,甚至找出圖片中的邊緣。
但這時候可能會有一個問題:
Kernel 怎麼選
所以明天,我們就要來討論如何設計一個 Kernel。