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),凸缺陷能夠用來處理手勢識别等問題。

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。
● 右圖是包含擷取的凸包的圖像。
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。
● 右圖中用點标出了凸缺陷。可以看出,除了在機械手各個手指的指縫間有凸缺陷外,在無名指、小拇指及手的最下端也都有凸缺陷。
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()構造的逼近多邊形。
同時,程式還會顯示如下的結果:
使用函數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位置的圖像。
同時,程式還會顯示如下的結果:
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位置的圖像。
同時,程式還會顯示如下的運作結果:
distA= 1.0
distB= -1.0
distC= 0.0
從以上結果可以看出,
● A點算出來的關系值為“1”,說明該點在輪廓的内部。
● B點算出來的關系值為“-1”,說明該點在輪廓的外部。
● C點算出來的關系值為零值,說明該點在輪廓上。