【图像处理】——Python+opencv实现二值图像的轮廓边界跟踪以及轮廓面积周长的求解(findcontours函数和contourArea函数)
目錄
一、函數 cv.findContours
二、輪廓層級(返回參數hierarchy)
三、輪廓尋找方式
1. RETR_LIST
2. RETR_TREE
3. RETR_EXTERNAL
4. RETR_CCOMP
四、實例
五、contourArea(contour)函數詳解
六、 drawContours()函數詳解
七、基于鏈碼的二值圖像輪廓跟蹤法
代碼復現:
-
轉載請注明本函數文詳細地址
-
本文講解了opencv自帶輪廓跟蹤以及繪制輪廓的函數
-
本文講解了一種基于鏈碼的一種輪廓檢測方法
-
本文一到四部分轉載至:《opencv cv.findContours 函數詳解》
一、函數 cv.findContours
contours, hierarchy = cv.findContours( image, mode, method[, contours[, hierarchy[, offset]]] )參數1:源圖像參數2:輪廓的檢索方式,這篇文章主要講解這個參數參數3:一般用 cv.CHAIN_APPROX_SIMPLE,就表示用盡可能少的像素點表示輪廓contours:圖像輪廓坐標,是一個列表hierarchy:[Next, Previous, First Child, Parent],文中有詳細解釋我們使用cv.findContours()尋找輪廓時,參數2表示輪廓的檢索方式(RetrievalModes),當我們傳入的是cv.RETR_TREE,它表示什么意思呢?另外,函數返回值hierarchy有什么用途呢?下面我們就來研究下這兩個問題。
二、輪廓層級(返回參數hierarchy)
圖中總共有8條輪廓,2和2a分別表示外層和里層的輪廓,3和3a也是一樣。從圖中看得出來:
??? 輪廓0/1/2是最外層的輪廓,我們可以說它們處于同一輪廓等級:0級
??? 輪廓2a是輪廓2的子輪廓,反過來說2是2a的父輪廓。輪廓2a算一個等級:1級
??? 同樣3是2a的子輪廓,輪廓3處于一個等級:2級
??? 類似的,3a是3的子輪廓,等等…………
??? 這里面OpenCV關注的就是兩個概念:同一輪廓等級和輪廓間的子屬關系。
?
現在既然我們了解了輪廓層級的概念,那么類似cv.RETR_TREE的輪廓尋找方式又是啥意思呢?
三、輪廓尋找方式
OpenCV中有四種輪廓尋找方式RetrievalModes,下面分別來看下:
1. RETR_LIST
這是最簡單的一種尋找方式,它不建立輪廓間的子屬關系,也就是所有輪廓都屬于同一層級。這樣,hierarchy中的后兩個值[First Child, Parent]都為-1。比如同樣的圖,我們使用cv.RETR_LIST來尋找輪廓:
?
?因為沒有從屬關系,所以輪廓0的下一條是1,1的下一條是2……
如果你不需要輪廓層級信息的話,cv.RETR_LIST更推薦使用,因為性能更好
2. RETR_TREE
cv.RETR_TREE就是之前我們一直在使用的方式,它會完整建立輪廓的層級從屬關系,前面已經詳細說明過了。
3. RETR_EXTERNAL
這種方式只尋找最高層級的輪廓,也就是它只會找到前面我們所說的3條0級輪廓:
實驗講解 RETR_EXTERNAL
4. RETR_CCOMP
相比之下cv.RETR_CCOMP比較難理解,它把所有的輪廓只分為2個層級,不是外層的就是里層的。結合代碼和圖片,我們來理解下:
實驗講解 RETR_CCOMP
contours, hierarchy = cv.findContours(thresh, cv.RETR_CCOMP, 2) print(hierarchy) # 結果如下 [[[ 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]]]使用這個參數找到的輪廓序號與之前不同。
圖中括號里面1代表外層輪廓,2代表里層輪廓。比如說對于輪廓2,Next就是4,Previous是1,它有里層的輪廓3,所以First Child=3,但因為只有兩個層級,它本身就是外層輪廓,所以Parent=-1。大家可以針對其他的輪廓自己驗證一下。
四、實例
import cv2 as cv
import numpy# 1.讀入圖片
img = cv.imread('test_contours.jpg')
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
contours, thresh = cv.threshold(img_gray, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)# 2.尋找輪廓
contours, hierarchy = cv.findContours(thresh, cv.RETR_LIST, 2)print(len(contours),hierarchy)# 3.繪制輪廓
cv.drawContours(img, contours, -1, (0, 0, 255), 2)cv.imshow('result',img)
cv.waitKey(0)
cv.destroyAllWindows()
五、contourArea(contour)函數詳解
contourArea(contour) #contour是存放著一個輪廓的坐標點的列表六、 drawContours()函數詳解
drawContours函數解析cv2.drawContours(draw_img,#繪制輪廓的畫布contours, #輪廓信息-1, #-1表示繪制所有的輪廓,具體的數字的話就是取列表中的某一個輪廓進行繪制( 0,0,225),#繪制輪廓的顏色,BGR通道的值,這里是指用紅色進行繪制2)#表示線寬,-1表示填充七、基于鏈碼的二值圖像輪廓跟蹤法
輪廓跟蹤法大部分的思路都是一致的,就是找到黑白兩個像素相鄰的物體像素格,然后根據一定的規則進行下一個邊界點的查找,當查找的下一個邊界點回到起始點時則結束這個輪廓的掃描。一般都是鏈表和一定的鄰域搜索規則結合使用進行鄰域搜索尋找下一個邊界點
鏈碼可參考:https://baike.baidu.com/item/%E9%93%BE%E7%A0%81/4272744?fr=aladdin
鏈碼表示了搜索的方向:
以下定義了利用當前點和上一個邊界點的相對位置關系的鄰域搜索方法
因此可以得出尋找下一邊界點的準則如下:設當前點 P ( x, y ) 在上一邊界點 C 的 8 鄰域內的位置編碼為 n, 則從當前點( x, y ) 的8鄰域內的編碼為 n 的位置, 順時針方向移動 2 個像素的位置就是下一邊界點的起始搜索位置.若不是邊界點,則從搜索的起始點開始按照逆時針方向順次搜索, 共搜索 5 次便可以找到下一個邊界點
順序說明:整體的輪廓是逆時針方向得到的,鄰域搜索的方法為逆時針,鄰域起始點的位置由順時針獲得,這里的順序都是通過鏈碼來進行控制的
?
參考論文:
《二值圖像中目標物體輪廓的邊界跟蹤算法》
?
代碼復現:
import cv2 import numpy as np import timestart = time.time() img_src = cv2.imread("dilate_img.jpg")#dialte_img,jpg是一張二值圖,但是由imread讀取進來默認是3通道的圖片# 2.灰度化與二值化 img_gray = cv2.cvtColor(img_src,cv2.COLOR_RGB2GRAY)#將其變成單通道 ret, img_bin = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)#防止變換過程中非0和非255的值出現而影響結果 img_bin = cv2.copyMakeBorder(img_bin,1,1,1,1,borderType=cv2.BORDER_CONSTANT,value=0)#在圖片四周各補零一行,即增加一行黑色像素 #初始化起始點 start_x = -1 start_y = -1 is_start_point = 0#判斷是否為起始點的標志#尋找起始點 h,w = img_bin.shape for i in range(h):for j in range(w):if (img_bin[i,j] == 255) and (img_bin[i-1,j] == 0):start_x = istart_y = jis_start_point = 1breakif is_start_point == 1:break #(1,39)#定義鏈碼相對應的增量坐標 neibor = [(0,1),(-1,1),(-1,0),(-1,-1),(0,-1),(1,-1),(1,0),(1,1)]#鄰域點 temp = 2#鏈碼值,也是neibor的索引序號,這里是從鏈碼的2號位進行搜索 contours = [(start_x,start_y)]#用于存儲輪廓點#將當前點設為輪廓的開始點 current_x = start_x current_y = start_y#temp=2,表示從鏈碼的2方向進行鄰域檢索,通過當前點和鄰域點集以及鏈碼值確定鄰域點 neibor_x = current_x + neibor[temp][0] neibor_y = current_y + neibor[temp][1]#因為當前點的值為起始點,而終止檢索的條件又是這個,所以會產生沖突,因此先尋找第二個邊界點 is_contour_point = 0 while is_contour_point == 0: # 鄰域點循環,當是目標像素點時跳出if img_bin[neibor_x, neibor_y] == 255:# 將符合條件的鄰域點設為當前點進行下一次的邊界點搜索current_x = neibor_xcurrent_y = neibor_yis_contour_point = 1contours.append((current_x, current_y))temp = temp - 2 # 作為下一個邊界點的鄰域檢測起始點,順時針旋轉90度print(1)if temp < 0:temp = len(neibor) + tempelse:temp = temp + 1 # 逆時針旋轉45度進行搜索if temp >= 8:temp = temp - len(neibor)neibor_x = current_x + neibor[temp][0]neibor_y = current_y + neibor[temp][1]#開始第三個及以后的邊界點的查找 while not((current_x == start_x) and (current_y == start_y)):#輪廓掃描循環is_contour_point = 0while is_contour_point == 0:#鄰域點循環,當是目標像素點時跳出if img_bin[neibor_x,neibor_y] == 255:#鄰域是白點時,即為邊界#將符合條件的鄰域點設為當前點進行下一次的邊界點搜索current_x = neibor_xcurrent_y = neibor_yis_contour_point = 1#將判斷是否為邊界點的標簽設置為1,用于跳出循環contours.append((current_x,current_y))temp = temp - 2#作為下一個邊界點的鄰域檢測起始點,順時針旋轉90度if temp < 0:temp = len(neibor) + tempelse:temp = temp + 1#逆時針旋轉45度進行搜索if temp >= 8:temp = temp - len(neibor)neibor_x = current_x + neibor[temp][0]neibor_y = current_y + neibor[temp][1]#對已經檢測到的輪廓進行標記 for xy in (contours):x = xy[0]y = xy[1]img_src[x-1,y-1] = (0,0,255) cv2.imshow('',img_src) cv2.waitKey(0)print((contours)) end = time.time() print("時長%fms"%((end-start)*1000))對于多輪廓邊界的掃描,需要每進行完一條輪廓的掃描就對該輪廓進行標記,以免重復掃描
總結
以上是生活随笔為你收集整理的【图像处理】——Python+opencv实现二值图像的轮廓边界跟踪以及轮廓面积周长的求解(findcontours函数和contourArea函数)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ftp的本地用户搭建
- 下一篇: UVC协议CT_ZOOM_RELATIV