⚠️由于自己的拖延症,3.4.3翻到一半,OpenCV发布了4.0.0了正式版,所以接下来是按照4.0.0翻译的。

⚠️除了版本之外,其他还是照旧,Contours in OpenCV,附原文。这篇比较特殊,有多个小节组成,我把它们合在一起了。

轮廓:入门

目标

  • 理解什么是轮廓。
  • 学会找到轮廓,画出轮廓等等。
  • 你会看到这些函数:cv.findContours()cv.drawContours()

什么是轮廓?

轮廓可以简单地解释为(沿边界)连接所有的连续点的一条曲线,具有相同的颜色或强度。轮廓是形状分析和目标检测、识别的重要工具。

  • 为了更准确的找到图像,使用二元图像。在找轮廓之前,先对原图应用阈值法或者坎尼边缘检测。
  • 从 OpenCV 3.2 开始, findContours() 这个函数不再修改原图。
  • 在OpenCV中,寻找轮廓就像从黑色背景中寻找白色物体一样。因此记住,要找的对象应该是白色的而背景应该是黑色的。

让咱们来看看怎么在二元图像上找出轮廓:

import numpy as np
import cv2 as cv
im = cv.imread('test.jpg')
imgray = cv.cvtColor(im, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray, 127, 255, 0)
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)

可以看到,cv.findContours() 函数有三个参数,第一个是原图像,第二个是轮廓检索模式,第三个是轮廓近似法。每个单独的轮廓是一个对象边界点坐标(x,y)的Numpy数组。

提示

我们稍后会讨论到第二第三参数以及关于层次结构的事情,在那之前,我们对它们给出的值都能获得一个不错的效果。

怎么画出轮廓?

要画出轮廓,就要用到 cv.drawContours 函数。它也可以被用来画任何的形状,只要你有那一组边界点。它的第一个参数是原图,第二个参数是Python list型的轮廓点,第三个参数是轮廓的编号(当你想要画一个单独的轮廓时有用,如果要画出所有轮廓,就传入-1)。还有其他参数是颜色、粗细等等。

  • 要画图一张图像上所有轮廓
cv.drawContours(img, contours, -1, (0,255,0), 3)
  • 要画一个单独的轮廓,比如第四个轮廓:
cv.drawContours(img, contours, 3, (0,255,0), 3)
  • 但大多数情况下,以下的方法更有用:
cnt = contours[4]
cv.drawContours(img, [cnt], 0, (0,255,0), 3)

提示

后两个方法是相同的,但继续看下去,你会发现最后这一种更有价值。

轮廓近似法

这是 cv.findContours 函数的第三个参数,它到底是什么意思呢?

之前我们提到,轮廓是具有相同强度的形状的边界。它存储了形状边界(上的点)的那些 (x,y) 坐标。但他是否保存了全部完整的边界(上的点)呢?这就由轮廓近似法这个参数来指定了。

如果你传入 cv.CHAIN_APPROX_NONE,所有的边界点都会被保存。但实际上,我们需要所有的点吗?比方说,你来找一条直线的轮廓。你需要这条直线上的全部的点来表示这条直线吗?当然不是,我们只需要那条直线的两个端点就够了。这就是我们要传入参数 cv.CHAIN_APPROX_SIMPLE 来做的事。它移除了所有冗余的点,压缩了轮廓,从而节约了内存。

以下的矩形图演示了这个技术。只是在所有轮廓数组点的坐标上(用蓝色)都画一个圈。第一张用参数 cv.CHAIN_APPROX_NONE 得到的点(734 points)而第二张图像显示了使用参数 cv.CHAIN_APPROX_SIMPLE 得到的点(只有4个点)。看它节约了多少内存!!

额外资源

练习

轮廓特征

目标

在这篇文章中,我们会学习

  • 找出轮廓线的不同特征,如面积、周长、质心、边框等
  • 你会遇到大量的与轮廓相关的函数

1. 图像矩

图像矩可以帮助你算出一些图像的特征,比如物体的质心,物体的面积等等。查看维基百科的页面,Image Moments。

