提示:
轉載請詳細注明原作者及出處,謝謝!
本文介紹使用OpenCV-Python進行形態學處理
本文不介紹形態學處理的基本概念,所以讀者需要預先對其有一定的了解。
定義結構元素
形態學處理的核心就是定義結構元素,在OpenCV-Python中,可以使用其自帶的getStructuringElement函數,也可以直接使用NumPy的ndarray來定義一個結構元素。首先來看用getStructuringElement函數定義一個結構元素:
element = cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))
這就定義了一個5×5的十字形結構元素,如下:
也可以用NumPy來定義結構元素,如下:
NpKernel = np.uint8(np.zeros((5,5))) for i in range(5): NpKernel[1, i] = 1 NpKernel[i, 1] = 1
這兩者方式定義的結構元素完全一樣:
[[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]]
這里可以看出,用OpenCV-Python內置的常量定義橢圓(MORPH_ELLIPSE)和十字形結構(MORPH_CROSS)元素要簡單一些,如果定義矩形(MORPH_RECT)和自定義結構元素,則兩者差不多。
本篇文章將用參考資料1中的相關章節的圖片做測試:
腐蝕和膨脹
下面先以腐蝕圖像為例子介紹如何使用結構元素:
#coding=utf-8 import cv2 import numpy as np img = cv2.imread('D:/binary.bmp',0) #OpenCV定義的結構元素 kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3)) #腐蝕圖像 eroded = cv2.erode(img,kernel) #顯示腐蝕后的圖像 cv2.imshow("Eroded Image",eroded); #膨脹圖像 dilated = cv2.dilate(img,kernel) #顯示膨脹后的圖像 cv2.imshow("Dilated Image",dilated); #原圖像 cv2.imshow("Origin", img) #NumPy定義的結構元素 NpKernel = np.uint8(np.ones((3,3))) Nperoded = cv2.erode(img,NpKernel) #顯示腐蝕后的圖像 cv2.imshow("Eroded by NumPy kernel",Nperoded); cv2.waitKey(0) cv2.destroyAllWindows()
如上所示,腐蝕和膨脹的處理很簡單,只需設置好結構元素,然后分別調用cv2.erode(...)和cv2.dilate(...)函數即可,其中第一個參數是需要處理的圖像,第二個是結構元素。返回處理好的圖像。
結果如下:
開運算和閉運算
了解形態學基本處理的同學都知道,開運算和閉運算就是將腐蝕和膨脹按照一定的次序進行處理。但這兩者並不是可逆的,即先開后閉並不能得到原先的圖像。代碼示例如下:
#coding=utf-8 import cv2 import numpy as np img = cv2.imread('D:/binary.bmp',0) #定義結構元素 kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(5, 5)) #閉運算 closed = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel) #顯示腐蝕后的圖像 cv2.imshow("Close",closed); #開運算 opened = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel) #顯示腐蝕后的圖像 cv2.imshow("Open", opened); cv2.waitKey(0) cv2.destroyAllWindows()
閉運算用來連接被誤分為許多小塊的對象,而開運算用於移除由圖像噪音形成的斑點。因此,某些情況下可以連續運用這兩種運算。如對一副二值圖連續使用閉運算和開運算,將獲得圖像中的主要對象。同樣,如果想消除圖像中的噪聲(即圖像中的“小點”),也可以對圖像先用開運算后用閉運算,不過這樣也會消除一些破碎的對象。
對原始圖像進行開運算和閉運算的結果如下:
用形態學運算檢測邊和角點
這里通過一個較復雜的例子介紹如何用形態學算子檢測圖像中的邊緣和拐角(這里只是作為介紹形態學處理例子,實際使用時請用Canny或Harris等算法)。
檢測邊緣
形態學檢測邊緣的原理很簡單,在膨脹時,圖像中的物體會想周圍“擴張”;腐蝕時,圖像中的物體會“收縮”。比較這兩幅圖像,由於其變化的區域只發生在邊緣。所以這時將兩幅圖像相減,得到的就是圖像中物體的邊緣。這里用的依然是參考資料1中相關章節的圖片:
代碼如下:
#coding=utf-8 import cv2 import numpy image = cv2.imread("D:/building.jpg",0); #構造一個3×3的結構元素 element = cv2.getStructuringElement(cv2.MORPH_RECT,(3, 3)) dilate = cv2.dilate(image, element) erode = cv2.erode(image, element) #將兩幅圖像相減獲得邊,第一個參數是膨脹后的圖像,第二個參數是腐蝕后的圖像 result = cv2.absdiff(dilate,erode); #上面得到的結果是灰度圖,將其二值化以便更清楚的觀察結果 retval, result = cv2.threshold(result, 40, 255, cv2.THRESH_BINARY); #反色,即對二值圖每個像素取反 result = cv2.bitwise_not(result); #顯示圖像 cv2.imshow("result",result); cv2.waitKey(0) cv2.destroyAllWindows()
處理結果如下:
檢測拐角
與邊緣檢測不同,拐角的檢測的過程稍稍有些復雜。但原理相同,所不同的是先用十字形的結構元素膨脹像素,這種情況下只會在邊緣處“擴張”,角點不發生變化。接着用菱形的結構元素腐蝕原圖像,導致只有在拐角處才會“收縮”,而直線邊緣都未發生變化。
第二步是用X形膨脹原圖像,角點膨脹的比邊要多。這樣第二次用方塊腐蝕時,角點恢復原狀,而邊要腐蝕的更多。所以當兩幅圖像相減時,只保留了拐角處。示意圖如下(示意圖來自參考資料1):
代碼如下:
#coding=utf-8 import cv2 image = cv2.imread("D:/building.jpg", 0) origin = cv2.imread("D:/building.jpg") #構造5×5的結構元素,分別為十字形、菱形、方形和X型 cross = cv2.getStructuringElement(cv2.MORPH_CROSS,(5, 5)) #菱形結構元素的定義稍麻煩一些 diamond = cv2.getStructuringElement(cv2.MORPH_RECT,(5, 5)) diamond[0, 0] = 0 diamond[0, 1] = 0 diamond[1, 0] = 0 diamond[4, 4] = 0 diamond[4, 3] = 0 diamond[3, 4] = 0 diamond[4, 0] = 0 diamond[4, 1] = 0 diamond[3, 0] = 0 diamond[0, 3] = 0 diamond[0, 4] = 0 diamond[1, 4] = 0 square = cv2.getStructuringElement(cv2.MORPH_RECT,(5, 5)) x = cv2.getStructuringElement(cv2.MORPH_CROSS,(5, 5)) #使用cross膨脹圖像 result1 = cv2.dilate(image,cross) #使用菱形腐蝕圖像 result1 = cv2.erode(result1, diamond) #使用X膨脹原圖像 result2 = cv2.dilate(image, x) #使用方形腐蝕圖像 result2 = cv2.erode(result2,square) #result = result1.copy() #將兩幅閉運算的圖像相減獲得角 result = cv2.absdiff(result2, result1) #使用閾值獲得二值圖 retval, result = cv2.threshold(result, 40, 255, cv2.THRESH_BINARY) #在原圖上用半徑為5的圓圈將點標出。 for j in range(result.size): y = j / result.shape[0] x = j % result.shape[0] if result[x, y] == 255: cv2.circle(image, (y, x), 5, (255,0,0)) cv2.imshow("Result", image) cv2.waitKey(0) cv2.destroyAllWindows()
注意,由於封裝的緣故,OpenCV中函數參數中使用的坐標系和NumPy的ndarray的坐標系是不同的,在50行可以看出來。抽空我向OpenCV郵件列表提一下,看我的理解是不是正確的。
大家可以驗證一下,比如在代碼中插入這兩行代碼,就能知道結果了:
cv2.circle(image, (5, 10), 5, (255,0,0)) image[5, 10] = 0
通過上面的代碼就能檢測到圖像中的拐角並標出來,效果圖如下:
當然,這只是個形態學處理示例,檢測結果並不好。
未完待續...
在將來的某一篇文章中將做個總結,介紹下OpenCV中常用的函數,如threshold、bitwise_xxx,以及繪制函數等。
參考資料:
1、《Opencv2 Computer Vision Application Programming Cookbook》
2、《OpenCV References Manule》
如果覺得本文寫的還可以的話,請輕點“頂”,方便讀者、以及您的支持是我寫下去的最大的兩個動力。