天天看點

win10+Python3.7.3+OpenCV3.4.1入門學習(十二 圖像輪廓)————12.6 凸包

Python版本是Python3.7.3,OpenCV版本OpenCV3.4.1,開發環境為PyCharm

文章目錄

    • 12.6 凸包
      • 12.6.1 擷取凸包
      • 12.6.2 凸缺陷
      • 12.6.3 幾何學測試

12.6 凸包

逼近多邊形是輪廓的高度近似,但是有時候,我們希望使用一個多邊形的凸包來簡化它。凸包跟逼近多邊形很像,隻不過它是物體最外層的“凸”多邊形。凸包指的是完全包含原有輪廓,并且僅由輪廓上的點所構成的多邊形。凸包的每一處都是凸的,即在凸包内連接配接任意兩點的直線都在凸包的内部。在凸包内,任意連續三個點的内角小于180°。

例如,在下圖中,最外層的多邊形為機械手的凸包,在機械手邊緣與凸包之間的部分被稱為凸缺陷(Convexity Defect),凸缺陷能夠用來處理手勢識别等問題。

win10+Python3.7.3+OpenCV3.4.1入門學習(十二 圖像輪廓)————12.6 凸包

12.6.1 擷取凸包

OpenCV提供函數cv2.convexHull()用于擷取輪廓的凸包。該函數的文法格式為:

hull = cv2.convexHull( points[, clockwise[, returnPoints]] )
           

式中的傳回值hull為凸包角點。

式中的參數如下:

● points:輪廓。

● clockwise:布爾型值。該值為True時,凸包角點将按順時針方向排列;該值為False時,則以逆時針方向排列凸包角點。

● returnPoints:布爾型值。預設值是True,函數傳回凸包角點的x/y軸坐标;當為False時,函數傳回輪廓中凸包角點的索引。

eg1:設計程式,觀察函數cv2.convexHull()内參數returnPoints的使用情況。

代碼如下:

import cv2
o = cv2.imread('contours.bmp')  
gray = cv2.cvtColor(o,cv2.COLOR_BGR2GRAY)  
ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)  
image,contours, hierarchy = cv2.findContours(binary,
                                             cv2.RETR_TREE,
                                             cv2.CHAIN_APPROX_SIMPLE)  
hull = cv2.convexHull(contours[0])   #傳回坐标值
print("returnPoints為預設值True時傳回值hull的值:\n",hull)
hull2 = cv2.convexHull(contours[0], returnPoints=False) #傳回索引值
print("returnPoints為False時傳回值hull的值:\n",hull2)
           

運作上述程式,顯示如下的結果:

returnPoints為預設值True時傳回值hull的值:
 [[[195 383]]

 [[ 79 383]]

 [[ 79 270]]

 [[195 270]]]
returnPoints為False時傳回值hull的值:
 [[2]
 [1]
 [0]
           

從程式運作結果可以看出,函數cv2.convexHull()的可選參數returnPoints:

● 為預設值True時,函數傳回凸包角點的x/y軸坐标,本例中傳回了4個輪廓的坐标值。

● 為False時,函數傳回輪廓中凸包角點的索引,本例中傳回了4個輪廓的索引值。

eg2:使用函數cv2.convexHull()擷取輪廓的凸包。

代碼如下:

import cv2
# --------------讀取并繪制原始圖像------------------
o = cv2.imread('hand.bmp')  
cv2.imshow("original",o)
# --------------提取輪廓------------------
gray = cv2.cvtColor(o,cv2.COLOR_BGR2GRAY)  
ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)  
image,contours, hierarchy = cv2.findContours(binary,
                                             cv2.RETR_LIST,
                                             cv2.CHAIN_APPROX_SIMPLE)  
# --------------尋找凸包,得到凸包的角點------------------
hull = cv2.convexHull(contours[0])
# --------------繪制凸包------------------
cv2.polylines(o, [hull], True, (0, 255, 0), 2)
# --------------顯示凸包------------------
cv2.imshow("result",o)
cv2.waitKey()
cv2.destroyAllWindows()
           

運作上述程式,會顯示如下圖所示的圖像。其中:

● 左圖是圖像o。

● 右圖是包含擷取的凸包的圖像。

