OpenCV  4.10.0
开源计算机视觉
加载中...
搜索中...
无匹配项
轮廓层次结构

前一个教程: 轮廓:更多函数

目标

  • 这次,我们了解轮廓的层次结构,即轮廓中的父子关系。

理论

在关于轮廓的几篇文章中,我们使用 OpenCV 提供的几个与轮廓相关的函数。但是在使用 cv.findContours() 函数在图像中找到轮廓时,我们传递一个参数,即 轮廓检索模式。我们通常传递 cv.RETR_LISTcv.RETR_TREE ,它们都很好用。但它们实际意味着什么?

此外,在输出中,我们获得了三个数组,第一个是图像,第二个是轮廓,还有一个我们命名为 hierarchy 的输出(请查看上一篇文章中的代码)。但我们从未在任何地方使用过这个层次结构。那么这个层次结构是什么,它有什么用?它与前面提到的函数参数有什么关系?

这就是本文要讨论的内容。

什么是层次结构?

通常,我们使用 cv.findContours() 函数来检测图像中的对象,对吗?有时,对象位于不同的位置。但在某些情况下,一些形状位于其他形状内部。就像嵌套图形一样。在这种情况下,我们将外部形状称为 父项,将内部形状称为 子项。这样,图像中的轮廓之间就会有一定关系。而且我们可以指定一个轮廓如何连接到另一个轮廓,比如,它是某个其他轮廓的子项还是父项等。这种关系的表示被称为 层次结构

考虑下面的示例图像

图像

在这个图像中,有几个形状,我用 0-5 对它们进行了编号。2 和 2a 表示最外部方框的外部和内部轮廓。

这里,轮廓 0、1、2 是 外部或最外部轮廓。我们可以说,它们处于 层次结构 0 或简单地说它们处于 相同的层次级别

接着是contour-2a。可以将它视为contour-2子项(或者相反,contour-2是contour-2a的父项)。因此,它位于层级-1。类似地,contour-3是contour-2a的子项,它进入下一个层级。最后,contour 4,5是contour-3a的子项,它们位于最后一个层级级别。按照我对框进行编号的方式,contour-4是contour-3a的第一个子项(也可能是contour-5)。

我提到这些内容是为了了解相同层级外部轮廓子轮廓父轮廓第一个子项等术语。现在我们来了解一下OpenCV。

在OpenCV中表示层级关系

因此,每个轮廓都有自己的信息,说明它位于哪个层级、谁是它的子项、谁是它的父项等。OpenCV将它表示为一个四值数组:[Next、Previous、First_Child、Parent]

*“Next表示处于相同层级级别的下一个轮廓”。*

例如,请看图片中的contour-0。它的相同级别中的下一个轮廓是谁?是contour-1。因此,直接写出:Next = 1。类似地,对于Contour-1,下一个是contour-2。因此,Next = 2。

contour-2的情况如何?在同一级别中没有下一个轮廓。因此,直接写出:Next = -1。 contour-4的情况如何?它与contour-5处于同一级别。因此,它的下一个轮廓是contour-5,即Next = 5。

*“Previous表示处于相同层级级别的上一个轮廓”。*

这与上面相同。在同一级别中,contour-1的上一个轮廓是contour-0。对于contour-2,类似地,它是contour-1。对于contour-0,没有上一个,因此,写为-1。

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

无需任何解释。对于contour-2,子项是contour-2a。因此,它获取contour-2a的相应索引值。 contour-3a的情况如何?它有两个子项。但我们只关注第一个子项。它是contour-4。因此,对于contour-3a,First_Child = 4。

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

它恰好与First_Child相反。对于contour-4和contour-5,父轮廓都是contour-3a。对于contour-3a,是contour-3,以此类推。

注意
如果没有子项或父项,该字段将被视为-1

那么,现在我们了解了OpenCV中使用的层级结构,可以借助上面给出的相同图像来检查OpenCV中的轮廓检索模式。即类如cv.RETR_LISTcv.RETR_TREEcv.RETR_CCOMPcv.RETR_EXTERNAL等的标志代表什么?

轮廓检索模式

1.RETR_LIST

这是四面旗帜中最简单的(从解释的角度来看)。它仅检索所有轮廓,但不建立任何父子关系。在该规则下,父母和孩子们是平等的,且它们仅仅是轮廓。即,它们均属于同一层级。

因此,这里,层级数组中的第三项和第四项总是 -1。但很明显,下一个和上一个项将具有自己的相应值。

2. RETR_EXTERNAL

如果你使用此旗帜,它仅返回最外层的旗帜。所有的子轮廓都被舍弃。我们可以说,根据该规则,仅照顾到了每个家庭的长子。它不关心家庭的其他成员)

3. RETR_CCOMP

此旗帜检索所有轮廓并将它们排列成两层层级。即,对象的外部轮廓(即其边界)置于第一层级。对象的内部孔洞(如果有)的轮廓置于第二层级。如果内部有任何对象,则其轮廓再次仅置于第一层级。其孔洞置于第二层级,依此类推。

请考虑黑色背景上的“大白零”的图像。零的外圈属于第一层级,零的内圈属于第二层级。

我们可以用一张简单的图像来解释它。这里我用红色标记了轮廓的顺序,用绿色(1 或 2)标记了它们所属的层级。顺序与 OpenCV 检测轮廓的顺序相同。

图像

因此,考虑第一个轮廓,即轮廓-0。它属于第一层级。它有两个孔洞,轮廓 1&2,它们属于第二层级。因此,对于轮廓-0,同层级中的下一个轮廓是轮廓-3。没有上一个轮廓。它的第一个子级是在第二层级中的轮廓-1。它没有父级,因为它在第一层级。因此,其层级数组是 [3,-1,1,-1]

现在取轮廓-1。它在第二层级。同层级(在轮廓-1 的父级下)的下一个轮廓是轮廓-2。没有上一个轮廓。没有子级,但父级是轮廓-0。因此,数组是 [2,-1,-1,0]。

类似地,轮廓-2:它在第二层级。轮廓-0 下的同层级中没有下一个轮廓。因此没有下一个轮廓。上一个轮廓是轮廓-1。没有子级,父级是轮廓-0。因此,数组是 [-1,1,-1,0]。

轮廓 - 3:第一层级中的下一个轮廓是轮廓-5。上一个轮廓是轮廓-0。子级是轮廓-4,没有父级。因此,数组是 [5,0,4,-1]。

轮廓 - 4:它在轮廓-3 下的第二层级中,且没有兄弟姐妹。因此,没有下一个轮廓,没有上一个轮廓,没有子级,父级是轮廓-3。因此,数组是 [-1,-1,-1,3]。

4. RETR_TREE

它就是最后的旗帜,即完美先生。它检索所有轮廓,并创建完整的家庭层级列表。它甚至可以告诉你谁是爷爷、父亲、儿子、孙子,甚至更远的辈分... :)

例如,我采用了上面图片重写了cv.RETR_TREE的代码,按照 OpenCV 对轮廓的重新排列并对它进行分析。同样,红色字母给出轮廓数,绿色字母给出层级顺序。

图像

拿轮廓-0:它在层级-0 中。在同一层级中的下一个轮廓是轮廓-7。没有先前的轮廓。子轮廓是轮廓-1。且没有父轮廓。因此数组是 [7,-1,1,-1]。

拿轮廓-2:它在层级-1 中。在同一层级中没有轮廓。没有先前的轮廓。子轮廓是轮廓-2。父轮廓是轮廓-0。因此数组是 [-1,-1,2,0]。