函数 cv.moments() 给出计算的所有图像矩的字典。看下面:

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

从这个矩,你可以提取有用的数据,比如面积、质心等等。质心通过这个关系取到:Cx=M10M00 和 Cy=M01M00。这可以按照以下步骤来完成:

cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])

2. 轮廓面积

轮廓面积通过这个函数拿到 cv.contourArea(),或者通过图像矩 M['m00']

area = cv.contourArea(cnt)

3. 轮廓周长

它也被称为弧长。它可以用函数 cv.arcLength() 取到。第二个参数指定了其形状是否是一个封闭的轮廓(如果传Trued的话)或者只是一条曲线。

perimeter = cv.arcLength(cnt,True)

4. 轮廓近似法

它把轮廓形状拟合成另外一个形状,拟合中使用的顶点的数量取决于我们指定的精度。这就是道格拉斯普克算法(译者注:它又叫迭代端点拟合算法,大家可以自行百度)查看维基百科的页面来了解算法和证明。

为了理解这东西,假设你试图在图像中找到一个正方形,但是由于图像中的一些问题,没有得到一个完美的正方形,而是得到了一个“坏的形状”(如下面的第一幅图像所示)。现在你可以用这个函数来拟合这个形状。在这个函数里,第二个参数称为epsilon(就是希腊字母的ε),是从轮廓到近似轮廓的最大距离。这是一个参数就决定了拟合精度。要得到正确的输出,需要明智地选择。

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

以下第二张图,绿色的线条显示了epsilon = 10% 时候拟合曲线的长度。而第三图显示的是同样的操作在 epsilon = 1% 时的状况。而第三个参数则指定了拟合出来的曲线是否要闭合。

5. 凸包算法

凸包算法会看起来和轮廓近似算法有些相像,但它并非如此(有些情况下两者可能提供出相同的结果)。函数 cv.convexHull() 检测曲线的凸面瑕疵并且修正它。通常来说,凸曲线是那些总是向外突出的曲线,或者至少是扁平的曲线。而如果它是内凹的,我们就管他叫凸面瑕疵。例如说,看下面手的图像,红色的线条就是这张手图像的凸包。双面箭头标出的就是凸面瑕疵,是凸包与轮廓线的局部最大偏差。

关于它的语法有一点需要讨论:

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

参数细节:

  • points 是我们传入的轮廓。
  • hull 是输出值,通常我们都不管它。
  • clockwise:导向标志。如果为 True,输出的凸包向着顺时针方向,否则向着逆时针方向。
  • returnPoints:默认情况下,是True。然后它返回凸包点的坐标。如果为False,则返回与凸包点对应的轮廓点的索引(下标)。

因此,要得到上图所示的凸包,下面的步骤就足够了:

hull = cv.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]],这与(为True是的)第一个(点)结果相同(其他结果也一样)。

当我们讨论凸面瑕疵时,你会再次看到它。

6. 检测凸面

这有一个方法来检测一段曲线是凸还是凹,cv.isContourConvex()。它只返回 True 或者 False。没啥好多说的。

k = cv.isContourConvex(cnt)

7. 边框

有两种类型的矩形边框。

7.a. 整齐的边框

它是一个整齐的矩形,不考虑物体的旋转,因此边框框出来的区域不会是最小的。它通过cv.boundingRect() 函数来获取。
令坐标 (x,y) 作为边框的左上角,并且 (w,h) 作为他的宽度和高度。

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

7.b. 旋转后的边框

在这,边界矩形是用最小面积绘制的,所以它还考虑了旋转。使用的方法是 cv.minAreaRect()。它返回一个 Box2D 结构包含了以下细节 - ( 中心 (x,y)、(宽度,高度)、旋转角度)。但要画出这个矩形,我们需要矩形的四个角落的点。

它们通过cv.boxPoints() 来获取。

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

两种矩形都显示在了同一张图像中。绿色的矩形显示了普通的矩形边界,红色则是旋转之后的矩形边界。

8. 最小封闭圆

