Laplacian 運算(二)


在〈Laplacian 運算(一)〉中,透過 x 軸方向的二階導數,可以對圖像進行 x 軸方向的邊緣偵測,是不是也可以對 y 軸方向求二階導數,然後兩個二階導數相加,來得到兩個方向的邊緣偵測呢?來試試看吧!

import numpy as np
import cv2

img = cv2.imread('caterpillar.jpg', cv2.IMREAD_GRAYSCALE)

x_derivative2 = cv2.filter2D(img, cv2.CV_16S, np.array([
    [0, 0, 0],
    [1, -2, 1],
    [0, 0, 0]
]))

y_derivative2 = cv2.filter2D(img, cv2.CV_16S, np.array([
    [0, 1, 0],
    [0, -2, 0],
    [0, 1, 0]
]))

# 放大兩倍後取絕對值並轉為 uint8
edge = cv2.convertScaleAbs(x_derivative2 + y_derivative2, alpha = 2)

cv2.imshow('caterpillar', img)
cv2.imshow('edge', edge)

cv2.waitKey(0)
cv2.destroyAllWindows()

cv2.convertScaleAbs 可以指定 alpha,這會用來與第一個引數值相乘,得到縮放的效果,這也是函式名稱上為什麼有 Scale 字樣的原因,這邊放大兩倍,可以讓邊緣顯示時更亮一些,可以得到以下的結果:

Laplacian 運算(二)

實際上,可以將用於 cv2.filter2D 的兩個核合併,得到相同的效果:

import numpy as np
import cv2

img = cv2.imread('caterpillar.jpg', cv2.IMREAD_GRAYSCALE)

derivative2 = cv2.filter2D(img, cv2.CV_16S, np.array([
    [0, 1, 0],
    [1, -4, 1],
    [0, 1, 0]
]))

edge = cv2.convertScaleAbs(derivative2, alpha = 2)

cv2.imshow('caterpillar', img)
cv2.imshow('edge', edge)

cv2.waitKey(0)
cv2.destroyAllWindows()

更進一步地,可以直接使用 cv2.Laplacian

import numpy as np
import cv2

img = cv2.imread('caterpillar.jpg', cv2.IMREAD_GRAYSCALE)

laplacian = cv2.Laplacian(img, cv2.CV_16S)

edge = cv2.convertScaleAbs(laplacian, alpha = 2)

cv2.imshow('caterpillar', img)
cv2.imshow('edge', edge)

cv2.waitKey(0)
cv2.destroyAllWindows()

這些範例的結果都是相同的,只是為什麼它叫 Laplacian 呢?方才的範例中,x_derivative2 相當於每次選擇圖片中的一列(row)來求對應的一組二階導數,也就是說,如果圖片可以使用 f(x, y) 描述,這相當於求 x 的二階偏導數,也就是將 y 視為常數,求 x 方向上的變化。

而基於〈Laplacian 運算(一)〉中的推導,可以知道 f(x, y) 對 x 的二階偏微分會是:

Laplacian 運算(二)

相對地,y_derivative2 相當於每次選擇圖片中的一行(column),最後就相當於求 f(x, y) 對 y 的二階偏微分:

Laplacian 運算(二)

將這兩個公式相加,f(x, y) 的係數變成 -4,而上、下、左、右四個位置的係數都是 1,也就是為什麼方才的範例,可以指定 cv2.filter2D 的核為以下的原因:

[
    [0, 1, 0],
    [1, -4, 1],
    [0, 1, 0]
]

而這符合了〈Laplace 運算〉的定義:

Laplacian 運算(二)

Laplacian 運算是認識圖像邊緣偵測一個很好的入門,知道其原理後,日後接觸其他如 Sobel 運算、Scharr 運算等邊緣偵測方法時,想進一步認識原理或應用,會有很大的幫助。