本文主要內容來自於 OpenCV-Python 教程 的 OpenCV 中的圖像處理 部分,這部分的全部主要內容如下:
-
學習在不同色彩空間之間改變圖像。另外學習跟蹤視頻中的彩色對象。
-
學習對圖像應用不同的幾何變換,比如旋轉、平移等。
-
學習使用全局閾值、自適應閾值、Otsu 的二值化等將圖像轉換爲二值圖像。
-
學習模糊圖像,使用自定義內核過濾圖像等。
-
瞭解形態學變換,如侵蝕、膨脹、開放、閉合等。
-
學習尋找圖像漸變、邊緣等。
-
學習通過 Canny 邊緣檢測尋找邊緣。
-
學習關於圖像金字塔的內容,以及如何使用它們進行圖像混合。
-
所有關於 OpenCV 中的輪廓的內容。
-
所有關於 OpenCV 中的直方圖的內容。
-
在 OpenCV 中遇到不同的圖像變換,如傅里葉變換、餘弦變換等。
-
學習使用模板匹配在圖像中搜索對象。
-
學習在一幅圖像中探測線。
-
學習在一幅圖像中探測圓。
-
學習使用分水嶺分割算法分割圖像。
-
學習使用 GrabCut 算法提取前景
目標
在本章中:
- 我們將學習不同的形態學操作,如腐蝕、膨脹、開、閉等。
- 我們將看到不同的函數,例如:cv.erode(),cv.dilate(),cv.morphologyEx() 等等。
理論
形態變換是基於圖像形狀的一些簡單操作。它通常在二值圖像上操作。它需要兩個輸入,一個是我們的原始圖像,第二個稱爲 結構元素 或內核,其決定了操作的性質。 兩個基本的形態學操作是侵蝕和膨脹。然後它的變體形式,如 Opening,Closing,Gradient 等也開始發揮作用。我們將在下圖的幫助下一一看看它們:
1. 侵蝕
蝕的基本思想就像土壤侵蝕一樣,它侵蝕了前景物體的邊界(總是儘量保持前景爲白色)。那麼它有什麼作用呢?內核在圖像中滑動(如在 2D 卷積中)。只有當內核下的所有像素都爲 1 時,原始圖像中的一個像素(1 或 0)纔會被認爲是 1,否則它會被侵蝕(變爲 0)。
所以發生的情況是,邊界附近的所有像素都將根據內核的大小被丟棄。因此,前景物體的厚度或大小會減小,或者只是圖像中的白色區域減小。它對於去除小的白噪聲(正如我們在色彩空間章節中所見)、分離兩個連接的對象等很有用。
在這裏,作爲一個例子,我們使用一個 5x5 的內核,其中全是 1。讓我們看看它是如何工作的:
import cv2 as cv
import numpy as np
def erosion():
img = cv.imread("/home/zhangsan/j.png", 0)
kernel = np.ones((5, 5), np.uint8)
erosion = cv.erode(img, kezhangsanrnel, iterations=1)
edge = np.full((img.shape[0], 3, 1), 255, np.uint8);
images = [img, edge, erosion]
dest = cv.hconcat(images)
cv.imshow("Image", dest)
cv.waitKey(-1)
if __name__ == "__main__":
erosion()
結果如下:
2. 膨脹
它與侵蝕完全相反。在這裏,如果內核下的元素中至少有一個值爲 '1',則像素元素值爲 '1'。因此它增加圖像中的白色區域,或者增加前景對象的大小。通常,在去除噪聲等情況下,侵蝕之後是膨脹。因爲,侵蝕消除白噪聲,但它也縮小了我們的對象。所以我們擴張它。由於噪音消失了,它們不會回來,但我們的對象面積增加了。它也可用於連接對象的損壞部分。
dilation = cv.dilate(img, kernel, iterations=1)
結果如下:
3. 開
開只是 侵蝕後膨脹 的另一個名稱。正如我們上面解釋的那樣,它在消除噪音方面很有用。這裏我們使用函數 cv.morphologyEx()。
def opening():
img = cv.imread("/home/hanpfei/j.png", 0)
for i in range(50):
row = random.randint(0, img.shape[0] - 1)
col = random.randint(0, img.shape[1] - 1)
img[row][col] = 255
kernel = np.ones((5, 5), np.uint8)
opening = cv.morphologyEx(img, cv.MORPH_OPEN, kernel)
edge = np.full((img.shape[0], 3, 1), 255, np.uint8);
images = [img, edge, opening]
dest = cv.hconcat(images)
cv.imshow("Image", dest)
cv.waitKey(-1)
要處理的圖像中如果包含一些白色的噪聲像素點,效果會比較明顯。這裏先給輸入圖像增加了一些白色的噪聲像素點。結果如下:
4. 閉
閉是開的逆操作,膨脹後侵蝕。它對於閉合前景對象內的小孔,或對象上的小黑點很有用。
def closing():
img = cv.imread("/home/hanpfei/j.png", 0)
for i in range(5000):
row = random.randint(0, img.shape[0] - 1)
col = random.randint(0, img.shape[1] - 1)
img[row][col] = 0
kernel = np.ones((5, 5), np.uint8)
closing = cv.morphologyEx(img, cv.MORPH_CLOSE, kernel)
edge = np.full((img.shape[0], 3, 1), 255, np.uint8);
images = [img, edge, closing]
dest = cv.hconcat(images)
cv.imshow("Image", dest)
cv.waitKey(-1)
if __name__ == "__main__":
closing()
這裏同樣先在輸入圖像上製造一些噪聲點,不過這次是黑色噪聲像素點。由於大多數黑色噪聲點會落在黑色的背景區域內而看不出效果,所以這裏製造更多的噪聲點。最終的結果如下:
5. 形態梯度
它是一幅圖像的膨脹和侵蝕之間的差值。
結果將看起來像是對象的輪廓。
gradient = cv.morphologyEx(img, cv.MORPH_GRADIENT, kernel)
結果如下:
6. 禮帽
它是輸入圖像和圖像的開的插值。下面的示例是針對 9x9 內核完成的。
def top_hat():
img = cv.imread("/home/hanpfei/j.png", 0)
kernel = np.ones((9, 9), np.uint8)
tophat = cv.morphologyEx(img, cv.MORPH_TOPHAT, kernel)
edge = np.full((img.shape[0], 3, 1), 255, np.uint8);
images = [img, edge, tophat]
dest = cv.hconcat(images)
cv.imshow("Image", dest)
cv.waitKey(-1)
結果如下:
7. 黑帽
它是輸入圖像和圖像的閉的插值。下面的示例是針對 9x9 內核完成的。
tophat = cv.morphologyEx(img, cv.MORPH_BLACKHAT, kernel)
結果如下:
結構元素
我們在 Numpy 的幫助下手動創建前面的示例中的結構元素。它是矩形的。但在某些情況下,你可能需要 橢圓形/圓形 的內核。所以爲了這個目的,OpenCV 有一個函數,cv.getStructuringElement()。我們只需傳入內核的形狀和大小,就可以得到想要的內核。
# Rectangular Kernel
>>> cv.getStructuringElement(cv.MORPH_RECT,(5,5))
array([[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1]], dtype=uint8)
# Elliptical Kernel
>>> cv.getStructuringElement(cv.MORPH_ELLIPSE,(5,5))
array([[0, 0, 1, 0, 0],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[0, 0, 1, 0, 0]], dtype=uint8)
# Cross-shaped Kernel
>>> cv.getStructuringElement(cv.MORPH_CROSS,(5,5))
array([[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[1, 1, 1, 1, 1],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0]], dtype=uint8)
其它資源
- HIPR2 的 Morphological Operations
練習
參考文檔
Done.