接下来我们要找出一个对象的外接圆,通过方法 cv.minEnclosingCircle()。它是一个完全盖住了物体,但面积最小的圆。

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

9. 拟合一个椭圆

接下来是把对象拟合成一个椭圆。它返回的是,旋转后的矩形的内接椭圆。

ellipse = cv.fitEllipse(cnt)
cv.ellipse(img,ellipse,(0,255,0),2)

10. 拟合一条线段

类似地,我们可以将直线与一组点相匹配。下图包含一组白点。我们可以拟合出一条直线。

rows,cols = img.shape[:2]
[vx,vy,x,y] = cv.fitLine(cnt, cv.DIST_L2,0,0.01,0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((cols-x)*vy/vx)+y)
cv.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)

Additional Resources

Exercises

轮廓属性

在这里,我们将学习提取一些常用的物体属性,如密实度,当量直径,遮罩图像,平均强度等。更多的特征可以在 Matlab regionprops documentation 中找到。

*(NB : 质心、面积、周长等等也属于这个范畴,但我们之前一节已经遇到过了。)*

1. 高宽比

它是物体边界矩形的宽高比。

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

2. 规模

规模是轮廓面积与边界矩形面积之比。

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

3. 密实度

密实度是轮廓面积与其凸包面积的比值。

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

4. 当量直径

当量直径是与轮廓面积相同的圆的直径。

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

5. 朝向

朝向是指物体指向的角度,以下的方法还给出了主轴、副轴的长度。

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

6. 遮罩图像和像素点

有些情况下,我们可能需要构成那个物体的所有点,可以照以下方法做:

mask = np.zeros(imgray.shape,np.uint8)
cv.drawContours(mask,[cnt],0,255,-1)
pixelpoints = np.transpose(np.nonzero(mask))
#pixelpoints = cv.findNonZero(mask)

这有两种方案,其中之一是使用 Numpy 函数,另一个是使用 OpenCV 函数。(就是代码中最后注释的行)都能做同样的事情。结果也是一样的,但有一个小区别 Numpy 给出了坐标,以 **(row, column)** 的形式。然而 OpenCV 则以 **(x,y)** 格式给出了坐标。因此基本上,结果是可以互换的,记住 row = xcolumn = y

7. 最大最小值及其位置

我们可以通过遮罩图像找到这些参数。

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

8. 平均颜色和平均强度

在这里,我们可以找到一个物体的平均颜色。也可以是物体在灰度模式下的平均强度。我们再一次用同样的遮罩图像来做。

mean_val = cv.mean(im,mask = mask)

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])

比方说,如果我把它应用在一张印度地图上,我会得到以下的结果:

Additional Resources

Exercises

  • 还有很多特征在 matlab regionprops doc 中。试着实现他们。

轮廓 : 更多函数

目标

在这一章中,我们将学习

  • 凸面瑕疵以及如何找到凸面瑕疵
  • 求点到多边形的最短距离
  • 匹配不同的形状

理论和代码

1. 凸面瑕疵

在关于轮廓的第二节中,我们看到的啥叫凸包。任何物体到凸包的偏差,我们都可以认为这是凸面瑕疵。

OpenCV提供了一个现成的函数来找到这些凸面瑕疵,即 cv.convexityDefects()。一个基本的函数调用如下:

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

提示

记住当我们要找凸包时,我们传入的参数必须是 returnPoints = False,才能找到凸性缺陷。

它返回一个数组,其中每行(数组元素)包含这些值 - [ 起点, 终点, 最远点, 到最远点的近似距离 ]。我们可以用图像把它可视化。我们画一条线连接起点和终点,然后在最远处画一个圆。记住,返回的前三个值是cnt的索引。所以我们必须从cnt中得到这些值。

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

然后,看下结果:

2. 点多边形测试

这个函数找到图像上某一点到轮廓的最短距离,当点在轮廓之外时,它返回的距离为负数,在轮廓里时返回的数值为正,在轮廓上是返回的值是零。

举个例子,我们可以用以下方法来检查点 (50,50) :

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

