在介紹了 黑白圖片的Morphological Transformations 後,本文接下來會將相關概念延伸,簡單介紹灰階圖片的 Morphological Transformations。
灰階圖片的 Morphological Transformations:
首先,我們都知道灰階圖片是二維的矩陣,而其中每個元素為介於 0-255 間的整數,代表著該像素的亮度。
但灰階圖片的亮度值有256個數字,不像黑白可以用 0 和 1 表示,這樣當捲積用的 window 在滑動時,要怎麼產生"膨脹(Dilate)" 或 "侵蝕(Erode)" 的效果呢?
Idea💡:
讓我們先從侵蝕(Erode)進行一些啟發式的探討吧! 我們知道數字越靠近255,則亮度越亮,而越靠近 0 時,亮度越暗,所以如果我們取 kernel 中像素最小的值取代掉 Kernel 中心點的值,則該中心點的像素就會變暗,當對每一點都這樣做時,整體來說圖像某些(白色的)地方就會變暗,視覺上就會看起來像是白色(偏亮)的部分縮小了,達到被黑暗"侵蝕"的效果,如下圖所示:
(圖片來源:https://commons.wikimedia.org/wiki/File:Grayscale_Morphological_Erosion.gif)
反之,透過類似的概念,將 kernel 中像素最大的值取代掉 Kernel 中心點的值,則看起來就像亮度偏亮的地方膨脹(黑色的部分被白色侵蝕)了,如下圖所示:
(圖片來源:https://commons.wikimedia.org/wiki/File:Grayscale_Morphological_Dilation.gif)
以上大致就是灰階圖片的 Morphological Transformations 背後的直覺想法。
底下為簡單示範如何使用 OpenCV 套件進行 Morphological Transformations,其餘和Morphological Transformations相關的函數(Opening, Closing 等...)跟黑白圖片用的函數都是一樣的,所以就不在此贅述了。
程式碼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)
kernel = np.ones((10,10),np.uint8)
erosion = cv2.erode(dog_img_gray, kernel, iterations = 1)
dilation = cv2.dilate(dog_img_gray, kernel, iterations = 1)
gradient = cv2.morphologyEx(dog_img_gray, cv2.MORPH_GRADIENT, kernel)
pic_titles = ['origin','erosion', 'dilation', 'gradient']
morphological_images= [dog_img_gray, 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}")
執行結果:
bitwise_img_gray = cv2.bitwise_not(dog_img_gray) 將原先的圖像 bitwise 後進行 Morphological Transformation,則看的出來 erosion 對圖像進行侵蝕, dilation 對圖像進行膨脹,因此我們可以猜測灰階圖片和其 bitwise_not 後的圖像也有類似的對偶的行為。
抽象化:
若我們去觀察Wiki上延伸的抽象定義,可以發現 Erosion 是針對每一個 fixed point (x,y),將 f(x,y) 附近的值 f(a,b) 扣掉 b(a-x,b-y) 後[向量(a-x,b-y)落在 b 的 support B 內]去取 inf,而換成離散版本的話 inf 就會等同於 min,上述的 grayscale 就會是一個特例,下方為該抽象定義的示意圖,有興趣的讀者可以自行去參考 Wiki 上的抽象數學定義。
彩色圖片的 Morphological Transformations:
若我們將調用 OpenCV 進行 Morphological Transformations的來源圖片改成彩色圖片時,可以發現一樣有產生結果,可是其讀取的彩色圖片的像素是 bgr 向量,照理來說不像 real-valued 函數那樣可以比較大小,但這樣 OpenCV 套件是怎麼去輸出結果的呢? 我們可以很直覺的猜想是拆分成三個通道通道的圖片,對每個通道的圖片進行類似灰階圖片的 Morphological Transformations 後,最後再將其疊加,當作結果,底下為相關的程式實驗。
程式碼2(Python):
import cv2
import numpy as np
# 圖片位址
dog_pic = '../images/dog.png'
# 讀取圖片並取得各自分離成 r g b 三通道的圖片
dog_img = cv2.imread(dog_pic)
dog_img_blue_channel = (dog_img.copy())
dog_img_blue_channel[:,:,1:3] = 0
dog_img_green_channel = (dog_img.copy())
dog_img_green_channel[:,:,0:1] = 0
dog_img_green_channel[:,:,2:3] = 0
dog_img_red_channel = (dog_img.copy())
dog_img_red_channel[:,:,0:2] = 0
kernel = np.ones((3,3),np.uint8)
# 各自對圖片做 erode
blue_erosion = cv2.erode(dog_img_blue_channel, kernel,iterations = 1)
green_erosion = cv2.erode(dog_img_green_channel, kernel,iterations = 1)
red_erosion = cv2.erode(dog_img_red_channel, kernel,iterations = 1)
origin_erosion = cv2.erode(dog_img, kernel,iterations = 1)
# 將通道分離的圖片做加總
bg_erosion =cv2.add(blue_erosion, green_erosion)
bgr_erosion = cv2.add(bg_erosion, red_erosion)
diff = cv2.absdiff(origin_erosion, bgr_erosion)
# diff 看起來為全黑圖片
# 而diff array > 0 的元素加總為 0 代表每一項都是 0,
# 可以相信說 opencv 套件對彩色圖片做 erode 相當於對分離成 r, g , b 三通道的圖片單獨個別作 erode 後相加
cv2.imshow('diff', diff)
print('np.sum(diff):', np.sum(diff > 0))
cv2.imshow('before vs after', np.hstack([dog_img, origin_erosion, blue_erosion, green_erosion, red_erosion]))
cv2.imshow('bgr_erosion', bgr_erosion)
cv2.waitKey()
cv2.destroyAllWindows()
執行結果:
實際上,我們可以從官網文件上看到
In case of multi-channel images, each channel is processed independently.
所以其實跟我們的直覺猜想不約而同。
但實際上彩色的 Morphological Transformations 除了像用上述的 Component-wise Morphology 手法外,網路上可以看到還有 Luminance-based 或 Vector-Based Morphology 的相關手法,本文就不額外多作調研了。
以上就是筆者對灰階圖片的 Morphological Transformations 相關介紹,謝謝~