![]() |
OpenCV 4.12.0
开源计算机视觉
|
上一篇教程: 轮廓:更多函数
这次,我们将学习轮廓的层级结构,即轮廓中的父子关系。
在之前的几篇关于轮廓的文章中,我们已经使用了 OpenCV 提供的一些与轮廓相关的函数。但是,当我们使用 cv.findContours() 函数在图像中查找轮廓时,我们传递了一个参数,轮廓检索模式。我们通常传递 cv.RETR_LIST 或 cv.RETR_TREE,并且效果很好。但它实际上意味着什么?
此外,在输出中,我们得到了三个数组,第一个是图像,第二个是我们的轮廓,还有一个输出我们命名为 hierarchy(请查看之前文章中的代码)。但我们从未在任何地方使用过这个层级结构。那么这个层级结构是什么?它的作用是什么?它与前面提到的函数参数有什么关系?
这就是我们将在本文中讨论的内容。
通常我们使用 cv.findContours() 函数来检测图像中的对象,对吗?有时对象位于不同的位置。但在某些情况下,一些形状位于其他形状内部。就像嵌套的图形一样。在这种情况下,我们将外部的称为父,内部的称为子。这样,图像中的轮廓就具有彼此之间的关系。我们可以指定一个轮廓如何连接到另一个轮廓,例如,它是某个其他轮廓的子轮廓,还是父轮廓等。这种关系的表示称为层级结构。
考虑下面的示例图像
在此图像中,有一些形状,我已将其编号为 0-5。2 和 2a 表示最外层框的外部和内部轮廓。
在这里,轮廓 0、1、2 是外部或最外层的。我们可以说,它们位于层级-0 中,或者简单地说,它们位于相同的层级级别中。
接下来是 contour-2a。它可以被认为是 contour-2 的子轮廓(或者相反,contour-2 是 contour-2a 的父轮廓)。因此将其放在层级-1 中。类似地,轮廓 3 是轮廓 2 的子轮廓,它位于下一个层级中。最后,轮廓 4、5 是轮廓 3a 的子轮廓,它们位于最后一个层级级别中。从我给框编号的方式来看,我会说轮廓 4 是轮廓 3a 的第一个子轮廓(也可能是轮廓 5)。
我提到了这些内容是为了理解诸如相同的层级级别、外部轮廓、子轮廓、父轮廓、第一个子轮廓等术语。现在让我们进入 OpenCV。
因此,每个轮廓都有其自己的信息,包括它所属的层级、谁是它的子轮廓、谁是它的父轮廓等。OpenCV 将其表示为一个包含四个值的数组:[Next, Previous, First_Child, Parent]
例如,以我们图片中的轮廓 0 为例。同一级别的下一个轮廓是谁?是轮廓 1。因此,只需将 Next = 1。类似地,对于轮廓 1,下一个是轮廓 2。因此 Next = 2。
轮廓 2 呢?同一级别没有下一个轮廓。因此,只需将 Next = -1。轮廓 4 呢?它与轮廓 5 在同一级别。因此,它的下一个轮廓是轮廓 5,所以 Next = 5。
与上面相同。轮廓 1 的上一个轮廓是同一级别的轮廓 0。类似地,对于轮廓 2,它是轮廓 1。对于轮廓 0,没有上一个轮廓,因此将其设置为 -1。
无需任何解释。对于轮廓 2,子轮廓是轮廓 2a。因此,它获得轮廓 2a 的对应索引值。轮廓 3a 呢?它有两个子轮廓。但我们只取第一个子轮廓。它是轮廓 4。因此,轮廓 3a 的 First_Child = 4。
它与 First_Child 正好相反。对于轮廓 4 和轮廓 5,父轮廓都是轮廓 3a。对于轮廓 3a,它是轮廓 3,依此类推。
因此,现在我们了解了 OpenCV 中使用的层级结构样式,我们可以借助上面给出的相同图像来检查 OpenCV 中的轮廓检索模式。即像 cv.RETR_LIST、cv.RETR_TREE、cv.RETR_CCOMP、cv.RETR_EXTERNAL 等标志意味着什么?
这是四个标志中最简单的(从解释的角度来看)。它只是检索所有轮廓,但不创建任何父子关系。在这种规则下,父母和孩子是平等的,它们只是轮廓。即它们都属于相同的层级级别。
因此,层级结构数组中的第 3 项和第 4 项始终为 -1。但显然,Next 和 Previous 项将具有其对应的值。只需自己检查并验证它。
下面是我得到的结果,每一行都是对应轮廓的层级结构详细信息。例如,第一行对应于轮廓 0。下一个轮廓是轮廓 1。因此 Next = 1。没有上一个轮廓,因此 Previous = -1。其余两个,如前所述,是 -1。
如果您不使用任何层级结构功能,那么这是在您的代码中使用的好选择。
如果您使用此标志,它只会返回最外层的标志。所有子轮廓都将被遗漏。我们可以说,在这种规则下,只照顾每个家庭中最年长的成员。它不关心家庭的其他成员 :)。
那么,在我们的图像中,有多少个最外层的轮廓?即在层级-0 级别?只有 3 个,即轮廓 0、1、2,对吗?现在尝试使用此标志查找轮廓。在这里,给每个元素的值与上面相同。将其与上面的结果进行比较。下面是我得到的结果
如果您只想提取外部轮廓,则可以使用此标志。在某些情况下它可能很有用。
此标志检索所有轮廓并将它们排列到 2 级层级结构中。即对象的外部轮廓(即其边界)放置在层级-1 中。对象内部的孔的轮廓(如果有)放置在层级-2 中。如果其中有任何对象,则其轮廓再次仅放置在层级-1 中。其孔位于层级-2 中,依此类推。
只需考虑黑色背景上的“大白色零”的图像。零的外部圆属于第一层级结构,零的内部圆属于第二层级结构。
我们可以用一个简单的图像来解释它。在这里,我用红色标记了轮廓的顺序,用绿色标记了它们所属的层级结构(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 中。在轮廓 0 下的同一层级中没有下一个轮廓。所以没有 Next。上一个是轮廓 1。没有子轮廓,父轮廓是轮廓 0。因此,数组是 [-1,1,-1,0]。
轮廓 3:层级-1 中的下一个是轮廓 5。上一个是轮廓 0。子轮廓是轮廓 4,没有父轮廓。因此,数组是 [5,0,4,-1]。
轮廓 4:它位于轮廓 3 下的层级 2 中,并且没有兄弟轮廓。所以没有下一个,没有上一个,没有子轮廓,父轮廓是轮廓 3。因此,数组是 [-1,-1,-1,3]。
其余的你可以自己填写。这是我得到的最终答案
这是最后一个,Mr.Perfect。它检索所有轮廓并创建一个完整的家族层级结构列表。它甚至会告诉你,谁是祖父、父亲、儿子、孙子,甚至更远... :)。
例如,我使用了上面的图像,为 cv.RETR_TREE 重写代码,根据 OpenCV 给出的结果重新排序轮廓并进行分析。同样,红色字母给出轮廓编号,绿色字母给出层级结构顺序。
取轮廓 0:它位于层级-0 中。同一层级中的下一个轮廓是轮廓 7。没有上一个轮廓。子轮廓是轮廓 1。没有父轮廓。因此,数组是 [7,-1,1,-1]。
取轮廓 2:它位于层级-1 中。同一级别中没有轮廓。没有上一个轮廓。子轮廓是轮廓 3。父轮廓是轮廓 1。因此,数组是 [-1,-1,3,1]。
其余的,自己尝试一下。下面是完整答案