在这个函数中,第三个参数是 measureDist。如果它为True,函数返回带符号的距离。如果它为False,它返回该点是否在轮廓之内 (它分别返回 +1, -1, 0)。

提示

如果你(只关心内外)不想查出距离,第三个参数一定要写False。因为在这算距离是个时间复杂度很高的操作。因此False会带来2-3倍速度的提升。

3. 匹配形状

OpenCV 哟个函数 cv.matchShapes() 令我们可以比较两个形状,或者两个轮廓,并且返回一个数值用来表示相似度。这个返回的数字越小,两个形状的相似度就越高。它是基于Hu-不变矩计算出来的数值,不同的测算方法在文档中有说明。(译者附:hu-不变矩是什么)

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

我尝试匹配以下给出的不同的形状:

我得到以下结果:

  • 将图像A与自身进行匹配 0.0
  • 匹配图像A和图像B 0.001946
  • 匹配图像A和图像C 0.326911

看,即使图像旋转也不会对这个比较产生太大影响。

也看看这个:

胡氏不变矩(Hu-Moments)是平移、旋转和放缩不变的七个矩。第7个是偏斜量不变。可以使用cv.HuMoments()函数找到这些值。第7个是偏不变量。可以使用cv.HuMoments()函数找到这些值。

额外资源

练习

  1. 查阅有关于 cv.pointPolygonTest() 的文档。你可以找到一个漂亮的红蓝色图像,它表示从所有像素到白色曲线的距离。同样,外部的点也是红色的。轮廓边缘用白色标记。问题很简单。编写一个代码来创建这样的距离表示。
  2. 用函数 cv.matchShapes() 比较数字图像或者字母。(这是通往OCR(译者注:OCR,Optical Character Recognition,视觉字符识别)的简单一步)。

轮廓层级

目标

这次我们来学习轮廓的层次结构,就是说,轮廓中的父子关系。

理论

在之前的几篇关于轮廓的文章中,我们已经用到了OpenCV提供的几个和轮廓有关的函数。而当我们在图像中使用函数 cv.findContours() 来查找轮廓时,我们曾经穿过一个参数,轮廓检索模式。我们通常传的是cv.RETR_LIST 或者 cv.RETR_TREE 并且出来的效果还行。但它们到底是啥意思呢?

另外,在输出中,我们有三个数组,第一个是图像,第二个是咱们的轮廓,第三个我们管它叫层级hierarchy(请查看之前文章中写的代码)。 但我们在任何地方都从未使用过层级。那么这层级是什么,用来做什么呢?它与前面提到的函数参数有什么关系?

这就是我们要在这篇文章中解决的事情。

啥是层级?

通我们使用这函数 cv.findContours() 来检测一张图像中的物体,对吧?这些物体有时候会出现在不同地点吧,但在有些情况下,一些形状在另一些形状当中。就像嵌套的图形。如果那样的话,我们管外侧的叫父轮廓,内侧的叫子轮廓。这样的话,图像中的轮廓就彼此建立起了父子关系。我们可以确定一个轮廓是如何与其他轮廓相互关联的,比如,它是另一个轮廓的子轮廓,或者它是一个父轮廓等等。这种关系的表示被称为层级。

参考以下示例图像:

在这张图中,我们用数字0-5标记了一些形状。2 和 2a 表示边框的外侧轮廓和内侧轮廓。

在此,轮廓0,1,2是外侧或者外部我们可以说,它们处于 层级-0 或者简单的说它们在 同一个层级。

接下来是 轮廓-2a。它可以被认为是 轮廓-2的子轮廓(或者反过来说,轮廓-2是轮廓-2a的父轮廓)。因此我们让它处于层级-1。类似的,轮廓-3 是轮廓-2的子轮廓并且处于在下一个层级中。最后轮廓 4,5 是 contour-3a 的子轮廓,并且处于最后一个层级。按这种方法,我们标记了所有的边框。我们可以说轮廓-4 是轮廓-3a 的第一个子轮廓(也可以是轮廓-5)。

