Python版本是Python3.7.3,OpenCV版本OpenCV3.4.1,开发环境为PyCharm
逼近多边形是轮廓的高度近似,但是有时候,我们希望使用一个多边形的凸包来简化它。凸包跟逼近多边形很像,只不过它是物体最外层的“凸”多边形。凸包指的是完全包含原有轮廓,并且仅由轮廓上的点所构成的多边形。凸包的每一处都是凸的,即在凸包内连接任意两点的直线都在凸包的内部。在凸包内,任意连续三个点的内角小于180°。
例如,在下图中,最外层的多边形为机械手的凸包,在机械手边缘与凸包之间的部分被称为凸缺陷(Convexity Defect),凸缺陷能够用来处理手势识别等问题。
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。
● 右图是包含获取的凸包的图像。
凸包与轮廓之间的部分,称为凸缺陷。在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。
● 右图中用点标出了凸缺陷。可以看出,除了在机械手各个手指的指缝间有凸缺陷外,在无名指、小拇指及手的最下端也都有凸缺陷。
本节介绍几种与凸包有关的几何学测试。
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点算出来的关系值为零值,说明该点在轮廓上。