win10+Python3.7.3+OpenCV3.4.1入門學習(十二 圖像輪廓)————12.6 凸包
win10+Python3.7.3+OpenCV3.4.1入門學習(十二 圖像輪廓)————12.6 凸包

12.6.2 凸缺陷

凸包與輪廓之間的部分,稱為凸缺陷。在OpenCV中使用函數cv2.convexityDefects()擷取凸缺陷。其文法格式如下:

convexityDefects = cv2.convexityDefects( contour, convexhull )
           

式中的傳回值convexityDefects為凸缺陷點集。它是一個數組,每一行包含的值是[起點,終點,輪廓上距離凸包最遠的點,最遠點到凸包的近似距離]。

需要注意的是,傳回結果中[起點,終點,輪廓上距離凸包最遠的點,最遠點到凸包的近似距離]的前三個值是輪廓點的索引,是以需要到輪廓點中去找它們。

式中的參數如下:

● contour是輪廓。

● convexhull是凸包。

需要注意的是,用cv2.convexityDefects()計算凸缺陷時,要使用凸包作為參數。在查找該凸包時,所使用函數cv2.convexHull()的參數returnPoints的值必須是False。

eg:3:使用函數cv2.convexityDefects()計算凸缺陷。

代碼如下:

import cv2
#----------------原圖--------------------------
img = cv2.imread('hand.bmp')
cv2.imshow('original',img)
#----------------構造輪廓--------------------------
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 127, 255,0)
image,contours, hierarchy = cv2.findContours(binary,
                                             cv2.RETR_TREE,
                                             cv2.CHAIN_APPROX_SIMPLE)  
#----------------凸包--------------------------
cnt = contours[0]
hull = cv2.convexHull(cnt,returnPoints = False)
defects = cv2.convexityDefects(cnt,hull)
print("defects=\n",defects)
#----------------構造凸缺陷--------------------------
for i in range(defects.shape[0]):
    s,e,f,d = defects[i,0]
    start = tuple(cnt[s][0])
    end = tuple(cnt[e][0])
    far = tuple(cnt[f][0])
    cv2.line(img,start,end,[0,0,255],2)
    cv2.circle(img,far,5,[255,0,0],-1)
#----------------顯示結果、釋放圖像--------------------------
cv2.imshow('result',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
           

運作上述程式,會顯示如下結果:

defects=
 [[[  305   311   306   114]]

 [[  311   385   342 13666]]

 [[  385   389   386   395]]

 [[  389   489   435 20327]]

 [[    0   102    51 21878]]

 [[  103   184   150 13876]]

 [[  185   233   220  4168]]

 [[  233   238   235   256]]

 [[  238   240   239   247]]

 [[  240   294   255  2715]]

 [[  294   302   295   281]]

 [[  302   304   303   217]]]
           

同時,程式還會顯示如下圖所示的圖像。其中:

● 左圖為圖像o。

● 右圖中用點标出了凸缺陷。可以看出,除了在機械手各個手指的指縫間有凸缺陷外,在無名指、小拇指及手的最下端也都有凸缺陷。

win10+Python3.7.3+OpenCV3.4.1入門學習(十二 圖像輪廓)————12.6 凸包
win10+Python3.7.3+OpenCV3.4.1入門學習(十二 圖像輪廓)————12.6 凸包

12.6.3 幾何學測試

本節介紹幾種與凸包有關的幾何學測試。

1.測試輪廓是否是凸形的

在OpenCV中,可以用函數cv2.isContourConvex()來判斷輪廓是否是凸形的,其文法格式為:

retval = cv2.isContourConvex( contour )
           

式中:

● 傳回值retval是布爾型值。該值為True時,表示輪廓為凸形的;否則,不是凸形的。

● 參數contour為要判斷的輪廓。

eg4:使用函數cv2.isContourConvex()來判斷輪廓是否是凸形的。

代碼如下:

import cv2
o = cv2.imread('hand.bmp')  
cv2.imshow("original",o)
gray = cv2.cvtColor(o,cv2.COLOR_BGR2GRAY)  
ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)  
image,contours, hierarchy = cv2.findContours(binary,
                                             cv2.RETR_LIST,
                                             cv2.CHAIN_APPROX_SIMPLE)  