我提这些事情是为了理解诸如 相同的层级外部轮廓子轮廓,父轮廓第一个子轮廓等等,这些术语。现在我们进入OpenCV。

层级在OpenCV中的表示

所以每个轮廓都有它自己的信息关于它是什么层次结构,谁是它的子轮廓,谁是它的父轮廓等等。OpenCV将它表示为一个由四个值组成的数组:[Next, Previous, First_Child, Parent]

*"Next 表示在相同层级的下一个轮廓。"*

用我们图像中的轮廓-0来打个比方,它的下一个轮廓是哪一个呢?是轮廓-1。因此Next = 1。同样的对于轮廓-1,下一个轮廓是轮廓-2,Next = 2。

那轮廓contour-2又是个什么情况呢?在同一层级没有下一个轮廓了。因此Next = -1。那轮廓-4呢?它和轮廓-5在同一个层级。因此它下一个轮廓是轮廓-5,Next = 5。

*"Previous 表示在相同层级下的前一个轮廓。"*

和上面相同,轮廓-1的前一个轮廓是同一层级的轮廓-0。同样的,对于轮廓-2来说,前一个是轮廓-1。而轮廓-0没有前一个,所以它(Previous)的值就是-1。

*"First_Child 表示它的第一个子轮廓。"*

没必要多解释了。轮廓-2的子轮廓是轮廓-2a。所以它(First_Child)得到轮廓-2a的索引。而轮廓-3a呢?它有俩子轮廓,但我们只取到第一个子轮廓。也就是轮廓-4。所以轮廓-3a的First_Child = 4。

*"Parent 表示其父轮廓的索引。"*

它就是和First_Child相反。对轮廓-4和轮廓-5来说,父轮廓都是轮廓-3a。而轮廓-3a的父轮廓是轮廓-3,以此类推。

提示

如果没有父轮廓或者子轮廓,这个字段取-1。

现在我们了解了 OpenCV 里用来表示层级的形式,我们可以借助上面给出的图像来了解之前提到的OpenCV中的轮廓检索模式。比如像是 cv.RETR_LIST,cv.RETR_TREE,cv.RETR_CCOMP,cv.RETR_EXTERNAL 等等这些标志是什么意思?

轮廓检索模式

1. RETR_LIST

这是四种模式中最简单的模式(简单是指解释起来简单)。它简单的检索出所有的轮廓,但却不创建任何的父子关系。在这个规则下父子是等价的,它们仅仅是轮廓而已。意思就是说它们全都属于同一个层级。

因此,在这种规则下,层级数组的第三和第四项(第一子轮廓和父轮廓)总是返回-1。但是很明显,下一项和前一项会有所有轮廓对应的值。检查并验证一下。

下面是我得到的结果,每一行都是对应轮廓的层次细节。例如,第一行对应于轮廓0。下一个是轮廓1。所以Next = 1。没有上一个,所以Previous = -1。剩下的两个,如前所述,是-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]]])

如果不使用任何层次结构特性,这是在代码中使用的最佳选择。

2. RETR_EXTERNAL

如果使用此标志,它只返回最外部的轮廓。所有的子轮廓都被抛弃了。我们打个比方可以说,按照这个法律,只有一家之中的最长者被照顾到了,它完全不考虑这个家族的其他成员 :)

所以,在我们的图像中,有多少最外部的轮廓呢?就是说在层级-0的轮廓有多少?就三个,它们是轮廓0,1,2,对吧?现在尝试用这个标识来搜索轮廓。在这里,给每个元素的值与上面相同。并与上述结果进行了比较。下面是我得到的:

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

如果只想提取外部轮廓,可以使用此标志。它在某些情况下可能更好用。

3. RETR_CCOMP

这个标识检索This flag retrieves all the contours and arranges them to a 2-level hierarchy. ie external contours of the object (ie its boundary) are placed in hierarchy-1. And the contours of holes inside object (if any) is placed in hierarchy-2. If any object inside it, its contour is placed again in hierarchy-1 only. And its hole in hierarchy-2 and so on.

