昨天介紹了 Kernel 的設計。
當時提到,設計一個 Kernel 並不是先想矩陣長什麼樣子,而是先思考希望圖片發生什麼變化?
如果今天希望降低圖片中的雜訊,最直覺的方法會是什麼?
不要只相信某一個 Pixel,而是參考附近的 Pixel。
因為雜訊通常只出現在少數幾個 Pixel。
如果周圍八個 Pixel 都差不多,只有中間突然特別亮或特別暗,那它很可能就是雜訊。

既然如此,那是不是可以把周圍 Pixel 一起拿來平均?
這就是 Mean Filter(平均濾波器) 最基本的想法。
假設圖片中有一個 3×3 的區域。
20 22 21
23 100 24
22 23 21
中間的 Pixel 是 100,看起來是不是有點奇怪?
它和周圍 Pixel 相差很多,如果直接相信它,圖片就可能出現一個不自然的亮點。
Mean Filter 的做法就是:
把九個 Pixel 全部加起來,再除以 9。
(20 + 22 + 21
+23 +100 +24
+22 +23 +21) / 9
得到:
30.7
新的 Pixel 就會變成大約 31。
原本很突兀的亮點,也因此被周圍 Pixel 拉回來了。
上一篇提到,Kernel 本質上就是一組權重。
如果每個 Pixel 都一樣重要,那權重自然就會是:
1/9 1/9 1/9
1/9 1/9 1/9
1/9 1/9 1/9
也有人寫成:
1 1 1
1 1 1
1 1 1
最後再除以 9。
兩種寫法代表的是同一件事情:每個 Pixel 都有相同的重要性。
OpenCV 已經提供了 Convolution 的函式,因此只需要建立 Kernel。
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread("dog.jpeg")
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
kernel = np.ones((3,3), dtype=np.float32) / 9
result = cv2.filter2D(img, -1, kernel)
接著把原圖和結果放在一起比較。
plt.figure(figsize=(10,5))
plt.subplot(1,2,1)
plt.imshow(img)
plt.title("Original")
plt.axis("off")
plt.subplot(1,2,2)
plt.imshow(result)
plt.title("Mean Filter")
plt.axis("off")
plt.tight_layout()
plt.show()
結果:

可以發現,圖片變得比原本柔和了一些。
如果是細小的雜訊,也會跟著減少。
如果把 3×3 改成 5×5。
kernel = np.ones((5,5), dtype=np.float32) / 25
甚至改成 9×9。
kernel = np.ones((9,9), dtype=np.float32) / 81
每次平均的 Pixel 越多,圖片就會越模糊。
這是因為更多周圍資訊被混合在一起,原本細微的變化也逐漸被平均掉了。
Mean Filter 最大的優點就是簡單。
它容易理解,也容易實作。
對於一些隨機雜訊,也能有不錯的效果。
因此它常被拿來當作學習 Convolution 的第一個例子。
不過,它也有一個很明顯的缺點。
因為每個 Pixel 的權重都一樣。
所以它不知道哪些 Pixel 是雜訊,哪些是真正的影像細節。
結果就是:
雜訊被平均了,但邊緣也一起被平均了。
例如物體的輪廓,可能會變得沒有原本那麼清楚。
這也是 Mean Filter 最常被討論的缺點。
看到這裡,應該會有另一個疑問。
既然大家都知道 Mean Filter 會把邊緣一起模糊掉。
那有沒有一種方法,可以讓中心 Pixel 比較重要,而離得越遠的 Pixel 影響越小?
答案是:有,而且就是下一篇要介紹的 Gaussian Filter(高斯濾波)。
它一樣是在做平滑,但權重不再全部相同,而是根據距離重新分配,因此通常能保留更多影像細節。
下一篇,我們就來看看 Gaussian Filter 為什麼會成為影像處理中最常見的濾波方法之一。