上一篇介紹了 Sobel Operator。
它利用兩組 Kernel,分別計算 X 和 Y 方向的亮度變化,再找出圖片中可能存在邊緣的位置。
在影像處理中,其實還有一個跟 Sobel Operator 長得很像的名詞:Prewitt Operator。
可是不是已經有 Sobel Operator 了嗎,幹嘛還要 Prewitt Operator?
Sobel 的 X 方向 Kernel。
-1 0 1
-2 0 2
-1 0 1
Prewitt 的 X 方向 Kernel。
-1 0 1
-1 0 1
-1 0 1
如果換成 Y 方向。
Sobel。
-1 -2 -1
0 0 0
1 2 1
Prewitt。
-1 -1 -1
0 0 0
1 1 1
看起來是不是只差了一個數字?
沒錯,這兩個最大的差別就在於:
Sobel 把中心那一列(或那一欄)的權重加大了。
上一篇提到,Sobel 認為距離中心最近的 Pixel 通常比較具有代表性。
因此,它把中間那一列(或那一欄)的權重提高,讓附近 Pixel 的影響力更大。
Prewitt 則沒有這樣做,它把三列(或三欄)都視為同樣重要。
因此,每個方向的權重都完全一致。
如果只是計算亮度變化,兩者其實都可以。
但是,Sobel 因為增加了中心權重,通常會比 Prewitt 對雜訊更不敏感。
因此實際應用中,大部分情況都會優先使用 Sobel。
這也是為什麼很多影像處理函式庫都有 Sobel,卻不一定提供 Prewitt。
OpenCV 並沒有提供專門的 Prewitt 函式。
不過因為我們之前介紹過 Convolution,所以只需要自己建立 Kernel。
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread("dog.jpeg", cv2.IMREAD_GRAYSCALE)
kernel_x = np.array([
[-1, 0, 1],
[-1, 0, 1],
[-1, 0, 1]
], dtype=np.float32)
kernel_y = np.array([
[-1, -1, -1],
[ 0, 0, 0],
[ 1, 1, 1]
], dtype=np.float32)
grad_x = cv2.filter2D(img, cv2.CV_32F, kernel_x)
grad_y = cv2.filter2D(img, cv2.CV_32F, kernel_y)
接著把兩個方向合成。
magnitude = cv2.magnitude(grad_x, grad_y)
plt.imshow(magnitude, cmap="gray")
plt.title("Prewitt")
plt.axis("off")
plt.show()
結果:

如果再和 Sobel 的結果放在一起比較,應該會發現兩張圖片非常接近。
| 項目 | Prewitt | Sobel |
|---|---|---|
| 中心權重 | 沒有 | 有 |
| 對雜訊的抵抗能力 | 普通 | 較佳 |
| 計算方式 | 簡單 | 稍微複雜 |
| 實際使用 | 較少 | 非常常見 |
因此,現在大多時候介紹 Gradient 時都會以 Sobel 作為主要例子。
Prewitt 的價值,不是在於它比 Sobel 更好。
而是在於它讓我們理解:
Gradient Kernel 並沒有唯一答案。
只要符合「比較不同方向亮度變化」的設計理念,就能設計出不同的 Kernel。
Sobel 和 Prewitt,只是其中兩個經典例子。
真正重要的,是知道這些 Kernel 都是在估計亮度變化。
今天介紹了 Prewitt Operator。
它和 Sobel 一樣,都利用 Convolution 計算圖片在不同方向上的 Gradient。
最大的差別在於 Prewitt 對所有鄰近 Pixel 給予相同權重。
而 Sobel 則提高了中心 Pixel 附近的影響力,因此通常對雜訊更有抵抗能力。
到了這裡,我們已經學會利用一階微分的概念來找亮度變化。
但如果不是觀察亮度「變得有多快」,而是觀察亮度「變化是否突然改變」呢?
下一篇,我們就來介紹另一種完全不同的思路:
Laplacian(拉普拉斯算子)。