OpenCV学习——轮廓检测
前言
輪廓檢測是傳統(tǒng)視覺中非常常用的功能,這里簡單記錄一下opencv中的輪廓檢測算法使用方法,至于理論,后續(xù)有機(jī)會(huì)再去細(xì)品。
國際慣例:
OpenCV官方的輪廓檢測教程python版
OpenCV中的二值化方法教程
OpenCV輪廓層級官方文檔
維基百科:圖像矩(Image Moment)
調(diào)用流程和方法
OpenCV里面通常要求是針對二值圖像進(jìn)行二值化,所以輪廓檢測包含如下步驟:
- 載入圖像
- 灰度化
- 二值化
- 輪廓檢測
代碼實(shí)現(xiàn)如下:
img =cv2.imread("blackBG.jpg") # grayscale # https://docs.opencv.org/4.5.0/d7/d4d/tutorial_py_thresholding.html gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) ret,bin_img = cv2.threshold(gray_img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)二值化
注意二值化方法,這里使用的是threshold函數(shù),它的第三個(gè)參數(shù)代表的意義可以查詢此處的官方文檔,這里將方法截圖貼出來
其實(shí)除了threshold還有一個(gè)adaptiveThreshold函數(shù)可以做二值化,調(diào)用方法:
#dst=cv.adaptiveThreshold(src,maxValue,adaptiveMethod,thresholdType,blockSize,C[, dst]) bin_img1 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_MEAN_C,\cv.THRESH_BINARY,11,2) bin_img2 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,\cv.THRESH_BINARY,11,2)從第三個(gè)參數(shù)可以發(fā)現(xiàn)也有兩個(gè)二值化方法:
- ADAPTIVE_THRESH_MEAN_C:閾值是每個(gè)像素鄰域區(qū)域的均值減去常量C
- ADAPTIVE_THRESH_GAUSSIAN_C::閾值是每個(gè)像素相鄰域區(qū)域的高斯加權(quán)和減去常量C
輪廓檢測
python的調(diào)用方法如下:
contours, hierarchy =cv.findContours(image,mode,method[,contours[, hierarchy[, offset]]])返回的參數(shù)
- contours:檢測到的輪廓,每個(gè)輪廓是由一些點(diǎn)構(gòu)成的向量組成
- hierarchy:記錄輪廓之間的關(guān)系,四個(gè)維度分別代表:同級后一個(gè)輪廓的序號、同級上一個(gè)輪廓的序號、第一個(gè)孩子序號,父親序號
第二個(gè)數(shù)參數(shù)mode是檢測輪廓的層級關(guān)系排列規(guī)則:
- RETR_EXTERNAL:僅僅檢測外圈輪廓
- RETR_LIST:檢測所有輪廓,但是沒有層級關(guān)系
- RETR_CCOMP:僅僅兩層包含關(guān)系,即只有外層和內(nèi)層,假設(shè)有夾層,那么夾層也算外層,只要某個(gè)輪廓還包含有輪廓,都算外部輪廓
- RETR_TREE:檢測所有的輪廓,并建議非常完整的層級關(guān)系
- RETR_FLOODFILL:無描述
第三個(gè)參數(shù)method是輪廓點(diǎn)的存儲(chǔ)方式:
- CHAIN_APPROX_NONE:相鄰的輪廓點(diǎn)坐標(biāo)只相差一個(gè)像素,所以是連續(xù)輪廓點(diǎn)
- CHAIN_APPROX_SIMPLE:橫、豎、對角線段只保存斷點(diǎn)數(shù)據(jù),比如矩形就只保存四個(gè)頂點(diǎn)。
- 還有兩種沒做過多敘述:CHAIN_APPROX_TC89_L1和CHAIN_APPROX_TC89_KCOS是Teh-Chin chain近似算法里面采取的兩種表示
畫圖函數(shù)
就一個(gè)函數(shù)drawContours,調(diào)用方法如下:
image=cv.drawContours(image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]] )輸入?yún)?shù):
- contours:是list類型的數(shù)組,里面存儲(chǔ)了很多array數(shù)組去代表各個(gè)輪廓
- contourIdx:從上面的輪廓list中取出哪一個(gè)畫出來,-1代表全部
- color:線條顏色
- thickness:線條粗細(xì),-1代表填充式畫輪廓,整個(gè)輪廓內(nèi)部被指定顏色填充
- lineType:線條類型,虛線、實(shí)線之類的
【注意】如果將原圖傳入畫圖函數(shù),這個(gè)原圖會(huì)被畫上輪廓,所以畫圖時(shí)候最好建立一個(gè)副本,在副本上畫圖。
輪廓檢測函數(shù)驗(yàn)證
主要驗(yàn)證檢測時(shí)的層級結(jié)構(gòu)和記錄關(guān)鍵點(diǎn)的方式,也就是第2和3個(gè)參數(shù)。
檢測黑色還是白色邊界
黑色背景圖,以下圖為例
先檢測所有的輪廓并且畫出來
img =cv2.imread("blackBG.jpg") gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) ret,bin_img = cv2.threshold(gray_img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU) contours, hierarchy = cv2.findContours(bin_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)cv2.drawContours(img,contours,-1,(0,255,0),5) plt.imshow(img[...,::-1]) plt.axis('off')白色背景圖以下圖左為例,同時(shí)以同樣的代碼盡心輪廓檢測,輪廓圖為下圖右:
結(jié)論:檢測白色背景的圖片,會(huì)有一個(gè)和圖像寬高相等的輪廓,而黑色區(qū)域沒有;所以輪廓檢測是針對白色區(qū)域的邊緣進(jìn)行的,這個(gè)和圖像等寬高的輪廓經(jīng)常會(huì)影響一些邏輯的書寫。
層級關(guān)系
-
RETR_EXTERNAL:僅外圈輪廓
# RETR_EXTERNAL:僅外圈輪廓 contours, hierarchy = cv2.findContours(bin_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)cv2.drawContours(img,contours,-1,(0,255,0),5) plt.imshow(img[...,::-1]) plt.axis('off')print(hierarchy) ''' [[[ 1 -1 -1 -1][ 2 0 -1 -1][-1 1 -1 -1]]] '''
從輪廓圖可以發(fā)現(xiàn),僅僅只有確定為最外圈的輪廓被畫出來,而且輸出的hierarchy數(shù)組可以發(fā)現(xiàn),前兩列分別代表當(dāng)前層級當(dāng)前輪廓的下一個(gè)輪廓和上一個(gè)輪廓索引,而后兩列分別代表當(dāng)前層級的子層級的第一個(gè)輪廓索引和父層級的輪廓索引,因?yàn)镽ETR_EXTERNAL只提取最外層輪廓,所以上下層級都是-1
-
RETR_LIST:所有輪廓都包含,但是沒有層級關(guān)系
# RETR_EXTERNAL:全部輪廓,無層級關(guān)系 contours, hierarchy = cv2.findContours(bin_img, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)cv2.drawContours(img,contours,-1,(0,255,0),5) plt.imshow(img[...,::-1]) plt.axis('off')print(hierarchy) ''' [[[ 1 -1 -1 -1][ 2 0 -1 -1][ 3 1 -1 -1][ 4 2 -1 -1][ 5 3 -1 -1][ 6 4 -1 -1][ 7 5 -1 -1][-1 6 -1 -1]]] '''
代表當(dāng)前層級父子層級的后兩個(gè)維度依舊為-1,但是輪廓全部都提取出來了。
-
RETR_CCOMP:僅僅兩層關(guān)系,是否為內(nèi)層或者是否為外層,而且這個(gè)內(nèi)層一定是這個(gè)外層的洞,這個(gè)洞的定義指內(nèi)外層組合構(gòu)成一片白色區(qū)域。如下圖代碼測試
# RETR_CCOMP:全部輪廓,只有兩種層級關(guān)系 contours, hierarchy = cv2.findContours(bin_img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)img_show = img.copy() for i in range(len(contours)):if(hierarchy[0,i,3]!=-1):cv2.drawContours(img_show,contours,i,colormap[i],5)cv2.drawContours(img_show,contours,hierarchy[0,i,3],colormap[i],5) plt.imshow(img_show[...,::-1]) plt.axis('off')print(hierarchy) # 紅:0 橙;1 黃:2 綠:3 青:4 藍(lán):5 紫:6 灰:7 ''' [[ 1 -1 -1 -1][ 2 0 -1 -1][ 4 1 3 -1][-1 -1 -1 2][ 6 2 5 -1][-1 -1 -1 4][ 7 4 -1 -1][-1 6 -1 -1]]] '''上述代碼表示將當(dāng)前輪廓與其父輪廓用同色畫出來:
可以發(fā)現(xiàn)四個(gè)輪廓組成的兩個(gè)白色區(qū)域被顯示出來,綠色區(qū)域?yàn)?號輪廓,從hierarchy中找到3號輪廓的結(jié)構(gòu)為[-1 -1 -1 2],自行可視化可以發(fā)現(xiàn)這個(gè)3號輪廓是白色區(qū)域中最內(nèi)層的那個(gè)輪廓,而其父親索引為2,輪廓2的結(jié)構(gòu)為[ 4 1 3 -1],可以發(fā)現(xiàn)它的第一個(gè)孩子是3,而由于是外輪廓(不管是否為最外圈),所以父親索引為-1。其余輪廓同理分析。
【注】這個(gè)輪廓結(jié)構(gòu)有點(diǎn)繞,但是只需要記住只有內(nèi)、外輪廓,只要當(dāng)前輪廓有內(nèi)輪廓一起組成白色區(qū)域,那么這個(gè)輪廓就是外輪廓,不管它在不在其它輪廓內(nèi)部
可視化時(shí)候本來用當(dāng)前輪廓和子輪廓來顯示,但是想到hierarchy只記錄第一個(gè)子輪廓,當(dāng)時(shí)差點(diǎn)以為組成“洞”的只可能有兩個(gè)輪廓,也就是一個(gè)輪廓有且只可能有一個(gè)子輪廓,但是發(fā)現(xiàn)問題,一個(gè)輪廓可能會(huì)有兩個(gè)子輪廓,所以必須用當(dāng)前輪廓與父輪廓可視化,而不是當(dāng)前輪廓和子輪廓可視化,比如下面這個(gè)圖,及其對應(yīng)的輪廓圖和層級關(guān)系:
輪廓對應(yīng)順序分別是紅、橙、黃,其CCOMP層級關(guān)系為:
[[[-1 -1 1 -1][ 2 -1 -1 0][-1 1 -1 0]]]可以發(fā)現(xiàn),內(nèi)部兩個(gè)輪廓的父親都是0,證明這個(gè)洞是由三個(gè)輪廓組成的。
-
RETR_TREE:這個(gè)是非常嚴(yán)謹(jǐn)?shù)谋磉_(dá)輪廓間層級關(guān)系的參數(shù)
contours, hierarchy = cv2.findContours(bin_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) print(hierarchy) # 紅:0 橙;1 黃:2 綠:3 青:4 藍(lán):5 紫:6 灰:7 ''' [[[ 6 -1 1 -1][-1 -1 2 0][-1 -1 3 1][-1 -1 4 2][ 5 -1 -1 3][-1 4 -1 3][ 7 0 -1 -1][-1 6 -1 -1]]] '''
直接輸出hierarchy看看:真正的由外向內(nèi),一層一層的編號;是CCOMP的更進(jìn)一步細(xì)化,如果CCOMP中構(gòu)成洞的兩個(gè)輪廓的外輪廓在其它輪廓內(nèi)部,那么就是從其它輪廓編號繼續(xù)編號,即洞的外輪廓的父親是包含它的緊鄰著的輪廓編號。
通過判斷父親是否相同,將輪廓按照層級畫出來
img_show = img.copy() for i in range(len(contours)):cv2.drawContours(img_show,contours,i,colormap[hierarchy[0,i,3]+1],5) plt.imshow(img_show[...,::-1]) plt.axis('off')
可以發(fā)現(xiàn),紅色部分就是最外圈輪廓,父親為-1;而最內(nèi)部的青色(菱形、六角星)的孩子是-1,父親是綠色的輪廓3。
存儲(chǔ)方法
CHAIN_APPROX_NONE和CHAIN_APPROX_SIMPLE的區(qū)別就在于輪廓為線段的部分,是否僅存儲(chǔ)端點(diǎn)坐標(biāo)。
比如上述圖片的最外層的矩形輪廓,分別使用兩種存儲(chǔ)參數(shù)去存儲(chǔ)輪廓點(diǎn)的值:
使用SIMPLE只保存端點(diǎn)
contours, hierarchy = cv2.findContours(bin_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) img_show = img.copy() cnt_idx = 0 cnt = contours[cnt_idx] for i in range(cnt.shape[0]):cv2.circle(img_show,(cnt[i,0,0],cnt[i,0,1]),5,(0,255,0),5) plt.imshow(img_show[...,::-1])使用NONE按像素保存
contours, hierarchy = cv2.findContours(bin_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) img_show = img.copy() cnt_idx = 0 cnt = contours[cnt_idx] for i in range(cnt.shape[0]):cv2.circle(img_show,(cnt[i,0,0],cnt[i,0,1]),5,(0,255,0),5) plt.imshow(img_show[...,::-1])輪廓的其它特征和屬性
包括輪廓的圖像矩、面積、周長、多邊形逼近、外接凸多邊形、凸性判斷、外接矩形、外接圓、外接橢圓、直線擬合。
圖像矩
維基百科中的解釋是:指圖像的某些特定像素灰度的加權(quán)平均值,或者是圖像具有類似功能或意義的屬性。可以通過圖像的矩來獲得圖像的部分性質(zhì),包括面積(或總體亮度),以及有關(guān)幾何中心和方向的信息。它可以被用來獲得相對于特定變換的不變性(平移、縮放、旋轉(zhuǎn)不變性) 。具體可查閱維基百科中圖像矩的描述,這里列一下矩的計(jì)算方法:
-
對于二維連續(xù)函數(shù)f(x,y)f(x,y)f(x,y),(p+q)(p+q)(p+q)階的矩被定義為:
Mpq=∫?∞∞∫?∞∞xpyqf(x,y)dxdyM_{pq}=\int_{-\infty}^{\infty}\int_{-\infty}^{\infty}x^py^qf(x,y)dxdy Mpq?=∫?∞∞?∫?∞∞?xpyqf(x,y)dxdy -
對于灰度圖像的像素強(qiáng)度I(x,y)I(x,y)I(x,y),原始圖像的矩MijM_{ij}Mij?計(jì)算方法:
Mij=∑x∑yxiyiI(x,y)M_{ij}=\sum_x\sum_yx^iy^iI(x,y) Mij?=x∑?y∑?xiyiI(x,y) -
原始矩包含以下的一些的有關(guān)原始圖像屬性的信息:
- 二值圖像的面積或灰度圖像的像素總和,可以表示為M00M_{00}M00?
- 圖像的幾何中心可以表示為{xˉ,yˉ}={M10M00,M01M00}\{\bar x,\bar y\}= \{\frac{M_{10}}{M_{00}},\frac{M_{01}}{M_{00}}\}{xˉ,yˉ?}={M00?M10??,M00?M01??}
在OpenCV中的表示為:
cnt = contours[0] M = cv.moments(cnt)中心為:
cx = int(M['m10']/M['m00']) cy = int(M['m01']/M['m00'])輪廓面積和周長
獲取指定輪廓所包含的面積
area = cv.contourArea(cnt)獲取指定輪廓所包含的周長,第二個(gè)參數(shù)指示當(dāng)前輸入為閉合輪廓(true)還是非閉合曲線(false)
perimeter = cv.arcLength(cnt,True)輪廓多邊形逼近
通過具有更少輪廓點(diǎn)的形狀在允許誤差范圍內(nèi)逼近指定輪廓,比如你提取一個(gè)矩形,但是有鋸齒導(dǎo)致輪廓不是矩形,可以使用此功能將矩形近似逼近出來
epsilon = 0.1*cv.arcLength(cnt,True) approx = cv.approxPolyDP(cnt,epsilon,True)這個(gè)意思就是新的輪廓的周長和原始輪廓周長的誤差范圍在原周長的十分之一以內(nèi)。
比如最開始的例子中,最內(nèi)部的六角星的輪廓點(diǎn)并不是規(guī)整的五角星輪廓,也就是說使用SIMPLE存儲(chǔ)的時(shí)候不是存的每條邊的端點(diǎn)。
下圖就是使用這個(gè)逼近函數(shù)去找到端點(diǎn)的結(jié)果:
contours, hierarchy = cv2.findContours(bin_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) img_show = img.copy() cnt_idx = 4 cnt = contours[cnt_idx] for i in range(cnt.shape[0]):cv2.circle(img_show,(cnt[i,0,0],cnt[i,0,1]),5,(0,255,0),5)epsilon = 0.1*cv2.arcLength(cnt,True) approx = cv2.approxPolyDP(cnt,epsilon,True) for i in range(approx.shape[0]):cv2.circle(img_show,(approx[i,0,0],approx[i,0,1]),5,(0,0,255),5)plt.imshow(img_show[...,::-1]) plt.axis('off')綠色為原始輪廓點(diǎn),紅色為逼近后的輪廓點(diǎn),可以發(fā)現(xiàn)六角星的所有邊的頂點(diǎn)都保存了
輪廓凸多邊形逼近
上面的多邊形逼近不管簡化的輪廓是否為凸的,所以又提供了一個(gè)檢測凸多邊形逼近的函數(shù)
hull = cv.convexHull(points[, hull[, clockwise[, returnPoints]]輸入分別為:輪廓點(diǎn)、輸出(不管這個(gè)參數(shù))、順時(shí)針(true)/逆時(shí)針(false)、返回多邊形坐標(biāo)在原輪廓點(diǎn)序中的索引(False)/直接返回坐標(biāo)(true)
還是那個(gè)六角星:
contours, hierarchy = cv2.findContours(bin_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) img_show = img.copy() cnt_idx = 4 cnt = contours[cnt_idx] for i in range(cnt.shape[0]):cv2.circle(img_show,(cnt[i,0,0],cnt[i,0,1]),5,(0,255,0),5)approx = cv2.convexHull(cnt) for i in range(approx.shape[0]):cv2.circle(img_show,(approx[i,0,0],approx[i,0,1]),5,(0,0,255),5)plt.imshow(img_show[...,::-1]) plt.axis('off')綠色為原始輪廓點(diǎn),紅色為凸多邊形逼近后的輪廓點(diǎn),可以發(fā)現(xiàn)比多邊形逼近函數(shù)的結(jié)果少了內(nèi)凹角頂點(diǎn)。
凸性檢測
如何判斷一個(gè)輪廓是否為凸的,有一個(gè)函數(shù)k = cv.isContourConvex(cnt),返回true就是凸的。
OpenCV對這個(gè)凸多邊形還提供了提取更詳細(xì)信息的函數(shù)convexityDefects,用于獲取凸多邊形和輪廓之間的關(guān)系:
void convexityDefects(InputArray contour, InputArray convexhull, OutputArrayconvexityDefects)輸入:原始輪廓點(diǎn)、凸多邊形頂點(diǎn)對應(yīng)原輪廓中的索引、輸出(不管)
所以針對那個(gè)五角星的調(diào)用方法是:
contours, hierarchy = cv2.findContours(bin_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) cnt_idx = 4 cnt = contours[cnt_idx] approx = cv2.convexHull(cnt,returnPoints=False)#使用false獲取多邊形頂點(diǎn)索引print(cv2.convexityDefects(cnt,approx)) ''' [[[ 321 0 357 4403]][[ 0 67 31 4403]][[ 67 128 96 4230]][[ 128 194 163 4223]][[ 194 260 225 4223]][[ 260 321 292 4230]]] '''得到了和多邊形逼近線段個(gè)數(shù)相同行的列為4的矩陣,分別代表:起始點(diǎn)索引、結(jié)束點(diǎn)索引、當(dāng)前線段截取的輪廓點(diǎn)中距離線段最遠(yuǎn)的點(diǎn)索引、這個(gè)最遠(yuǎn)點(diǎn)與當(dāng)前線段的距離
驗(yàn)證一下,把第三個(gè)維度,也就是距離每條邊最遠(yuǎn)的輪廓點(diǎn)畫出來:
邊界框
分為矩形、圓形邊界
-
矩形:不考慮形狀的旋轉(zhuǎn),獲取直邊界矩形
x,y,w,h = cv2.boundingRect(cnt) -
矩形考慮旋轉(zhuǎn),獲取最小的外接矩形
rect = cv2.minAreaRect(cnt) box = cv2.boxPoints(rect) box = np.int0(box)
畫圖看看區(qū)別,直邊界用綠色,最小外接矩形用紅色
img_show = img.copy() #無旋轉(zhuǎn)矩形 cv2.rectangle(img_show,(x,y),(x+w,y+h),(0,255,0),4) #有旋轉(zhuǎn)矩形 cv2.drawContours(img_show,[box],0,(0,0,255),4)plt.imshow(img_show[...,::-1]) plt.axis('off')外接圓:minEnclosingCircle
# 最小外接圓 (x,y),radius = cv2.minEnclosingCircle(cnt) center = (int(x),int(y)) radius = int(radius)img_show = img.copy() cv2.circle(img,center,radius,(0,255,0),10) plt.imshow(img_show[...,::-1]) plt.axis('off')形狀擬合
包括橢圓、直線擬合
橢圓擬合:fitEllipse,將里面的那個(gè)菱形擬合
# 橢圓擬合 ellipse = cv2.fitEllipse(contours[5])img_show = img.copy() cv2.ellipse(img_show,ellipse,(0,255,0),10) plt.imshow(img_show[...,::-1]) plt.axis('off')直線擬合:fitLine,使得當(dāng)前輪廓所有點(diǎn)與直線距離和最短
rows,cols = img.shape[:2] [vx,vy,x,y] = cv2.fitLine(cnt, cv2.DIST_L2,0,0.01,0.01) lefty = int((-x*vy/vx) + y) righty = int(((cols-x)*vy/vx)+y)img_show = img.copy() cv2.line(img_show,(cols-1,righty),(0,lefty),(0,255,0),2) plt.imshow(img_show[...,::-1]) plt.axis('off')點(diǎn)與輪廓的關(guān)系
通過pointPolygonTest函數(shù)判斷某個(gè)點(diǎn)是否在輪廓內(nèi)部后者外部,然后返回距離輪廓的最短距離
retval=cv.pointPolygonTest(contour,pt,measureDist)輸入分別為:輪廓、某個(gè)點(diǎn)、是否返回距離;如果僅僅需要判斷點(diǎn)是否再輪廓內(nèi)部,第三個(gè)參數(shù)設(shè)置False,在內(nèi)部為+1,外部為-1,在輪廓上為0。
形狀匹配
可以利用matchShapes輸入兩個(gè)輪廓,計(jì)算相似度,得分越低越相似
retval = cv.matchShapes( contour1, contour2, method, parameter )輸入為:第一個(gè)形狀的輪廓、第二給形狀的輪廓、匹配算法、參數(shù)(暫不支持,不管)
匹配算法是基于圖像的Hu矩,計(jì)算方法為:
mi=sign(hi)?log?him_i = sign(h_i)\cdot \log h_i mi?=sign(hi?)?loghi?
其中hih_ihi?代表Hu矩。
匹配算法分為:
使用案例,先構(gòu)建一些圖像,然后計(jì)算相似度:
img1 = cv2.imread('shape1.png',0) img2 = cv2.imread('shape2.png',0) img3 = cv2.imread('shape3.png',0) ret, thresh = cv2.threshold(img1, 127, 255,0) ret, thresh2 = cv2.threshold(img2, 127, 255,0) ret, thresh3 = cv2.threshold(img3, 127, 255,0) contours,hierarchy = cv2.findContours(thresh,2,1) cnt1 = contours[0] contours,hierarchy = cv2.findContours(thresh2,2,1) cnt2 = contours[0] contours,hierarchy = cv2.findContours(thresh3,2,1) cnt3 = contours[0] ret1 = cv2.matchShapes(cnt1,cnt2,1,0.0) ret2 = cv2.matchShapes(cnt1,cnt3,1,0.0) print( ret1,ret2 ) plt.subplot(131) plt.imshow(img1,cmap='gray') plt.axis('off') plt.subplot(132) plt.imshow(img2,cmap='gray') plt.axis('off') plt.subplot(133) plt.imshow(img3,cmap='gray') plt.axis('off') ''' 0.14475720763533126 0.3168697153308031 '''可以發(fā)現(xiàn)形狀1和2的非常接近,一個(gè)四角星一個(gè)五角星,他倆得分很低,越相似。
其它屬性
-
獲取掩膜(mask)
mask = np.zeros(gray_img.shape,np.uint8) cv2.drawContours(mask,[cnt],0,255,-1) pixelpoints = np.transpose(np.nonzero(mask)) #pixelpoints = cv.findNonZero(mask) plt.imshow(mask,cmap='gray')
注意使用可以使用numpy或者OpenCV去查找到掩膜內(nèi)所有像素坐標(biāo),但是他倆的位置不一樣,因此numpy的坐標(biāo)需要轉(zhuǎn)置才能與OpenCV保持一致,列是x,行是y
-
獲取局部最大值、最小值級它們的位置
min_val, max_val, min_loc, max_loc = cv.minMaxLoc(imgray,mask = mask)注意第一個(gè)參數(shù)必須是單通道的圖,第二個(gè)參數(shù)可有可無,用于選擇特定區(qū)域。
這個(gè)在Openpose中,從每個(gè)關(guān)節(jié)的特征圖中提取關(guān)節(jié)坐標(biāo)用到過,具體可看之前解析OpenPose的文章。
-
均值:通道分開
mean_val = cv.mean(im,mask = mask)
后記
圖像處理經(jīng)常遇到輪廓相關(guān)的問題,比如二維碼檢測定位之類的大都是用二維碼四個(gè)角的定位符和矯正符的比例特征來定位。這里對官方的教程做了簡單的綜合整理。
完整的python腳本實(shí)現(xiàn)放在微信公眾號的簡介中描述的github中,有興趣可以去找找,同時(shí)文章也同步到微信公眾號中,有疑問或者興趣歡迎公眾號私信。
總結(jié)
以上是生活随笔為你收集整理的OpenCV学习——轮廓检测的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: VUE-周日历的实现
- 下一篇: Power BI 报表服务器中的行级别安