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