上一篇介紹了 Sobel 和 Prewitt。
它們都是利用不同方向的 Kernel,估計圖片的 Gradient(亮度變化)。
也就是說,它們真正回答的是亮度改變得有多快?
那如果,今天不是想知道亮度變化有多大,而是想知道亮度的變化是不是突然改變了,要怎麼做?
這就要提到我們今天要討論的 Laplacian(拉普拉斯算子)。
假設有一排 Pixel:
10 20 30 40 50
每往右一格,亮度都增加 10,變化非常規律。
Sobel 看到的是:
10 10 10 10
也就是每一步的亮度變化都一樣。
再看另一個例子:
10 20 30 150 160
原本增加得很平穩,結果突然跳到 150。
這代表亮度的變化不再平滑,而是在某個位置突然變大。

Laplacian 關心的,就是這種:
變化本身發生改變的位置。
因此,它特別容易對圖片中的邊界產生反應。
Sobel 需要分成:
因為它要知道亮度往哪個方向改變。
Laplacian 則不同,它直接觀察周圍所有 Pixel。
因此,它本身沒有 X 和 Y 的區分。
這也是它和 Sobel 最大的不同之一。
最常見的 Laplacian Kernel 之一:
0 -1 0
-1 4 -1
0 -1 0
也有人使用:
-1 -1 -1
-1 8 -1
-1 -1 -1
兩個都是 Laplacian,只是考慮的鄰近 Pixel 範圍不同。
共同點都是中心 Pixel 會和周圍 Pixel 做比較。
如果差異很大,輸出就會變得很大。
假設圖片有一塊很大的灰色區域:
100 100 100
100 100 100
100 100 100
中心和周圍幾乎沒有差別。
因此 Laplacian 的輸出接近 0。
但是如果剛好在黑白交界:
20 20 20
20 200 200
20 200 200
中心 Pixel 和周圍差異突然變得很大,Laplacian 就會得到很大的回應。
因此,圖片中的邊緣會變得特別明顯。
OpenCV 已經提供 Laplacian:
import cv2
import matplotlib.pyplot as plt
img = cv2.imread("dog.jpeg", cv2.IMREAD_GRAYSCALE)
lap = cv2.Laplacian(img, cv2.CV_64F)
印出結果:
plt.figure(figsize=(10,4))
plt.subplot(1,2,1)
plt.imshow(img, cmap="gray")
plt.title("Original")
plt.axis("off")
plt.subplot(1,2,2)
plt.imshow(cv2.convertScaleAbs(lap), cmap="gray")
plt.title("Laplacian")
plt.axis("off")
plt.tight_layout()
plt.show()
結果如下:

可以發現,圖片中只有亮度變化劇烈的位置特別明顯。
其他區域大多接近黑色。
剛剛我們提到,Laplacian 對亮度變化非常敏感。
但雜訊本身也是一種劇烈變化。
因此,如果圖片中有很多雜訊,Laplacian 很容易把它們一起放大。
所以在實際應用時,通常會先使用 Gaussian Filter。
降低雜訊之後再進行 Laplacian。
這也是很多影像處理流程會先做平滑,再做邊緣偵測的原因。
| Sobel | Laplacian |
|---|---|
| 估算一階 Gradient | 估算二階變化 |
| 分 X、Y 方向 | 不分方向 |
| 對雜訊較不敏感 | 對雜訊較敏感 |
| 常用於估算邊緣方向 | 常用於強調邊緣位置 |
這兩個都能幫助我們找到圖片中的邊界。
只是觀察的角度不同。
從 Sobel、Prewitt 到今天的 Laplacian。
我們看到三種不同的方法來分析圖片中的亮度變化。
Sobel 和 Prewitt 透過不同方向的 Kernel,估計亮度變化的大小。
而 Laplacian 更進一步,它觀察的是 亮度變化本身是否突然改變。
但是,就算用了 Sobel 或 Laplacian。
最後得到的結果,通常還是會有很多不必要的邊緣。
有沒有一種方法可以把前面介紹的 Gaussian Filter、Gradient、以及邊緣篩選全部結合在一起?
答案是肯定的,就是我們明天要介紹的 Canny Edge Detector。
它也是目前最經典、最廣泛使用的邊緣偵測方法之一。