本文將簡單介紹什麼是 Morphological Transformations,並示範如何用 OpenCV 對黑白圖片進行相關的運算。
Morphological Transformations 基本概念:
首先讓我們講講什麼是 Morphology 吧! Morphology 翻成中文就是 "形態學(Mathematical morphology)",顧名思義,也就是一門專注在圖形幾何形狀上的學問,所以 Morphological Transformations 不難看出就是其相關的運算,而其相關應用有"去除影像雜訊"、"物體輪廓提取"、"填補物品內微小孔洞"和"縮小前景物品的邊界"等...。
常見的相關運算有Erosion、Dilation、Gradient、Opening、Closing、Top-Hat 等...,而其中最重要的就是 Erosion 和 Dilation (因為上述的其他運算皆由這兩個運算衍生而來),接下來將簡單介紹 Erosion 和 Dilation 的相關概念。
Erosion,顧名思義為侵蝕,而要如何達到讓圖像邊界縮小,像受到某種東西"侵蝕"的效果呢?
概念上是使用 Convolution 去完成,當滑動的 window 區域內有黑色的像素時,將其中心點改成黑色,否則維持原樣,也就是說 window 內所有的像素必須全都是白色的,捲積後產生用來取代中心點的像素值才會是白色的。

同樣類似的概念,當滑動的 window 區域內有白色的像素時,將其中心點改成白色,反之則一樣維持原本的黑色,即可產生膨脹的效果。

而將稍微膨脹的圖形(dilation)拿去扣掉稍微縮小的圖形(erosion),則可以得到輪廓,這樣的運算就是 "gradient"。

底下為使用 OpenCV 套件對黑白圖片進行 erosion、dilation 和 gradient 的簡單範例。
程式碼1(Python):
import cv2
import numpy as np
import matplotlib.pyplot as plt
dog_pic = '../images/dog.png' # 圖片位址
try:
dog_img = cv2.imread(dog_pic)
dog_img_gray = cv2.cvtColor(dog_img, cv2.COLOR_BGR2GRAY)
# 取得 mask
ret, mask = cv2.threshold(dog_img_gray, 220, 255, cv2.THRESH_BINARY)
mask_inv = cv2.bitwise_not(mask)
kernel = np.ones((5,5),np.uint8)
erosion = cv2.erode(mask_inv, kernel, iterations = 1)
dilation = cv2.dilate(mask_inv, kernel, iterations = 1)
gradient = cv2.morphologyEx(mask_inv, cv2.MORPH_GRADIENT, kernel)
pic_titles = ['origin','erosion', 'dilation', 'gradient']
morphological_images= [mask_inv, erosion, dilation, gradient]
for i in range(len(pic_titles)):
plt.subplot(1, len(pic_titles) ,i+1), plt.imshow(morphological_images[i], cmap='gray')
plt.title(pic_titles[i])
plt.xticks([]),plt.yticks([]) # 拿掉 X座標軸 和 y座標軸 刻度文字
plt.show()
except Exception as ex:
print(f"An unexpected error occurred: {ex}")
執行結果:
Observation:





Opening, Closing, Top-hat, Black-hat:
在理解了 Erode 和 Dilation 這兩個基本且重要的原理後,接下來讓我們看看由其衍伸出來的 "Opening" 和 "Closing" 吧~~~
Opening 主要可以用於消除雜訊,其運算方式為先對影像進行 Erode(侵蝕) 後再進行 Dilate(膨脹), Erdoe 可以將外部的雜訊消除,但因為邊界會跟著被侵蝕,所以再用 Dilate 稍微將其補回來。

而 Closing 則是可以用來填補物品的內部小空洞,其運算方式為先對影像進行 Dilate(膨脹) 後再進行 Erode(侵蝕),和上述的 Opening 類似概念,先透過 Dilate 填補內部小空洞,接著因為邊界稍微膨脹,所以在用 Erode 侵蝕將其稍微縮小。

至於 Top-hat 則是將拿原來的圖像去扣掉 "Opening 運算後的圖像" 的結果,Black-hat 則是將原來的圖像去扣掉 "Closing 運算後的圖像" 的結果。
底下為使用 OpenCV 套件對黑白圖片進行 Opening 和 Closing 的簡單範例。
程式碼2(Python):
import cv2
import numpy as np
import random as rd
import matplotlib.pyplot as plt
dog_pic = '../images/dog.png' # 圖片位址
try:
dog_img = cv2.imread(dog_pic)
dog_img_gray = cv2.cvtColor(dog_img, cv2.COLOR_BGR2GRAY)
# 取得 mask
ret, mask = cv2.threshold(dog_img_gray, 220, 255, cv2.THRESH_BINARY)
mask_inv = cv2.bitwise_not(mask)
height, width = mask_inv.shape
# 隨機選取格子點加入雜訊
grid_points = [(a, b) for a in range(height) for b in range(width)]
num_samples = 1000
random_points = rd.sample(grid_points, num_samples)
for pt in random_points:
if(mask_inv[pt] == 255):
mask_inv[pt] = 0
else:
mask_inv[pt] = 255
# 使用 opening 和 closing 消除部分雜訊
kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(mask_inv, cv2.MORPH_OPEN, kernel)
closing = cv2.morphologyEx(mask_inv, cv2.MORPH_CLOSE, kernel)
top_hat = cv2.morphologyEx(mask_inv, cv2.MORPH_TOPHAT, kernel)
black_hat = cv2.morphologyEx(mask_inv, cv2.MORPH_BLACKHAT, kernel)
pic_titles = ['pic with noise','opening', 'closing', 'top_hat', 'black_hat']
morphological_images= [mask_inv, opening, closing, top_hat, black_hat]
num_of_pics = len(pic_titles)
for i in range(num_of_pics):
plt.subplot(1, num_of_pics ,i+1), plt.imshow(morphological_images[i], cmap='gray')
plt.title(pic_titles[i])
plt.xticks([]),plt.yticks([]) # 拿掉 X座標軸 和 y座標軸 刻度文字
plt.show()
except Exception as ex:
print(f"An unexpected error occurred: {ex}")
執行結果:
在這張隨機添加雜訊的圖片上,我們可以觀察到 Opening 和 closing 是一體兩面的,使用 Opening 時,內部的空洞跟著變多,而使用 Closing 時,內部的空洞雖然被填補了,但是外部的雜訊卻也被加強了。
Generalization:

上述是筆者得一些簡單推論,有興趣的讀者可以自己去參考維基百科的Binary_morphology,了解一下這些 Openration 背後的 Peoperties。
以上就是對黑白圖片的 Morphological Transformations 相關介紹,謝謝~