逼近多边形是轮廓的高度近似,但是有时候,我们希望使用一个多边形的凸包来简化它。
凸包跟逼近多边形很像,只不过它是物体最外层的“凸”多边形。凸包指的是完全包含原有轮 廓,并且仅由轮廓上的点所构成的多边形。凸包的每一处都是凸的,即在凸包内连接任意两点 的直线都在凸包的内部。在凸包内,任意连续三个点的内角小于 180°。
例如,在图 12-25 中,最外层的多边形为机械手的凸包,在机械手边缘与凸包之间的部分被称为凸缺陷(Convexity Defect),凸缺陷能够用来处理手势识别等问题。
获取凸包
OpenCV 提供函数 cv2.convexHull()
用于获取轮廓的凸包。该函数的语法格式为:
hull = cv2.convexHull( points[, clockwise[, returnPoints]] )
式中的返回值 hull 为凸包角点。
式中的参数如下:
- points:轮廓。
- clockwise:布尔型值。该值为 True 时,凸包角点将按顺时针方向排列;该值为 False 时,则以逆时针方向排列凸包角点。
- returnPoints:布尔型值。默认值是 True,函数返回凸包角点的 x/y 轴坐标;当为 False时,函数返回轮廓中凸包角点的索引。
示例:观察函数 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)
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 270]]
[[195 383]]
[[ 79 383]]
[[ 79 270]]]
returnPoints 为 False 时返回值 hull 的值:
[[3]
[2]
[1]
[0]]
从程序运行结果可以看出,函数 cv2.convexHull()的可选参数returnPoints:
- 为默认值 True 时,函数返回凸包角点的 x/y 轴坐标,本例中返回了 4 个轮廓的坐标值。
- 为 False 时,函数返回轮廓中凸包角点的索引,本例中返回了 4 个轮廓的索引值。
示例2:使用函数 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)
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()
凸缺陷
凸包与轮廓之间的部分,称为凸缺陷。在 OpenCV 中使用函数 cv2.convexityDefects()
获取凸缺陷。其语法格式如下:
convexityDefects = cv2.convexityDefects( contour, convexhull )
式中的返回值 convexityDefects 为凸缺陷点集。它是一个数组,每一行包含的值是[起点,终点,轮廓上距离凸包最远的点,最远点到凸包的近似距离]。
需要注意的是,返回结果中[起点,终点,轮廓上距离凸包最远的点
,最远点到凸包的近似距离]的前三个值是轮廓点的索引,所以需要到轮廓点中去找它们。
式中的参数如下:
- contour 是轮廓。
- convexhull 是凸包。
需要注意的是,用 cv2.convexityDefects()计算凸缺陷时,要使用凸包作为参数。在查找该凸包时,所使用函数 cv2.convexHull()的参数 returnPoints 的值必须是 False。
为了让大家更直观地观察凸缺陷点集,我们尝试将凸缺陷点集在一幅图内显示出来。实现方式为,将起点和终点用一条线连接,在最远点画一个圆圈。下面我们通过一个例子来展示上述操作。
示例2:使用函数 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)
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=
[[[ 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]]
[[ 305 311 306 114]]
[[ 311 385 342 13666]]
[[ 385 389 386 395]]
[[ 389 489 435 20327]]]
判断轮廓是否是凸形的
在 OpenCV 中,可以用函数 cv2.isContourConvex()来判断轮廓是否是凸形的,其语法格式为:
retval = cv2.isContourConvex( contour )
式中:
- 返回值 retval 是布尔型值。该值为 True 时,表示轮廓为凸形的;否则,不是凸形的。
- 参数 contour 为要判断的轮廓。
示例:使用函数 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)
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()
运行结果:
使用函数 cv2.convexHull()构造的多边形是否是凸形的: True
使用函数 cv2.approxPolyDP()构造的多边形是否是凸形的: False
从以上运行结果可以看出:
- 使用函数 cv2.convexHull()构造凸包后,对绘制的凸包使用函数 cv2.isContourConvex()判断,返回值为 True,说明该轮廓是凸形的。
- 使用函数 cv2.approxPolyDP()构造逼近多边形后,对绘制的逼近多边形使用函数cv2.isContourConvex()判断,返回值为 False,说明该轮廓(多边形)不是凸形的。
实验原图: