• [OpenCV-Python] OpenCV 中的图像处理 部分 IV (四)


    部分 IV
    OpenCV 中的图像处理

    OpenCV-Python 中文教程(搬运)目录

    21 OpenCV 中的轮廓


    21.1 初识轮廓
    目标
      • 理解什么是轮廓
      • 学习找轮廓,绘制轮廓等
      • 函数:cv2.findContours(),cv2.drawContours()


    21.1.1 什么是轮廓
      轮廓可以简单认为成将连续的点(连着边界)连在一起的曲线,具有相同、的颜色或者灰度。轮廓在形状分析和物体的检测和识别中很有用。
      • 为了更加准确,要使用二值化图像。在寻找轮廓之前,要进行阈值化处理、或者 Canny 边界检测。
      • 查找轮廓的函数会修改原始图像。如果你在找到轮廓之后还想使用原始图、像的话,你应该将原始图像存储到其他变量中。
      • 在 OpenCV 中,查找轮廓就像在黑色背景中超白色物体。你应该记住,、要找的物体应该是白色而背景应该是黑色。
    让我们看看如何在一个二值图像中查找轮廓:
      函数 cv2.findContours() 有三个参数,第一个是输入图像,第二个是轮廓检索模式,第三个是轮廓近似方法。返回值有三个,第一个是图像,第二个是轮廓,第三个是(轮廓的)层析结构。轮廓(第二个返回值)是一个 Python列表,其中存储这图像中的所有轮廓。每一个轮廓都是一个 Numpy 数组,包含对象边界点(x,y)的坐标。
    注意:我们后边会对第二和第三个参数,以及层次结构进行详细介绍。在那之前,例子中使用的参数值对所有图像都是适用的。


    21.1.2 怎样绘制轮廓
      函数 cv2.drawContours() 可以被用来绘制轮廓。它可以根据你提供的边界点绘制任何形状。它的第一个参数是原始图像,第二个参数是轮廓,一个 Python 列表。第三个参数是轮廓的索引(在绘制独立轮廓是很有用,当设置为 -1 时绘制所有轮廓)。接下来的参数是轮廓的颜色和厚度等。
    在一幅图像上绘制所有的轮廓:

    import numpy as np
    import cv2
    im = cv2.imread('test.jpg')
    imgray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
    ret,thresh = cv2.threshold(imgray,127,255,0)
    image, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
    绘制独立轮廓,如第四个轮廓:
    img = cv2.drawContour(img, contours, -1, (0,255,0), 3)
    但是大多数时候,下面的方法更有用:
    img = cv2.drawContours(img, contours, 3, (0,255,0), 3)

    注意:最后这两种方法结果是一样的,但是后边的知识会告诉你最后一种方法更有用。


    21.1.3 轮廓的近似方法
      这是函数 cv2.findCountours() 的第三个参数。它到底代表什么意思呢?
    上边我们已经提到轮廓是一个形状具有相同灰度值的边界。它会存贮形状边界上所有的 (x,y) 坐标。但是需要将所有的这些边界点都存储吗?这就是这个参数要告诉函数 cv2.findContours 的。
    这个参数如果被设置为 cv2.CHAIN_APPROX_NONE,所有的边界点都会被存储。但是我们真的需要这么多点吗?例如,当我们找的边界是一条直线时。你用需要直线上所有的点来表示直线吗?不是的,我们只需要这条直线的两个端点而已。这就是 cv2.CHAIN_APPROX_SIMPLE 要做的。它会将轮廓上的冗余点都去掉,压缩轮廓,从而节省内存开支。我们用下图中的矩形来演示这个技术。在轮廓列表中的每一个坐标上画一个蓝色圆圈。第一个图显示使用 cv2.CHAIN_APPROX_NONE 的效果,一共 734 个点。第二个图是使用 cv2.CHAIN_APPROX_SIMPLE 的结果,只有 4 个点。看到他的威力了吧!

         Contour Retrieval Method

    21.2 轮廓特征


    目标
      • 查找轮廓的不同特征,例如面积,周长,重心,边界框等。
      • 你会学到很多轮廓相关函数
    21.2.1 矩
      图像的矩可以帮助我们计算图像的质心,面积等。详细信息请查看维基百科Image Moments。
    函数 cv2.moments() 会将计算得到的矩以一个字典的形式返回。如下:

    import cv2
    import numpy as np
    
    img = cv2.imread('star.jpg',0)
    ret,thresh = cv2.threshold(img,127,255,0)
    contours,hierarchy = cv2.findContours(thresh, 1, 2)
    
    cnt = contours[0]
    M = cv2.moments(cnt)
    print M

    根据这些矩的值,我们可以计算出对象的重心: C_x = frac{M_{10}}{M_{00}}   和    C_y = frac{M_{01}}{M_{00}}. 。

    #This can be done as follows:
    cx = int(M['m10']/M['m00'])
    cy = int(M['m01']/M['m00'])

    21.2.2 轮廓面积
      轮廓的面积可以使用函数 cv2.contourArea() 计算得到,也可以使用矩(0 阶矩),M['m00']。

    area = cv2.contourArea(cnt)


    21.2.3 轮廓周长
      也被称为弧长。可以使用函数 cv2.arcLength() 计算得到。这个函数的第二参数可以用来指定对象的形状是闭合的(True),还是打开的(一条曲线)。

    perimeter = cv2.arcLength(cnt,True)


    21.2.4 轮廓近似
      将轮廓形状近似到另外一种由更少点组成的轮廓形状,新轮廓的点的数目由我们设定的准确度来决定。使用的Douglas-Peucker算法,你可以到维基百科获得更多此算法的细节。
    为了帮助理解,假设我们要在一幅图像中查找一个矩形,但是由于图像的种种原因,我们不能得到一个完美的矩形,而是一个“坏形状”(如下图所示)。
    现在你就可以使用这个函数来近似这个形状()了。这个函数的第二个参数叫epsilon,它是从原始轮廓到近似轮廓的最大距离。它是一个准确度参数。选择一个好的 epsilon 对于得到满意结果非常重要。

    epsilon = 0.1*cv2.arcLength(cnt,True)
    approx = cv2.approxPolyDP(cnt,epsilon,True)

    下边,第二幅图中的绿线是当 epsilon = 10% 时得到的近似轮廓,第三幅图是当 epsilon = 1% 时得到的近似轮廓。第三个参数设定弧线是否闭合。

        Contour Approximation

    21.2.5 凸包
      凸包与轮廓近似相似,但不同,虽然有些情况下它们给出的结果是一样的。
      函数 cv2.convexHull() 可以用来检测一个曲线是否具有凸性缺陷,并能纠正缺陷。一般来说,凸性曲线总是凸出来的,至少是平的。如果有地方凹进去了就被叫做凸性缺陷。例如下图中的手。红色曲线显示了手的凸包,凸性缺陷被双箭头标出来了。

        Convex Hull
    关于他的语法还有一些需要交代:

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

    参数:
      • points 我们要传入的轮廓
      • hull 输出,通常不需要
      • clockwise 方向标志。如果设置为 True,输出的凸包是顺时针方向的。否则为逆时针方向。
      • returnPoints 默认值为 True。它会返回凸包上点的坐标。如果设置为 False,就会返回与凸包点对应的轮廓上的点。
    要获得上图的凸包,下面的命令就够了:

    hull = cv2.convexHull(cnt)

    但是如果你想获得凸性缺陷,需要把 returnPoints 设置为 False。以上面的矩形为例,首先我们找到他的轮廓 cnt。现在我把 returnPoints 设置为 True 查找凸包,我得到下列值:
    [[[234 202]], [[ 51 202]], [[ 51 79]], [[234 79]]],其实就是矩形的四个角点。
    现在把 returnPoints 设置为 False,我得到的结果是[[129],[ 67],[ 0],[142]]。他们是轮廓点的索引。例如:cnt[129] = [[234,202]],这与前面我们得到结果的第一个值是一样的。

    在凸检验中你我们还会遇到这些。


    21.2.6 凸性检测
      函数 cv2.isContourConvex() 可以可以用来检测一个曲线是不是凸的。它只能返回 True 或 False。没什么大不了的。

    k = cv2.isContourConvex(cnt)

    21.2.7 边界矩形
      有两类边界矩形。
      直边界矩形 一个直矩形(就是没有旋转的矩形)。它不会考虑对象是否旋转。所以边界矩形的面积不是最小的。可以使用函数 cv2.boundingRect() 查找得到。
    (x,y)为矩形左上角的坐标,(w,h)是矩形的宽和高。

    x,y,w,h = cv2.boundingRect(cnt)
    img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)


    旋转的边界矩形 这个边界矩形是面积最小的,因为它考虑了对象的旋转。用到的函数为 cv2.minAreaRect()。返回的是一个 Box2D 结构,其中包含矩形左上角角点的坐标(x,y),矩形的宽和高(w,h),以及旋转角度。但是要绘制这个矩形需要矩形的 4 个角点,可以通过函数 cv2.boxPoints() 获得。

    rect = cv2.minAreaRect(cnt)
    box = cv2.boxPoints(rect)
    box = np.int0(box)
    im = cv2.drawContours(im,[box],0,(0,0,255),2)

    把这两中边界矩形显示在下图中,其中绿色的为直矩形,红的为旋转矩形。

        Bounding Rectangle


    21.2.8 最小外接圆
      函数 cv2.minEnclosingCircle() 可以帮我们找到一个对象的外切圆。
      它是所有能够包括对象的圆中面积最小的一个。

    (x,y),radius = cv2.minEnclosingCircle(cnt)
    center = (int(x),int(y))
    radius = int(radius)
    img = cv2.circle(img,center,radius,(0,255,0),2)

        Minimum Enclosing Circle


    21.2.9 椭圆拟合
      使用的函数为 cv2.ellipse(),返回值其实就是旋转边界矩形的内切圆。

    ellipse = cv2.fitEllipse(cnt)
    im = cv2.ellipse(im,ellipse,(0,255,0),2)

        Fitting an Ellipse


    21.2.10 直线拟合
      我们可以根据一组点拟合出一条直线,同样我们也可以为图像中的白色点拟合出一条直线。

    rows,cols = img.shape[:2]
    #cv2.fitLine(points, distType, param, reps, aeps[, line ]) → line
    #points – Input vector of 2D or 3D points, stored in std::vector<> or Mat.
    #line – Output line parameters. In case of 2D fitting, it should be a vector of
    #4 elements (likeVec4f) - (vx, vy, x0, y0), where (vx, vy) is a normalized
    #vector collinear to the line and (x0, y0) is a point on the line. In case of
    #3D fitting, it should be a vector of 6 elements (like Vec6f) - (vx, vy, vz,
    #x0, y0, z0), where (vx, vy, vz) is a normalized vector collinear to the line
    #and (x0, y0, z0) is a point on the line.
    #distType – Distance used by the M-estimator
    #distType=CV_DIST_L2
    #ρ(r) = r2 /2 (the simplest and the fastest least-squares method)
    #param – Numerical parameter ( C ) for some types of distances. If it is 0, an optimal value
    #is chosen.
    #reps – Sufficient accuracy for the radius (distance between the coordinate origin and the
    #line).
    #aeps – Sufficient accuracy for the angle. 0.01 would be a good default value for reps and
    #aeps.
    [vx,vy,x,y] = cv2.fitLine(cnt, cv2.DIST_L2,0,0.01,0.01)
    lefty = int((-x*vy/vx) + y)
    righty = int(((cols-x)*vy/vx)+y)
    img = cv2.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)

        Fitting a Line

    21.3 轮廓的性质


      本小节我们将要学习提取一些经常使用的对象特征。你可以在Matlab regionprops documentation 更多的图像特征。


    21.3.1 长宽比
      边界矩形的宽高比
          Aspect ; Ratio = frac{Width}{Height}

    x,y,w,h = cv2.boundingRect(cnt)
    aspect_ratio = float(w)/h


    21.3.2 Extent
      轮廓面积与边界矩形面积的比。
          Extent = frac{Object ; Area}{Bounding ; Rectangle ; Area}

    area = cv2.contourArea(cnt)
    x,y,w,h = cv2.boundingRect(cnt)
    rect_area = w*h
    extent = float(area)/rect_area


    21.3.3 Solidity
      轮廓面积与凸包面积的比。
          Solidity = frac{Contour ; Area}{Convex ; Hull ; Area}

    area = cv2.contourArea(cnt)
    hull = cv2.convexHull(cnt)
    hull_area = cv2.contourArea(hull)
    solidity = float(area)/hull_area


    21.3.4 Equivalent Diameter
      与轮廓面积相等的圆形的直径
          Equivalent ; Diameter = sqrt{frac{4 	imes Contour ; Area}{pi}}

    area = cv2.contourArea(cnt)
    equi_diameter = np.sqrt(4*area/np.pi)


    21.3.5 方向
      对象的方向,下面的方法还会返回长轴和短轴的长度

    (x,y),(MA,ma),angle = cv2.fitEllipse(cnt)


    21.3.6 掩模和像素点
      有时我们需要构成对象的所有像素点,我们可以这样做:

    mask = np.zeros(imgray.shape,np.uint8)
    # 这里一定要使用参数 -1, 绘制填充的的轮廓
    cv2.drawContours(mask,[cnt],0,255,-1)
    #Returns a tuple of arrays, one for each dimension of a,
    #containing the indices of the non-zero elements in that dimension.
    #The result of this is always a 2-D array, with a row for
    #each non-zero element.
    #To group the indices by element, rather than dimension, use:
    #transpose(nonzero(a))
    #>>> x = np.eye(3)
    #>>> x
    #array([[ 1., 0., 0.],
    # [ 0., 1., 0.],
    # [ 0., 0., 1.]])
    #>>> np.nonzero(x)
    #(array([0, 1, 2]), array([0, 1, 2]))
    #>>> x[np.nonzero(x)]
    #array([ 1., 1., 1.])
    #>>> np.transpose(np.nonzero(x))
    #array([[0, 0],
    # [1, 1],
    # [2, 2]])
    pixelpoints = np.transpose(np.nonzero(mask))
    #pixelpoints = cv2.findNonZero(mask)
    # 官方代码
    mask = np.zeros(imgray.shape,np.uint8)
    cv2.drawContours(mask,[cnt],0,255,-1)
    pixelpoints = np.transpose(np.nonzero(mask))
    #pixelpoints = cv2.findNonZero(mask)

    这里我们是用来两种方法,第一种方法使用了 Numpy 函数,第二种使用了 OpenCV 函数。结果相同,但还是有点不同。Numpy 给出的坐标是 (row ,colum )
    形式的。而 OpenCV 给出的格式是 (x ,y )形式的。所以这两个结果基本是可以互换的。row=x,colunm=y。


    21.3.7 最大值和最小值及它们的位置
      我们可以使用掩模图像得到这些参数。

    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(imgray,mask = mask)


    21.3.8 平均颜色及平均灰度
      我们也可以使用相同的掩模求一个对象的平均颜色或平均灰度

    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(imgray,mask = mask)


    21.3.9 极点
      一个对象最上面,最下面,最左边,最右边的点。

    leftmost = tuple(cnt[cnt[:,:,0].argmin()][0])
    rightmost = tuple(cnt[cnt[:,:,0].argmax()][0])
    topmost = tuple(cnt[cnt[:,:,1].argmin()][0])
    bottommost = tuple(cnt[cnt[:,:,1].argmax()][0]

    如下图所示:

        Extreme Points

    练习
    1. Matlab regionprops 文档中还有一些图像特征我们在这里没有讲到,你可以尝试着使用 Python 和 OpenCV 来实现他们。


    21.4 轮廓:更多函数


    目标
    我们要学习
      • 凸缺陷,以及如何找凸缺陷
      • 找某一点到一个多边形的最短距离
      • 不同形状的匹配
    原理与代码


    21.4.1 凸缺陷
      前面我们已经学习了轮廓的凸包,对象上的任何凹陷都被成为凸缺陷。OpenCV 中有一个函数 cv.convexityDefect() 可以帮助我们找到凸缺陷。函数调用如下:

    hull = cv2.convexHull(cnt,returnPoints = False)
    defects = cv2.convexityDefects(cnt,hull)

    注意:如果要查找凸缺陷,在使用函数 cv2.convexHull 找凸包时,参数returnPoints 一定要是 False。
    它会返回一个数组,其中每一行包含的值是 [起点,终点,最远的点,到最远点的近似距离]。我们可以在一张图上显示它。我们将起点和终点用一条绿线连接,在最远点画一个圆圈,要记住的是返回结果的前三个值是轮廓点的索引。
    所以我们还要到轮廓点中去找它们。

    import cv2
    import numpy as np
    
    img = cv2.imread('star.jpg')
    img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    ret, thresh = cv2.threshold(img_gray, 127, 255,0)
    contours,hierarchy = cv2.findContours(thresh,2,1)
    cnt = contours[0]
    
    hull = cv2.convexHull(cnt,returnPoints = False)
    defects = cv2.convexityDefects(cnt,hull)
    
    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,255,0],2)
        cv2.circle(img,far,5,[0,0,255],-1)
    
    cv2.imshow('img',img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

    结果如下:

        Convexity Defects


    21.4.2 Point Polygon Test
      求解图像中的一个点到一个对象轮廓的最短距离。如果点在轮廓的外部,
    返回值为负。如果在轮廓上,返回值为 0。如果在轮廓内部,返回值为正。

    下面我们以点(50,50)为例:

    dist = cv2.pointPolygonTest(cnt,(50,50),True)

    此函数的第三个参数是 measureDist。如果设置为 True,就会计算最短距离。如果是 False,只会判断这个点与轮廓之间的位置关系(返回值为+1,-1,0)。

    注意:如果你不需要知道具体距离,建议你将第三个参数设为 False,这样速度会提高 2 到 3 倍。


    21.4.3 形状匹配
      函数 cv2.matchShape() 可以帮我们比较两个形状或轮廓的相似度。如果返回值越小,匹配越好。它是根据 Hu 矩来计算的。文档中对不同的方法都有解释。
    我们试着将下面的图形进行比较:

    import cv2
    import numpy as np
    
    img1 = cv2.imread('star.jpg',0)
    img2 = cv2.imread('star2.jpg',0)
    
    ret, thresh = cv2.threshold(img1, 127, 255,0)
    ret, thresh2 = cv2.threshold(img2, 127, 255,0)
    contours,hierarchy = cv2.findContours(thresh,2,1)
    cnt1 = contours[0]
    contours,hierarchy = cv2.findContours(thresh2,2,1)
    cnt2 = contours[0]
    
    ret = cv2.matchShapes(cnt1,cnt2,1,0.0)
    print ret

    我得到的结果是:
      • A 与自己匹配 0.0
      • A 与 B 匹配 0.001946
      • A 与 C 匹配 0.326911

    看见了吗,及时发生了旋转对匹配的结果影响也不是非常大。
    注意:Hu 矩是归一化中心矩的线性组合,之所以这样做是为了能够获取代表图像的某个特征的矩函数,这些矩函数对某些变化如缩放,旋转,镜像映射(除了 h1)具有不变形。此段摘自《学习 OpenCV》中文版。

    练习
    1. 创建一个小程序,可以将图片上的点绘制成不同的颜色,颜色是根据这个
    点到轮廓的距离来决定的。要使用的函数:cv2.pointPolygonTest()。
    2. 使用函数 cv2.matchShapes() 匹配带有字母或者数字的图片。


    21.5 轮廓的层次结构


    目标
      现在我们要学习轮廓的层次结构了,比如轮廓之间的父子关系。


    原理
      在前面的内容中我们使用函数 cv2.findContours 来查找轮廓,我们需要传入一个参数:轮廓提取模式(Contour_Retrieval_Mode)。我们总是把它设置为 cv2.RETR_LIST 或者是 cv2.RETR_TREE,效果还可以。但是它们到底代表什么呢?
      同时,我们得到的结果包含 3 个数组,第一个图像,第二个是轮廓,第三个是层次结构。但是我们从来没有用过层次结构。层次结构是用来干嘛的呢?
      层次结构与轮廓提取模式有什么关系呢?
      这就是我们本节要讲的。


    21.5.1 什么是层次结构
      通常我们使用函数 cv2.findContours 在图片中查找一个对象。有时对象可能位于不同的位置。还有些情况,一个形状在另外一个形状的内部。这种情况下我们称外部的形状为父,内部的形状为子。按照这种方式分类,一幅图像中的所有轮廓之间就建立父子关系。这样我们就可以确定一个轮廓与其他轮廓是怎样连接的,比如它是不是某个轮廓的子轮廓,或者是父轮廓。这种关系就成为组织结构
    下图就是一个简单的例子:

        Hierarchy Representation
    在这幅图像中,我给这几个形状编号为 0-5。2 和 2a 分别代表最外边矩形的外轮廓和内轮廓。
    在这里边轮廓 0,1,2 在外部或最外边。我们可以称他们为(组织结构)0 级,简单来说就是他们属于同一级。

    接下来轮廓 2a。我们把它当成轮廓 2 的子轮廓。它就成为(组织结构)第1 级。同样轮廓 3 是轮廓 2 的子轮廓,成为(组织结构)第 3 级。最后轮廓4,5 是轮廓 3a 的子轮廓,成为(组织结构)4 级(最后一级)。按照这种方式给这些形状编号,我们可以说轮廓 4 是轮廓 3a 的子轮廓(当然轮廓 5 也是)。
    我说这么多就是为了解释层次结构,外轮廓,子轮廓,父轮廓,子轮廓等。
    现在让我们进入 OpenCV 吧。


    21.5.2 OpenCV 中层次结构
      不管层次结构是什么样的,每一个轮廓都包含自己的信息:谁是父,谁是子等。OpenCV 使用一个含有四个元素的数组表示。[Next ,Previous ,First_Child ,Parent]。
    Next 表示同一级组织结构中的下一个轮廓。
    以上图中的轮廓 0 为例,轮廓 1 就是他的 Next。同样,轮廓 1 的 Next是 2,Next=2。
    那轮廓 2 呢?在同一级没有 Next。这时 Next=-1。而轮廓 4 的 Next为 5,所以它的 Next=5。
      Previous 表示同一级结构中的前一个轮廓。
    与前面一样,轮廓 1 的 Previous 为轮廓 0,轮廓 2 的 Previous 为轮廓 1。轮廓 0 没有 Previous,所以 Previous=-1。
      First_Child 表示它的第一个子轮廓。
    没有必要再解释了,轮廓 2 的子轮廓为 2a。所以它的 First_Child 为2a。那轮廓 3a 呢?它有两个子轮廓。但是我们只要第一个子轮廓,所以是轮廓 4(按照从上往下,从左往右的顺序排序)。
      Parent 表示它的父轮廓。
    与 First_Child 刚好相反。轮廓 4 和 5 的父轮廓是轮廓 3a。而轮廓 3a的父轮廓是 3。
    注意:如果没有父或子,就为 -1。
    现在我么了解了 OpenCV 中的轮廓组织结构。我们还是根据上边的图片再学习一下 OpenCV 中的轮廓检索模式。
    cv2.RETR_LIST,cv2.RETR_TREE,cv2.RETR_CCOMP,cv2.RETR_EXTERNAL
    到底代表什么意思?


    21.5.3 轮廓检索模式
      RETR_LIST

    从解释的角度来看,这中应是最简单的。它只是提取所有的轮廓,而不去创建任何父子关系。换句话说就是“人人平等”,它们属于同一级组织轮廓。
    所以在这种情况下,组织结构数组的第三和第四个数都是 -1。但是,很明显,Next 和 Previous 要有对应的值,你可以自己试着看看。
    下面就是我得到的结果,每一行是对应轮廓的组织结构细节。例如,第一行对应的是轮廓 0。下一个轮廓为 1,所以 Next=1。前面没有其他轮廓,所以 Previous=0。接下来的两个参数就是 -1,与刚才我们说的一样。

    >>> hierarchy
    array([[[ 1, -1, -1, -1],
            [ 2,  0, -1, -1],
            [ 3,  1, -1, -1],
            [ 4,  2, -1, -1],
            [ 5,  3, -1, -1],
            [ 6,  4, -1, -1],
            [ 7,  5, -1, -1],
            [-1,  6, -1, -1]]])

      如果你不关心轮廓之间的关系,这是一个非常好的选择。


      RETR_EXTERNAL

    如果你选择这种模式的话,只会返回最外边的的轮廓,所有的子轮廓都会被忽略掉。
    所以在上图中使用这种模式的话只会返回最外边的轮廓(第 0 级):轮廓0,1,2。下面是我选择这种模式得到的结果:

    >>> hierarchy
    array([[[ 1, -1, -1, -1],
            [ 2,  0, -1, -1],
            [-1,  1, -1, -1]]])

    当你只想得到最外边的轮廓时,你可以选择这种模式。这在有些情况下很有用。

      RETR_CCOMP

    在这种模式下会返回所有的轮廓并将轮廓分为两级组织结构。例如,一个对象的外轮廓为第 1 级组织结构。而对象内部中空洞的轮廓为第 2 级组织结构,空洞中的任何对象的轮廓又是第 1 级组织结构。空洞的组织结构为第 2 级。
    想象一下一副黑底白字的图像,图像中是数字 0。0 的外边界属于第一级组织结构,0 的内部属于第 2 级组织结构。
    我们可以以下图为例简单介绍一下。我们已经用红色数字为这些轮廓编号,并用绿色数字代表它们的组织结构。顺序与 OpenCV 检测轮廓的顺序一直。

        CCOMP Hierarchy

      现在我们考虑轮廓 0,它的组织结构为第 1 级。其中有两个空洞 1 和 2,它们属于第 2 级组织结构。所以对于轮廓 0 来说跟他属于同一级组织结构的下一个(Next)是轮廓 3,并且没有 Previous。它的 Fist_Child 为轮廓 1,组织结构为 2。由于它是第 1 级,所以没有父轮廓。因此它的组织结构数组为[3,-1,1,-1]。
      现在是轮廓 1,它是第 2 级。处于同一级的下一个轮廓为 2。没有 Previous,也没有 Child,(因为是第 2 级所以有父轮廓)父轮廓是 0。所以数组是[2,-1,-1,0]。
      轮廓 2:它是第 2 级。在同一级的组织结构中没有 Next。Previous 为轮廓 1。没有子,父轮廓为 0,所以数组是 [-1,1,-1,0]
      轮廓 3:它是第 1 级。在同一级的组织结构中 Next 为 5。Previous 为轮廓 0。子为 4,没有父轮廓,所以数组是 [5,0,4,-1]轮廓 4:它是第 2 级。在同一级的组织结构中没有 Next。没有 Previous,没有子,父轮廓为 3,所以数组是 [-1,-1,-1,3]
    下面是我得到的答案:

    >>> hierarchy
    array([[[ 3, -1, 1, -1],
    [ 2, -1, -1, 0],
    [-1, 1, -1, 0],
    [ 5, 0, 4, -1],
    [-1, -1, -1, 3],
    [ 7, 3, 6, -1],
    [-1, -1, -1, 5],
    [ 8, 5, -1, -1],
    [-1, 7, -1, -1]]])

      RETR_TREE

    终于到最后一个了,也是最完美的一个。这种模式下会返回所有轮廓,并且创建一个完整的组织结构列表。它甚至会告诉你谁是爷爷,爸爸,儿子,孙子等。

    还是以上图为例,使用这种模式,对 OpenCV 返回的结果重新排序并分析它,红色数字是边界的序号,绿色是组织结构。

        CCOMP Hierarchy
    轮廓 0 的组织结构为 0,同一级中 Next 为 7,没有 Previous。子轮廓是 1,没有父轮廓。所以数组是 [7,-1,1,-1]。
    轮廓 1 的组织结构为 1,同一级中没有其他,没有 Previous。子轮廓是2,父轮廓为 0。所以数组是 [-1,-1,2,0]。
    剩下的自己试试计算一下吧。下面是结果:

    >>> hierarchy
    array([[[ 7, -1, 1, -1],
    [-1, -1, 2, 0],
    [-1, -1, 3, 1],
    [-1, -1, 4, 2],
    [-1, -1, 5, 3],
    [ 6, -1, -1, 4],
    [-1, 5, -1, 4],
    [ 8, 0, -1, -1],
    [-1, 7, -1, -1]]])
  • 相关阅读:
    【NOI2015】荷马史诗
    Codeforces Round #415 (Div. 2)
    Codeforces Round #408 (Div. 2)
    bzoj3230
    poj1743
    poj1226
    bzoj1295
    bzoj1294
    bzoj1296
    bzoj1239
  • 原文地址:https://www.cnblogs.com/Undo-self-blog/p/8438808.html
Copyright © 2020-2023  润新知