此标志检索所有轮廓,并将其排列到一个两级层次结构中。就是说,物体的外部轮廓(即其边界)被置于层次-1中。对象内部的孔洞轮廓(如有)放在层次-2中。如果其中有任何对象,则将其轮廓又放置在层级-1。它的孔洞轮廓在层级-2。

想象一个图像,在黑色的背景上有个很大的白色0。这个0的外圈,属于层级-1,而0的内圈则属于层级-2。

我们可以用一张图来简单解释它,我已经用红色标注了轮廓的顺序,用绿色标注了它们所属的层次结构(1或者2)。标注的顺序和OpenCV检测轮廓的顺序一样。

来考虑第一个(译者注:上图中红色标为0的)轮廓,就是轮廓-0。它属于层级-1。它有两个洞,轮廓1和2,它们都属于层级-2。因此对于轮廓-0来说,同层级的下一个轮廓是轮廓-3,没有前一个轮廓,第一个子轮廓是属于层级-2的轮廓-1,没有父轮廓,因为它在层级-1里。因此它的层级数组是[3,-1,1,-1]。

现在说说轮廓-1。它属于层级-2。同层级(在轮廓-1的父子关系下)的下一个是轮廓-2,没有前一个,没有子轮廓,但父轮廓是轮廓-0。因此数组是[2,-1,-1,0]。

类似的,轮廓-2:它在层级-2,在同一层级没有下一个轮廓,前一个轮廓是轮廓-1,没有子轮廓,父轮廓是轮廓-0。所以轮廓数组是 [-1,1,-1,0]。

轮廓-3:在层级-1的下一个轮廓是轮廓-5,前一个是轮廓-0,子轮廓是轮廓-4,父轮廓没有。数组是 [5,0,4,-1]。

轮廓-4:在层级-2轮廓-3之下,没有兄弟轮廓,没有下一个轮廓,没有子轮廓,父轮廓是轮廓-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]]])

4. RETR_TREE

还剩下这最后一家伙,完美先生。它检索全部的轮廓,并且构建一个完整的家谱。它甚至会告诉你,谁是爷爷,爸爸,儿子,孙子,甚至超过... :)

我用上面的图像来做例子,按照 cv.RETR_TREE 重新谢了代码。根据OpenCV给出的结果对轮廓线进行重新排序并进行分析。同样,红色字母表示轮廓号,绿色字母表示层次顺序。

拿轮廓-0来说:在层级-0,下一个同级轮廓是轮廓-7,没有前一个轮廓,子轮廓是轮廓-1,无父轮廓。因此数组是 [7,-1,1,-1]。

拿轮廓-2来说:在层级-1,没有其他轮廓在同一层。因此没有前一个(和后一个)。子轮廓是轮廓-3,父轮廓是轮廓-1.因此数组是[-1,-1,3,1]。

剩下的,靠你自己啦。以下是完整答案:

>>> 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]]])

额外资源

练习


上篇:【翻译:OpenCV-Python教程】图像金字塔

下篇:【翻译:OpenCV-Python教程】OpenCV里的直方图

