在先前的 OpenCV 系列文件中,〈Laplacian 轉換(一)〉與〈Laplacian 轉換(二)〉,透過 Laplacian 運算等來取得圖像邊緣,如果現在不依賴 OpenCV,該怎麼進行呢?
雖然 Scipy 不是專用於影像處理,然而有時確實有分析影像的需求,因此 Scipy 提供有 ndimage
模組,可以應付基本的影像分析。
首先來解決讀取影像檔的問題,如〈簡介 Scipy〉談到的,ndimage
已經不再提供讀取影像的函式,官方是建議安裝與使用 imageio:
import imageio
img = imageio.imread('caterpillar.jpg')
讀入檔案後,傳回的是 NumPy 陣列,在第三個維度部份,是像素的 RGB 資料,若要轉為灰階,可以使用常見的轉灰階公式 R * 0.299 + G * 0.587 + B * 0.114
(轉灰階有多種不同的公式):
gray = np.dot(img[...,:3], [0.299, 0.587, 0.114])
若想要去除雜訊,ndimage
可以使用 gaussian_filter
、median_filter
等濾波,這邊使用中值濾波 median_filter
:
filtered = ndimage.median_filter(gray, size = (3, 3))
如果要進行卷積(convolution)運算,可以使用 convolve
函式並指定核,不過對於 Laplacian 運算直接提供有 laplace
函式:
laplace = ndimage.laplace(filtered)
以下是完整的程式:
import imageio
from scipy import ndimage
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
img = imageio.imread('caterpillar.jpg')
# 轉灰階
gray = np.dot(img[...,:3], [0.299, 0.587, 0.114])
# 中值濾波
filtered = ndimage.median_filter(gray, size = (3, 3))
# Laplacian 運算
laplace = ndimage.laplace(filtered)
# 取絕對值轉 uint8
edge = np.abs(laplace).astype('uint8')
plt.imshow(edge, cmap = cm.gray)
plt.show()
執行結果如下:
除了 laplace
函式可以使用之外,ndimage
還提供有 sobel
函式(當然,OpenCV 也有 Sobel 的實作),可以用來偵測邊緣,Sobel 運算的原理也是基於二階偏導數,在 x 方向與 y 方向各自求偏導數的差(懶得用方程式編輯器畫了,直接純文字寫公式XD):
Gx = (f(x + 1,y - 1) + 2 * f(x + 1, y) + f(x + 1, y + 1)) -
(f(x - 1,y - 1) + 2 * f(x - 1, y) + f(x - 1, y + 1))
Gy = (f(x - 1, y - 1) + 2 * f(x, y - 1) + f(x + 1,y - 1)) -
(f(x - 1, y + 1) + 2 * f(x, y + 1) + f(x + 1, y + 1))
G = sqrt(Gx * Gx, Gy * Gy)
Gx
、Gy
整理一下,以矩陣表示的話就是:
ndimage
的 sobel
函式可以透過 axis
來指定處理哪個軸的方向,預設是 -1,也就是考量兩個方向,指定軸 0 時,就圖像而言是指垂直方向(也就是 y 方向),指定軸 1 時是指水平方向(也就是 x 方向),底下的程式可以顯示三個處理的不同結果:
import imageio
from scipy import ndimage
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
img = imageio.imread('caterpillar.jpg')
# 轉灰階
gray = np.dot(img[...,:3], [0.299, 0.587, 0.114])
# 中值濾波
filtered = ndimage.median_filter(gray, size = (3, 3))
# 兩個方向
s = ndimage.sobel(filtered)
plt.subplot(1, 3, 1)
plt.imshow(np.abs(s).astype('uint8') , cmap = cm.gray)
# 水平方向
sx = ndimage.sobel(filtered, axis = 1)
plt.subplot(1, 3, 2)
plt.imshow(np.abs(sx).astype('uint8') , cmap = cm.gray)
# 垂直方向
sy = ndimage.sobel(filtered, axis = 0)
plt.subplot(1, 3, 3)
plt.imshow(np.abs(sy).astype('uint8') , cmap = cm.gray)
plt.show()
執行結果如下:
其他更多有關 ndimage
的使用,可以參考〈Multidimensional image processing (scipy.ndimage)〉。