这一次,我们学习轮廓的层次轮廓,即轮廓的父子关系。
在上几篇关于轮廓的文章中,我们使用了OpenCV提供的几个与轮廓相关的函数。但当我们使用cv.findContours()函数发现图像的轮廓时,
我们传递一个参数,轮廓检索模式。我们通常传递cv.RETR_LIST或 cv.RETR_TREE,它工作得很好。但它到底是什么意思呢?
同样,在输出中,我们获得了三个数组,第一个是图像,第二个是轮廓,还有一个输出,我们将其命名为层次结构。
但我们从未在任何地方使用过这个层次。那么这个层次是什么呢?
这就是我们在本文中讨论的问题。
通常我们使用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,child是轮廓2a。所以它得到了轮廓2a对应的索引值。轮廓3a呢?它有两个child。
但我们只需要第一个child。这是轮廓4。所以轮廓3a First_Child = 4。
Parent表示其父轮廓线的索引。
它正好与First_Child相反。对于轮廓4和轮廓5,父轮廓都是轮廓3a。对于轮廓3a,父轮廓是轮廓3,以此类推。
如果没有子轮廓或父轮廓,该轮廓的子轮廓或父轮廓将被视为-1
像cv.RETR_LIST,cv.RETR_TREE,cv.RETR_CCOMP,cv.RETR_EXTERNAL等等的轮廓检索模式意思是什么?
这是四个标志中最简单的一个(从解释的角度)。它只是检索所有轮廓,但不创建任何父子关系。在这个规则下,父轮廓和子轮廓是平等的,他们只是轮廓。它们都属于同一个等级。
[Next, Previous, First_Child, Parent]中的第三和第四项总是-1。但显然,Next和Previous将有相应的值。
下面是我得到的结果,每一行是对应轮廓的层次细节。例如,第一行对应轮廓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中等等。
我们可以用一张简单的图片来解释。在这里,我用红色标记了轮廓的顺序和用绿色(1或2)表示它们所属的层次。顺序与OpenCV检测轮廓的顺序相同。
考虑第一条轮廓,即轮廓0。这是层次1。它有两个洞,轮廓1和2,它们属于等级2。对于轮廓0,相同层次的下一个轮廓是轮廓3。
而且之前没有。第一个是child,是层级2中的轮廓1。它没有父节点,因为它处于层次1中。它的层次数组是[3,-1,1,-1]
对于轮廓1。它属于等级2。下一个在相同层次是轮廓2。没有前一个。没有子轮廓,但父轮廓是轮廓0。数组是[2,-1,-1,0]
类似地,轮廓2:它处于层次2中。在轮廓0下没有相同层次的下一条轮廓。所以没有相同层次的下一个轮廓。前一个是轮廓1。
没有子轮廓,父轮廓是轮廓0。所以数组(1,1,1,0)。
轮廓3:在层次1中的下一个是轮廓5。前一个是轮廓0。子轮廓是轮廓4,没有父轮廓。数组是[5,0,4,-1]。
轮廓4:它在轮廓3下面的层级是2,它没有兄弟。所以下一个=-1,上一个=-1,子轮廓=-1,父轮廓都是轮廓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中。没有轮廓在同一层次上。没有前一个。子轮廓是c轮廓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]]])
https://docs.opencv.org/4.x/d9/d8b/tutorial_py_contours_hierarchy.html