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 凸包_第1张图片

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 凸包_第2张图片win10+Python3.7.3+OpenCV3.4.1入门学习(十二 图像轮廓)————12.6 凸包_第3张图片

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 凸包_第4张图片win10+Python3.7.3+OpenCV3.4.1入门学习(十二 图像轮廓)————12.6 凸包_第5张图片

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 凸包_第6张图片win10+Python3.7.3+OpenCV3.4.1入门学习(十二 图像轮廓)————12.6 凸包_第7张图片win10+Python3.7.3+OpenCV3.4.1入门学习(十二 图像轮廓)————12.6 凸包_第8张图片
同时,程序还会显示如下的结果:

使用函数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 凸包_第9张图片win10+Python3.7.3+OpenCV3.4.1入门学习(十二 图像轮廓)————12.6 凸包_第10张图片
同时,程序还会显示如下的结果:

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 凸包_第11张图片win10+Python3.7.3+OpenCV3.4.1入门学习(十二 图像轮廓)————12.6 凸包_第12张图片
同时,程序还会显示如下的运行结果:

distA= 1.0
distB= -1.0
distC= 0.0

从以上结果可以看出,
● A点算出来的关系值为“1”,说明该点在轮廓的内部。
● B点算出来的关系值为“-1”,说明该点在轮廓的外部。
● C点算出来的关系值为零值,说明该点在轮廓上。

你可能感兴趣的:(Python-OpenCV,Python-OpenCV,图像处理,凸包)