前文链接:OpenCV学习笔记(九)——图像轮廓(上)

    4. 轮廓拟合

    在计算轮廓时,可能并不需要实际的轮廓,而仅需要一个接近于轮廓的近似多边形。OpenCV 提供了多种计算轮廓近似多边形的方法。

    4.1 矩形包围框

    x,y,w,h = cv2.boundingRect( array )

    • 返回矩形边界左上角顶点的x坐标,y坐标,矩形宽度和高度。
    • 参数 array 是灰度图像或轮廓。

    import cv2
    import numpy as np
    #---------------读取并显示原始图像------------------
    o = cv2.imread('cc.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)
    #---------------构造矩形边界------------------
    x,y,w,h = cv2.boundingRect(contours[0])
    brcnt = np.array([[[x, y]], [[x+w, y]], [[x+w, y+h]], [[x, y+h]]])
    cv2.drawContours(o, [brcnt], -1, (255, 255,255), 2)
    #---------------显示矩形边界------------------
    cv2.imshow("result",o)
    cv2.waitKey()
    cv2.destroyAllWindows()

    在这里插入图片描述

    4.2 最小包围矩形框

    retval =cv2.minAreaRect( points )

    返回值 retval 表示返回的矩形特征信息。该值的结构是(最小外接矩形的中心(x,y),(宽度,高度),旋转角度)。
    参数 points 是轮廓。
    需要注意,返回值retval的结构不符合函数cv2.drawContours()的参数结构要求。因此,必须将其转换为符合要求的结构,才能使用。函数cv2.boxPoints()能够将上述返回值retval转换为符合要求的结构。函数cv2.boxPoints()的语法格式是:
    points = cv2.boxPoints( box )

    • 返回值 points,是能够用于函数cv2.drawContours()参数的轮廓点。
    • 参数 box 是函数cv2.minAreaRect()返回值的类型的值。

    import cv2
    import numpy as np
    o = cv2.imread('cc.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)
    rect = cv2.minAreaRect(contours[0])
    print("返回值rect:\n",rect)
    points = cv2.boxPoints(rect)
    print("\n 转换后的points:\n",points)
    points = np.int0(points) #取整
    image=cv2.drawContours(o,[points],0,(255,255,255),2)
    cv2.imshow("result",o)
    cv2.waitKey()
    cv2.destroyAllWindows()

    同时,程序还会输出如下结果:
    返回值 rect:
    ((280.3699951171875, 138.58999633789062), (154.99778747558594,
    63.78103256225586), -8.130102157592773)
    转换后的 points:
    [[208.16002 181.12 ]
    [199.14 117.979996]
    [352.57996 96.06 ]
    [361.59998 159.2 ]]

    在这里插入图片描述

    4.3 最小包围圆形

    center, radius = cv2.minEnclosingCircle( points )

    • 返回值 center 是最小包围圆形的中心。
    • 返回值 radius 是最小包围圆形的半径。
    • 参数 points 是轮廓。

    import cv2
    o = cv2.imread('cc.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)
    (x,y),radius = cv2.minEnclosingCircle(contours[0])
    center = (int(x),int(y))
    radius = int(radius)
    cv2.circle(o,center,radius,(255,255,255),2)
    cv2.imshow("result",o)
    cv2.waitKey()
    cv2.destroyAllWindows()

    在这里插入图片描述

    4.4 最优拟合椭圆

    retval = cv2.fitEllipse( points )

    • 返回值 retval 是RotatedRect 类型的值。这是因为该函数返回的是拟合椭圆的外接矩形,retval 包含外接矩形的质心、宽、高、旋转角度等参数信息,这些信息正好与椭圆的中心点、轴长度、旋转角度等信息吻合。
    • 参数 points 是轮廓。

    import cv2
    o = cv2.imread('cc.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_LIST,cv2.CHAIN_APPROX_SIMPLE)
    cv2.imshow("original",o)
    ellipse = cv2.fitEllipse(contours[0])
    print("ellipse=",ellipse)
    cv2.ellipse(o,ellipse,(0,255,0),3)
    cv2.imshow("result",o)
    cv2.waitKey()
    cv2.destroyAllWindows()

    在这里插入图片描述

    4.5 最优拟合直线

    line = cv2.fitLine( points, distType, param, reps, aeps )

    • points:轮廓。
    • distType:距离类型。拟合直线时,要使输入点到拟合直线的距离之和最小,其类型如表所示。

    在这里插入图片描述

    • param:距离参数,与所选的距离类型有关。当此参数被设置为0 时,该函数会自动选择最优值。
    • reps:用于表示拟合直线所需要的径向精度,通常该值被设定为0.01。
    • aeps:用于表示拟合直线所需要的角度精度,通常该值被设定为0.01。

    import cv2
    o = cv2.imread('cc.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)
    [vx,vy,x,y] = cv2.fitLine(contours[0], cv2.DIST_L2,0,0.01,0.01)
    lefty = int((-x*vy/vx) + y)
    righty = int(((cols-x)*vy/vx)+y)
    cv2.line(o,(cols-1,righty),(0,lefty),(0,255,0),2)
    cv2.imshow("result",o)
    cv2.waitKey()
    cv2.destroyAllWindows()

    4.6 最小外包三角形

    retval, triangle = cv2.minEnclosingTriangle( points )

    • retval:最小外包三角形的面积。
    • triangle:最小外包三角形的三个顶点集。
    • points:轮廓。

    import cv2
    o = cv2.imread('cc.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)
    area,trgl = cv2.minEnclosingTriangle(contours[0])
    print("area=",area)
    print("trgl:",trgl)
    for i in range(0, 3):
        cv2.line(o, tuple(trgl[i][0]),tuple(trgl[(i + 1) % 3][0]), (255,255,255), 2)
    cv2.imshow("result",o)
    cv2.waitKey()
    cv2.destroyAllWindows()

    显示如下的运行结果:
    area= 12904.00390625
    trgl: [[[193.25641 107. ]]
    [[222.58975 211. ]]
    [[441.41025 107. ]]]

    在这里插入图片描述

    4.7 逼近多边形

    approxCurve = cv2.approxPolyDP( curve, epsilon, closed )

    approxCurve 为逼近多边形的点集。
    curve 是轮廓。
    epsilon 为精度,原始轮廓的边界点与逼近多边形边界之间的最大距离。
    closed 是布尔型值。该值为True 时,逼近多边形是封闭的;否则,逼近多边形是不封闭的。
    函数 cv2.approxPolyDP()采用的是Douglas-Peucker 算法(DP 算法)。以下图为例,该算法首先从轮廓中找到距离最远的两个点,并将两点相连(见(b)图)。接下来,在轮廓上找到一个离当前直线最远的点,并将该点与原有直线连成一个封闭的多边形,此时得到一个三角形,如(c)图所示。

    将上述过程不断地迭代,将新找到的距离当前多边形最远的点加入到结果中。当轮廓上所有的点到当前多边形的距离都小于函数cv2.approxPolyDP()的参数epsilon 的值时,就停止迭代。最终可以得到如图12-23 的(f)图所示的处理结果。

    通过上述过程可知,epsilon 是逼近多边形的精度信息。通常情况下,将该精度设置为多边形总长度的百分比形式。
    在这里插入图片描述

    import cv2
    #----------------读取并显示原始图像-------------------------------
    o = cv2.imread('cc.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)
    #----------------epsilon=0.1*周长-------------------------------
    adp = o.copy()
    epsilon = 0.1*cv2.arcLength(contours[0],True)
    approx = cv2.approxPolyDP(contours[0],epsilon,True)
    adp=cv2.drawContours(adp,[approx],0,(0,0,255),2)
    cv2.imshow("result0.1",adp)
    #----------------epsilon=0.09*周长-------------------------------
    adp = o.copy()
    epsilon = 0.09*cv2.arcLength(contours[0],True)
    approx = cv2.approxPolyDP(contours[0],epsilon,True)
    adp=cv2.drawContours(adp,[approx],0,(0,0,255),2)
    cv2.imshow("result0.09",adp)
    #----------------epsilon=0.055*周长-------------------------------
    adp = o.copy()
    epsilon = 0.055*cv2.arcLength(contours[0],True)
    approx = cv2.approxPolyDP(contours[0],epsilon,True)
    adp=cv2.drawContours(adp,[approx],0,(0,0,255),2)
    cv2.imshow("result0.055",adp)
    #----------------epsilon=0.05*周长-------------------------------
    adp = o.copy()
    epsilon = 0.05*cv2.arcLength(contours[0],True)
    approx = cv2.approxPolyDP(contours[0],epsilon,True)
    adp=cv2.drawContours(adp,[approx],0,(0,0,255),2)
    cv2.imshow("result0.05",adp)
    #----------------epsilon=0.02*周长-------------------------------
    adp = o.copy()
    epsilon = 0.02*cv2.arcLength(contours[0],True)
    approx = cv2.approxPolyDP(contours[0],epsilon,True)
    adp=cv2.drawContours(adp,[approx],0,(0,0,255),2)
    cv2.imshow("result0.02",adp)
    #----------------等待释放窗口-------------------------------
    cv2.waitKey()
    cv2.destroyAllWindows()

    在这里插入图片描述

    5. 凸包

    逼近多边形是轮廓的高度近似,但是有时候,我们希望使用一个多边形的凸包来简化它。凸包跟逼近多边形很像,只不过它是物体最外层的“凸”多边形。凸包指的是完全包含原有轮廓,并且仅由轮廓上的点所构成的多边形。凸包的每一处都是凸的,即在凸包内连接任意两点的直线都在凸包的内部。在凸包内,任意连续三个点的内角小于180°。
    在这里插入图片描述

    5.1 获取凸包

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

    hull:凸包角点。
    points:轮廓。
    clockwise:布尔型值。该值为True 时,凸包角点将按顺时针方向排列;该值为False 时,则以逆时针方向排列凸包角点。
    returnPoints:布尔型值。默认值是True,函数返回凸包角点的x/y 轴坐标;当为False时,函数返回轮廓中凸包角点的索引。
    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()

    在这里插入图片描述

    5.2 凸缺陷

    凸包与轮廓之间的部分,称为凸缺陷。

    convexityDefects = cv2.convexityDefects( contour, convexhull )

    convexityDefects 为凸缺陷点集。它是一个数组,每一行包含的值是[起点,终点,轮廓上距离凸包最远的点,最远点到凸包的近似距离]。需要注意的是,返回结果中[起点,终点,轮廓上距离凸包最远的点,最远点到凸包的近似距离]的前三个值是轮廓点的索引,所以需要到轮廓点中去找它们。起点和终点为轮廓上最远点对应的凸包直线的起点和终点。
    contour 是轮廓。
    convexhull 是凸包。需要注意的是,cv2.convexityDefects()计算凸缺陷时,要使用凸包作为参数。在查找该凸包时,所使用函数cv2.convexHull()的参数returnPoints的值必须是False。
    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()

    在这里插入图片描述

    5.3 几何学测试

    5.3.1 测试轮廓是否是凸形的

    retval = cv2.isContourConvex( contour )

    • 返回值 retval 是布尔型值。该值为True 时,表示轮廓为凸形的;否则,不是凸形的。
    • 参数 contour 为要判断的轮廓。
    5.3.2 点到轮廓的距离

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

    contour 为轮廓。
    pt 为待判定的点。
    measureDist 为布尔型值,表示距离的判定方式。
    1.当值为 True 时,表示计算点到轮廓的距离。如果点在轮廓的外部,返回值为负数;如果点在轮廓上,返回值为0;如果点在轮廓内部,返回值为正数。
    2.当值为 False 时,不计算距离,只返回“-1”、“0”和“1”中的一个值,表示点相对于轮廓的位置关系。如果点在轮廓的外部,返回值为“-1”;如果点在轮廓上,返回值为“0”;如果点在轮廓内部,返回值为“1”。

    6-7 利用形状场景算法比较轮廓及轮廓的特征值

    OpenCV学习笔记(九)——图像轮廓(下)