1. 程式人生 > 實用技巧 >(八)OpenCV-Python學習—輪廓查詢,繪製和擬合

(八)OpenCV-Python學習—輪廓查詢,繪製和擬合

針對物體輪廓,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

  Teh-Chin chain近似演算法: C-H Teh and Roland T. Chin. On the detection of dominant points on digital curves. Pattern Analysis and Machine Intelligence, IEEE Transactions on, 11(8):859–872, 1989.

  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()
cv2.findContours()

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()
arcLength()和contourArea()

3. 點和輪廓的位置關係

  opencv提供函式pointPolygonTest()來計算座標點和一個輪廓的位置關係,其引數如下:

retval=cv.pointPolygonTest(contour, pt, measureDist)
    contour: 組成輪廓的點集
    pt: 座標點
    measureDist: 為False時,返回值為1,-10(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()
cv2.pointPolygonTest()

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()
cv2.convexityDefects()

參考:https://zhuanlan.zhihu.com/p/107257870