針對物體輪廓,opencv還提供了一些相關的函數,來處理輪廓查找,繪制,擬合,以及計算輪廓周長和面積等,詳細介紹如下:
1. 尋找和繪制輪廓
opencv的findContours()能尋找圖片中的輪廓,實現的是下面論文的算法:
Satoshi Suzuki and others. Topological structural analysis of digitized binary images by border following. Computer Vision, Graphics, and Image Processing, 30(1):32–46, 1985.
函數對應的參數如下:
contours, hierarchy =cv2.findContours(image, mode, method, offset=None) image: 單通道的二值圖(若輸入灰度圖,非零像素點會被設成1,0像素點設成0) mode: 輪廓檢索模式,包括RETR_EXTERNAL,RETR_LIST,RETR_CCOMP,RETR_TREE,RETR_FLOODFILL RETR_EXTERNAL: 只檢索最外層的輪廓 (返回值會設置所有hierarchy[i][2]=hierarchy[i][3]=-1) RETR_LIST: 檢索所有的輪廓,但不建立輪廓間的層次關系(hierarchy relationship) RETR_CCOMP: 檢測所有的輪廓,但只建立兩個等級關系,外圍為頂層,若外圍內的內圍輪廓還包含了其他的輪廓信息,則內圍內的所有輪廓均歸屬於頂層,只有內圍輪廓不再包含子輪廓時,其為內層。 RETR_TREE:檢測所有輪廓,所有輪廓建立一個等級樹結構。外層輪廓包含內層輪廓,內層輪廓還可以繼續包含內嵌輪廓。 RETR_FLOODFILL: method: 輪廓的近似方法,包括CHAIN_APPROX_NONE,CHAIN_APPROX_SIMPLE,CHAIN_APPROX_TC89_L1,CHAIN_APPROX_TC89_KCOS CHAIN_APPROX_NONE: 保存物體邊界上所有連續的輪廓點到contours中,即點(x1,y1)和點(x2,y2),滿足max(abs(x1-x2),abs(y2-y1))==1,則認為其是連續的輪廓點 CHAIN_APPROX_SIMPLE: 僅保存輪廓的拐點信息到contours,拐點與拐點之間直線段上的信息點不予保留 CHAIN_APPROX_TC89_L1: 采用Teh-Chin chain近似算法 CHAIN_APPROX_TC89_KCOS:采用Teh-Chin chain近似算法 offset:偏移量,所有的輪廓信息相對於原始圖像的偏移量,相當於在每一個檢測出的輪廓點上加上該偏移量(在圖片裁剪時比較有用) 返回值: contours:返回的輪廓點集合,一個list,每一個元素是一個輪廓,輪廓是一個N*1*2的ndarray hierarchy: 輪廓之間的層次關系,每一個元素對應contours中相應索引輪廓的層次關系,是一個N*4的array,hierarchy[i][0]~hierarchy[i][3]分別表示第i個輪廓的后一個輪廓,
前一個輪廓,第一個內嵌輪廓(子輪廓),父輪廓的索引編號,如果當前輪廓沒有對應的后一個輪廓、前一個輪廓、內嵌輪廓或父輪廓,則hierarchy[i][0]~hierarchy[i][3]的相應位被設置為默認值-1。
opencv還提供drawContours()函數來繪制檢測到的輪廓,其對應參數如下:
image=cv2.drawContours(image, contours, contourIdx, color, thickness=None, lineType=None, hierarchy=None, maxLevel=None, offset=None
image: 繪制的輪廓的圖像矩陣
contours: 所有的輪廓集合(findContours()返回值)
contourIdx: 輪廓集合的索引,表示指定一個輪廓進行繪制;若為負數,表示繪制所有輪廓
color: 繪制使用的顏色
thickness:線的粗細
lineType: 線的類型,包括FILLED,LINE_4,LINE_8,LINE_AA
hierarchy: 輪廓的層次關系(findContours()返回值)
maxLevel: 0表示只有指定的輪廓被繪制,1表示繪制指定的輪廓和其第一層內嵌輪廓,2表示繪制指定輪廓和其所有的內嵌輪廓(只有hierarchy部位None時,才起作用)
offset: 繪制輪廓時的偏移量
一般輪廓尋找和擬合,能解決一些簡單的目標定位問題,其大致流程如下:
-
對圖像邊緣檢測或閾值分割得到二值圖(適當的形態學處理)
-
利用findContours()函數尋找二值圖中多個輪廓
-
對於每一個輪廓采用boundingRect(), minAreaRect()等進行擬合得到目標位置框
findContours()函數使用示例代碼及結果如下:

#coding:utf-8 import cv2 img = cv2.imread(r"D:\data\receipt.jpg") img1 = img.copy() img2 = img.copy() img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) img_gaussian = cv2.GaussianBlur(img_gray, (3, 3), 1) edge = cv2.Canny(img_gaussian, 100, 300) kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (10, 1)) edge = cv2.dilate(edge, kernel, iterations=2) #橫向的形態學膨脹 # thre, edge = cv2.threshold(img_gaussian, 0, 255, cv2.THRESH_OTSU+cv2.THRESH_BINARY) #尋找輪廓 contours, hierarchy = cv2.findContours(edge, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cv2.drawContours(img1, contours, -1, (0,0,255)) #輪廓擬合 num = len(contours) for i in range(num): area = cv2.contourArea(contours[i], oriented=False) if 30 < area < 8000: #限定輪廓的面積 rect = cv2.boundingRect(contours[i]) print(rect) cv2.rectangle(img2, (rect[0], rect[1]), (rect[0]+rect[2], rect[1]+rect[3]), (0, 255, 0)) # cv2.imshow("img_gray", img_gray) cv2.imshow("img", img) cv2.imshow("img_contour", img1) cv2.imshow("img_rect", img2) cv2.imshow("edge", edge) cv2.waitKey(0) cv2.destroyAllWindows()
2. 輪廓周長和面積
opencv提供函數arcLength()來計算點集所圍區域的周長,其參數如下:
retval= cv2.arcLength(curve, closed) curve: 坐標點集,n*2的array closed: 點集所圍區域是否時封閉的
opencv提供函數contourArea() 來計算點集所圍區域的面積,其參數如下:
retval=cv2.contourArea(contour, oriented=False)
contour: 組成輪廓的坐標點集
oriented: 為True時,返回的面積會帶有符號,正數表示輪廓點順時針排列,負數表示逆時針排列;為False時,返回面積的絕對值,不帶符號
arcLength() 和contourArea()函數使用示例如下:

#coding:utf-8 import cv2 import numpy as np img = np.ones((400, 400,3), dtype=np.uint8)*255 points = np.array([[120, 120],[150, 170],[120, 220],[220,220],[200,170],[220,120]], np.int32) length1 = cv2.arcLength(points, False) #首尾不相連 length2 = cv2.arcLength(points, True) #首尾相連 print(length1, length2) #324.3223342895508 424.3223342895508 area1 = cv2.contourArea(points, oriented=True) #返回點集排列順序 area2 = cv2.contourArea(points, oriented=False) print(area1, area2) #-7500.0 7500.0 rows, cols = points.shape for i in range(rows): point1 = tuple(points[i]) point2 = tuple(points[(i+1)%rows]) cv2.circle(img, point1, radius=2, color=(55, 55, 55), thickness=2) cv2.line(img, point1, point2, color=(0, 255, 0), thickness=1, lineType=cv2.LINE_4) cv2.imshow("img", img) cv2.waitKey(0) cv2.destroyAllWindows()
opencv提供函數pointPolygonTest()來計算坐標點和一個輪廓的位置關系,其參數如下:
retval=cv.pointPolygonTest(contour, pt, measureDist) contour: 組成輪廓的點集 pt: 坐標點 measureDist: 為False時,返回值為1,-1,0(1表示點在輪廓內,-1表示點在輪廓外面,0在輪廓邊緣上);為True時,返回坐標點離輪廓邊緣的最小距離(帶符號,分別表示輪廓內和輪廓外)
pointPolygonTest()函數的使用示例代碼如下:

#coding:utf-8 import cv2 import numpy as np img = np.ones((400, 400,3), dtype=np.uint8)*255 points = np.array([[120, 120],[150, 170],[120, 220],[220,220],[200,170],[220,120]], np.int32) p1 = (100, 100) p1_ret = cv2.pointPolygonTest(points, p1, measureDist=False) p1_ret2 = cv2.pointPolygonTest(points, p1, measureDist=True) print(p1_ret, p1_ret2) # -1.0 -28.284271247461902 p2 = (160, 200) p2_ret = cv2.pointPolygonTest(points, p2, measureDist=False) p2_ret2 = cv2.pointPolygonTest(points, p2, measureDist=True) print(p2_ret, p2_ret2) #1.0 20.0 cv2.circle(img, p1, radius=2, color=(0, 0, 255), thickness=2) cv2.circle(img, p2, radius=2, color=(0, 0, 255), thickness=2) rows, cols = points.shape for i in range(rows): point1 = tuple(points[i]) point2 = tuple(points[(i+1)%rows]) cv2.circle(img, point1, radius=2, color=(55, 55, 55), thickness=2) cv2.line(img, point1, point2, color=(0, 255, 0), thickness=1, lineType=cv2.LINE_4) cv2.imshow("img", img) cv2.waitKey(0) cv2.destroyAllWindows()
4. 輪廓的凸包缺陷
convexHull()函數能檢測出點集的最小凸包,opencv還提供了函數convexityDefects()來檢測凸包的缺陷,這里缺陷指凸包的內陷處,如下圖所示:
convexityDefects()函數的參數如下:
convexityDefects=cv2.convexityDefects(contour, convexhull) contour: 組成輪廓的點集(有序的點集) convexhull: convexHull()的返回值,代表組成凸包的的坐標點集索引 返回值: convexityDefects:n*1*4的array,每一個元素代表一個缺陷,缺陷包括四個值:缺陷的起點,終點和最遠點的索引,最遠點到凸包的距離 (返回的距離值放大了256倍,所以除以256才是實際的距離)
convexityDefects()函數使用代碼和結果如下:

#coding:utf-8 import cv2 import numpy as np img = np.ones((400, 400,3), dtype=np.uint8)*255 points = np.array([[120, 120],[150, 170],[120, 220],[220,220],[200,170],[220,120]], np.int32) hull = cv2.convexHull(points,returnPoints=False) # 返回索引 defects = cv2.convexityDefects(points, hull) print(hull) print(defects) rows, cols = points.shape for i in range(rows): point1 = tuple(points[i]) point2 = tuple(points[(i+1)%rows]) cv2.circle(img, point1, radius=2, color=(55, 55, 55), thickness=2) cv2.line(img, point1, point2, color=(0, 255, 0), thickness=1, lineType=cv2.LINE_4) rows2, _ = hull.shape for j in range(rows2): index1 = hull[j][0] index2 = hull[(j+1)%rows2][0] point1 = tuple(points[index1]) point2 = tuple(points[index2]) cv2.line(img, point1, point2, color=(0, 0, 255), thickness=1, lineType=cv2.LINE_4) cv2.imshow("img", img) cv2.waitKey(0) cv2.destroyAllWindows()