官方文档 – https://docs.opencv.org/3.4.0/d9/d8b/tutorial_py_contours_hierarchy.html
这一次,我们学习了轮廓的层次结构,也就是轮廓的父子关系。
在最后几篇关于轮廓的文章中,我们已经研究了一些与OpenCV所提供的轮廓有关的函数。但是当我们用cv.findContours()
函数找到图像的轮廓时,我们已经传入了一个参数,轮廓检索模式(Contour Retrieval Mod)。我们通常传入cv.RETR_LIST
或cv.retrtree
,它运行的很好。但它到底是什么意思呢?
同样,在输出中,我们得到了三个数组,首先是图像,其次是我们的轮廓,还有一个输出,我们把它命名为层次结构。但我们从未在任何地方使用这种层次结构。那么这个层次结构是什么,它是用来干什么的呢?它与前面提到的函数参数的关系是什么?
通常我们使用cv.findContours()
函数来检测图像中的对象,对吗?有时物体在不同的位置。但在某些情况下,有些形状是在其他形状中。就像嵌套数据。
在这种情况下,我们将外部称为父类,而内部类称为子类。这样,图像中的轮廓就会有一些相互关系。我们可以指定一个轮廓是如何相互连接的,比如,它是其他轮廓的子结点,或者是父结点等等,这种关系的表示被称为层次结构。
在这个图像中,有一些形状是我从0-5中编号的。2和2a表示最外层的外部和内部的轮廓。
这里,轮廓0,1,2是外部的或最外层的。我们可以说,它们在等级-0中,或者它们在相同的层次结构中。
其次是 轮廓-2a。可以把它看作是 轮廓-2 的子类(或者相反的,轮廓-2 是 轮廓-2a 的父类)。所以让它在等级-1中。类似的,轮廓-3 是 轮廓-2 的子,它在下一个层次结构中。最后,轮廓-4、5 是 轮廓-3a 的子类,他们进入了最后的等级等级。从我编号的方式,我想说,轮廓-4 是 轮廓-3a 的第一个子类(它也可以是 轮廓-5)。
每个轮廓都有它自己关于它是什么层级,谁是它的子类,谁是它的父类等等的信息。OpenCV将它表示为四个值的数组:[ 下一个(Next),前一个(Previous),第一个子类(First_Child),父类(Parent) ]
Next表示同一个层次的下一个轮廓
例如,在我们的图片中取一个 轮廓-0 。那么谁是下一个等级?是 轮廓-1 ,所以 Next=1 。对于 轮廓-1 ,下一个就是 轮廓-2,所以 Next=2
轮廓-2 呢?在同一水平上没有下一个轮廓,所以 Next=-1 。轮廓-4呢?和 轮廓-5 在同一个层级,所以下一个轮廓就是 轮廓-5, Next=5
Previous表示同一个层次的前一个轮廓
和上面一样。轮廓-1 同层次的前一个轮廓是 轮廓-0 。相似的对于 轮廓-2,前一个是 轮廓-1 。同样对于 轮廓-0,没有前一个,则表示 -1 。
First_Child表示第一个子类轮廓
对于 轮廓-2,子类是 轮廓-2a 。所以它得到了相应的 轮廓-2a 的索引值。轮廓-3a 呢,它有两个子类。但是我们只取第一个子类。是 轮廓-4 。所以对于 轮廓-3a来说,First_Child=4
Parent表示其父轮廓的索引。
与First_Child相对。轮廓-4 和 轮廓-5 的父类都是 轮廓-3a,轮廓-3a 的父类是 轮廓-3
如果没有子类或者父类,都是-1
因此,现在我们了解了OpenCV中使用的层次结构样式,我们可以在OpenCV中使用相同的图像来检查OpenCV中的轮廓检索模式
这是四个标志中最简单的一个。它只检索所有的轮廓,但不创建任何父子关系。在这条规则下,父类和子类是平等的,他们只是轮廓。它们都属于同一个层次结构。
这里,层级数组中的第3和第4项总是-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]]])
如果您没有使用任何层次结构特性,那么这是在您的代码中使用的最佳选择。
如果您使用这个标志,它只返回极端的外部标志。所有的子类轮廓都被遗忘了。
那么,在我们的图像中,有多少个极端的外轮廓?在结构层次-0水平?。只有3个,即轮廓,0,1,2,对吧?现在试着用这个标志来找到轮廓,。在这里,每个元素的值与上面的值相同。将其与上面的结果进行比较。下面是我得到的:
>>> hierarchy
array([[[ 1, -1, -1, -1],
[ 2, 0, -1, -1],
[-1, 1, -1, -1]]])
如果你想只提取外部轮廓,你可以使用这个标志。在某些情况下可能有用。
这个标记检索所有的轮廓,并将它们安排到一个2级的层次结构中。物体的外部轮廓(即它的边界)被放置在一个层级-1中。物体内部的孔(如果有的话)被放置在等级2中。如果里面有任何物体,它的轮廓只会被放在一个等级1中。以及它的洞放置在等级2上等等。
只要想想黑色背景下的“大白零”的图像就可以了。0的外圆属于第一个层次,而0的内圆属于第二级。
我们可以用一个简单的图像来解释它。在这里,我用红色表示了轮廓的顺序和它们所属的层次,用绿色的颜色(1或2),顺序与OpenCV检测轮廓的顺序相同。
第一个轮廓,即 轮廓-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 。没有下一个,没有前一个,没有子类。所以数组是 [ -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]]])
它检索所有的轮廓并创建一个完整的家族层次结构列表。它甚至告诉我们,谁是爷爷,父亲,儿子,孙子,或者更甚……:)。
举例来说,我取了上面的图片,重写了代码,传入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]]])