#--------------凸包----------------------
image1=o.copy()
hull = cv2.convexHull(contours[0])
cv2.polylines(image1, [hull], True, (0, 255, 0), 2)
print("使用函數cv2.convexHull()構造的多邊形是否是凸包:",
      cv2.isContourConvex(hull))
cv2.imshow("result1",image1)
#------------逼近多邊形--------------------
image2=o.copy()
epsilon = 0.01*cv2.arcLength(contours[0],True)
approx = cv2.approxPolyDP(contours[0],epsilon,True)
image2=cv2.drawContours(image2,[approx],0,(0,0,255),2)
print("使用函數cv2.approxPolyDP()構造的多邊形是否是凸包:",
      cv2.isContourConvex(approx))
cv2.imshow("result2",image2)
#------------釋放視窗--------------------
cv2.waitKey()
cv2.destroyAllWindows()
           

運作上述程式,會顯示如下圖所示的圖像。其中:

● 左圖是圖像o。

● 中間的圖顯示了在圖像o上使用函數cv2.convexHull()構造的凸包。

● 右圖顯示了在圖像o上使用函數cv2.approxPolyDP()構造的逼近多邊形。

win10+Python3.7.3+OpenCV3.4.1入門學習(十二 圖像輪廓)————12.6 凸包
win10+Python3.7.3+OpenCV3.4.1入門學習(十二 圖像輪廓)————12.6 凸包
win10+Python3.7.3+OpenCV3.4.1入門學習(十二 圖像輪廓)————12.6 凸包

同時,程式還會顯示如下的結果:

使用函數cv2.convexHull()構造的多邊形是否是凸包: True
使用函數cv2.approxPolyDP()構造的多邊形是否是凸包: False
           

從以上運作結果可以看出:

● 使用函數cv2.convexHull()構造凸包後,對繪制的凸包使用函數cv2.isContourConvex()判斷,傳回值為True,說明該輪廓是凸形的。

● 使用函數cv2.approxPolyDP()構造逼近多邊形後,對繪制的逼近多邊形使用函數cv2.isContourConvex()判斷,傳回值為False,說明該輪廓(多邊形)不是凸形的。

2.點到輪廓的距離

在OpenCV中,函數cv2.pointPolygonTest()被用來計算點到多邊形(輪廓)的最短距離(也就是垂線距離),這個計算過程又稱點和多邊形的關系測試。該函數的文法格式為:

retval = cv2.pointPolygonTest( contour, pt, measureDist )
           

式中的傳回值為retval,與參數measureDist的值有關。

式中的參數如下:

● contour為輪廓。

● pt為待判定的點。

● measureDist為布爾型值,表示距離的判定方式。

● 當值為True時,表示計算點到輪廓的距離。如果點在輪廓的外部,傳回值為負數;如果點在輪廓上,傳回值為0;如果點在輪廓内部,傳回值為正數。

● 當值為False時,不計算距離,隻傳回“-1”、“0”和“1”中的一個值,表示點相對于輪廓的位置關系。如果點在輪廓的外部,傳回值為“-1”;如果點在輪廓上,傳回值為“0”;如果點在輪廓内部,傳回值為“1”。

eg5:使用函數cv2.pointPolygonTest()計算點到輪廓的最短距離。

使用函數cv2.pointPolygonTest()計算點到輪廓的最短距離,需要将參數measureDist的值設定為True。

代碼如下:

import cv2
#----------------原始圖像-------------------------
o = cv2.imread('cs.bmp')
cv2.imshow("original",o)
#----------------擷取凸包------------------------  
gray = cv2.cvtColor(o,cv2.COLOR_BGR2GRAY)  
ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)  
image,contours, hierarchy = cv2.findContours(binary,
                                             cv2.RETR_LIST,
                                             cv2.CHAIN_APPROX_SIMPLE)  