【翻译:OpenCV-Python教程】图像轮廓相关推荐

  1. OpenCV Python教程(2、图像元素的访问、通道分离与合并)

    OpenCV Python教程之图像元素的访问.通道分离与合并 转载请详细注明原作者及出处,谢谢! 访问像素 像素的访问和访问numpy中ndarray的方法完全一样,灰度图为: [python] v ...

  2. OpenCV Python教程(3)(4)(5): 直方图的计算与显示 形态学处理 初级滤波内

    OpenCV Python教程(3.直方图的计算与显示) 本篇文章介绍如何用OpenCV Python来计算直方图,并简略介绍用NumPy和Matplotlib计算和绘制直方图 直方图的背景知识.用途 ...

  3. openCV—Python(6)—— 图像算数与逻辑运算

    openCV-Python(6)-- 图像算数与逻辑运算 一.函数简介 1.add-图像矩阵相加 函数原型:add(src1, src2, dst=None, mask=None, dtype=Non ...

  4. OpenCV python 提取图像内的三色

    OpenCV python 提取图像内的三色 原图 [opencv.jpg] import cv2 import numpy as npdef main():# 1.导入图片img_src = cv2 ...

  5. Python机器视觉--OpenCV进阶(核心)--图像轮廓查找识别,绘制图像轮廓与图像轮廓的面积周长计算

    1.图像轮廓查找识别与绘制图像轮廓 1.1 什么是图像轮廓 图像轮廓是具有相同颜色或灰度的连续点的曲线. 轮廓在形状分析和物体的检测和识别中很有用. 轮廓的作用: 用于图形分析 物体的识别和检测 注意 ...

  6. 【opencv】(6) 图像轮廓处理

    各位同学好,今天和大家分享一下opencv中如何获取图像轮廓,以及对轮廓的一些其他操作.内容有: (1)轮廓检测:cv2.findContours():(2)轮廓绘制:cv2.drawContours ...

  7. 利用python提取图像轮廓

    from PIL import Imagefrom pylab import * # 读取图像到数组中 # convert('L')表示将RGB转换为L模式,表示像素点在[0,255]之间im = a ...

  8. OpenCV:07图像轮廓

    图像轮廓 什么是图形轮廓 查找轮廓 绘制轮廓 计算轮廓的面积和周长 轮廓面积 轮廓周长 多边形逼近 凸包 轮廓拟合 外接矩形 最小外接矩形 最大外接矩形 外接圆 边缘检测`Canny` 霍夫变换 直线 ...

  9. opencv python教程简书_OpenCV-Python系列二:常用的图像属性

    对于图像,我们经常需要知道关于图像的特殊属性,比如宽度,高度,面积,像素点数目等等,那么在opencv-python中,这些信息如何获取呢? 本文结构: 1.基本图像属性 2. 对于opencv中的特 ...

  10. OpenCV Python教程(1、图像的载入、显示和保存)

    本文是OpenCV  2 Computer Vision Application Programming Cookbook读书笔记的第一篇.在笔记中将以Python语言改写每章的代码. PythonO ...

最新文章

  1. BZOJ 2154 [国家集训队]Crash的数字表格 / JZPTAB(莫比乌斯反演,经典好题)(Luogu P1829)
  2. pandas从dataframe中选择部分行、列
  3. 通过 powershell 配置 IIS
  4. C语言中scanf()的用法
  5. 2020年度SaaS企业 TOP100
  6. Keil5 解决编译通过显示红叉
  7. Java学习手册:Java网络编程面试问题
  8. arch linux 看图软件,菠萝看图
  9. C语言源代码转变为可执行程序的过程
  10. Rock Paper将为圣地亚哥教士棒球队开发AR游戏
  11. /usr/bin/ld: 找不到 -lgcc_s怎么办?
  12. AR红包Android端实现原理
  13. Git 进阶 —— 时光穿梭机
  14. war3 小头像图标位置
  15. 【前端】Ajax-form表单与模板引擎
  16. 在线分数计算机_乘除法,分数乘除法口算题.doc
  17. 前端的工作越来越难找,到底是不是前端领域已经饱和了?
  18. 镭神C16激光雷达在ubantu下测试
  19. Java简单代码验证三门问题
  20. 17-阿里云服务器ECS使用教程之Web环境的搭建

热门文章

  1. 教你批量查询韵达快递物流并分析出包含提前签收的单号
  2. 音频采样率与时间戳的计算
  3. Xmind软件及文件双击不能打开?
  4. 网工学习笔记(四):办公网络布线
  5. 计算机学位论文的开题报告,计算机硕士论文开题报告格式范文
  6. OceanBase携手天阳科技推出新一代信用卡核心系统联合解决方案,为信用卡业务稳健增长提供创新活力与数据动力
  7. Excel神奇的输入自动替换,原来就是这么简单!
  8. 怎么将视频转成gif格式?手把手教你转换方法
  9. 一帆风顺物业管理系统 v3.01 免费
  10. 华为云×环信,强强联手实现用户增长,降本增效加快企业转型!