hull = cv2.convexHull(contours[0])
image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
cv2.polylines(image, [hull], True, (0, 255, 0), 2)
#----------------内部點A的距離-------------------------
distA = cv2.pointPolygonTest(hull, (300, 150), True)  
font=cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(image,'A',(300,150), font, 1,(0,255,0),3)
print("distA=",distA) 
#----------------外部點B的距離-------------------------
distB = cv2.pointPolygonTest(hull, (300, 250), True)  
font=cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(image,'B',(300,250), font, 1,(0,255,0),3)
print("distB=",distB) 
#------------正好處于邊緣上的點C的距離-----------------
distC = cv2.pointPolygonTest(hull, (423, 112), True)  
font=cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(image,'C',(423,112), font, 1,(0,255,0),3)
print("distC=",distC) 
#print(hull)   #測試邊緣到底在哪裡,然後再使用确定位置的
#----------------顯示-------------------------
cv2.imshow("result",image)
cv2.waitKey()
cv2.destroyAllWindows()
           

運作上述程式,會顯示如下圖所示的圖像。其中:

● 左圖是圖像o。

● 右圖是标注了點A、B、C位置的圖像。

win10+Python3.7.3+OpenCV3.4.1入門學習(十二 圖像輪廓)————12.6 凸包
win10+Python3.7.3+OpenCV3.4.1入門學習(十二 圖像輪廓)————12.6 凸包

同時,程式還會顯示如下的結果:

distA= 16.891650862259112
distB= -81.17585848021565
distC= -0.0

           

從以上結果可以看出,

● A點算出來的距離為“16.891650862259112”,是一個正數,說明A點在輪廓内部。

● B點算出來的距離為“-81.17585848021565”,是一個負數,說明B點在輪廓外部。

● C點算出來的距離為“-0.0”,說明C點在輪廓上。

在實際使用中,如果想擷取位于輪廓上的點,可以通過列印輪廓點集的方式擷取。例如,本例中可以通過語句“print(hull)”擷取輪廓上的點。在擷取輪廓上的點以後,可以将其用作函數cv2.pointPolygonTest()的參數,以測試函數傳回值是否為零。

eg6:使用函數cv2.pointPolygonTest()判斷點與輪廓的關系。

使用函數cv2.pointPolygonTest()判斷點與輪廓的關系時,需要将參數measureDist的值設定為False。

代碼如下:

import cv2
#----------------原始圖像-------------------------
o = cv2.imread('cs.bmp')
cv2.imshow("original",o)
#----------------擷取凸包------------------------ 
gray = cv2.cvtColor(o,cv2.COLOR_BGR2GRAY)  
ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)  
image,contours, hierarchy = cv2.findContours(binary,
                                             cv2.RETR_LIST,
                                             cv2.CHAIN_APPROX_SIMPLE)  
hull = cv2.convexHull(contours[0])
image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
cv2.polylines(image, [hull], True, (0, 255, 0), 2)
#----------------内部點A與多邊形的關系-------------------------
distA = cv2.pointPolygonTest(hull, (300, 150),False)  
font=cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(image,'A',(300,150), font, 1,(0,255,0),3)
print("distA=",distA) 
#----------------外部點B與多邊形的關系-------------------------
distB = cv2.pointPolygonTest(hull, (300, 250), False)  
font=cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(image,'B',(300,250), font, 1,(0,255,0),3)
print("distB=",distB) 
#----------------邊緣線上點C與多邊形的關系----------------------
distC = cv2.pointPolygonTest(hull, (423, 112),False)  
font=cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(image,'C',(423,112), font, 1,(0,255,0),3)
print("distC=",distC) 
#print(hull)   #測試邊緣到底在哪裡,然後再使用确定位置的
#----------------顯示-------------------------
cv2.imshow("result",image)
cv2.waitKey()
cv2.destroyAllWindows()
           

運作上述程式,會顯示如下圖所示的圖像。其中:

● 左圖是圖像o。

● 右圖是标注了點A、B、C位置的圖像。

win10+Python3.7.3+OpenCV3.4.1入門學習(十二 圖像輪廓)————12.6 凸包
win10+Python3.7.3+OpenCV3.4.1入門學習(十二 圖像輪廓)————12.6 凸包

同時,程式還會顯示如下的運作結果:

distA= 1.0
distB= -1.0
distC= 0.0
           

從以上結果可以看出,

● A點算出來的關系值為“1”,說明該點在輪廓的内部。

● B點算出來的關系值為“-1”,說明該點在輪廓的外部。

● C點算出來的關系值為零值,說明該點在輪廓上。

繼續閱讀