OpenCV 4.11.0
开源计算机视觉
|
上一教程: 轮廓:更多函数
这次,我们将学习轮廓的层次结构,即轮廓中的父子关系。
在前面几篇关于轮廓的文章中,我们使用了OpenCV提供的几个与轮廓相关的函数。但是当我们使用cv.findContours()函数在图像中查找轮廓时,我们传递了一个参数,轮廓检索模式。我们通常传递cv.RETR_LIST或cv.RETR_TREE,它运行良好。但这究竟意味着什么?
此外,在输出中,我们得到了三个数组,第一个是图像,第二个是我们的轮廓,还有一个我们命名为hierarchy的输出(请查看前面文章中的代码)。但是我们从未在任何地方使用过这个hierarchy。那么这个hierarchy是什么,它有什么作用?它与前面提到的函数参数有什么关系?
这就是我们将在本文中讨论的内容。
通常我们使用cv.findContours()函数来检测图像中的物体,对吧?有时物体位于不同的位置。但在某些情况下,一些形状位于其他形状内部。就像嵌套图形一样。在这种情况下,我们将外部形状称为父级,内部形状称为子级。这样,图像中的轮廓彼此之间具有一定的关系。我们可以指定一个轮廓如何与另一个轮廓连接,例如,它是某个其他轮廓的子级,还是父级等等。这种关系的表示称为层次结构。
考虑下面的示例图像
在这张图像中,有一些形状,我已经用0-5编号。2 和 2a表示最外层框的外部和内部轮廓。
这里,轮廓0、1、2是外部或最外层的。我们可以说,它们位于层次结构-0中,或者简单地说它们位于同一层次结构级别。
接下来是轮廓-2a。它可以被认为是轮廓-2的子级(或者反过来,轮廓-2是轮廓-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,对吗?现在尝试使用此标志查找轮廓。这里每个元素的值也与上面相同。将其与上面的结果进行比较。以下是我的结果
如果您只想提取外部轮廓,可以使用此标志。在某些情况下这可能很有用。
此标志检索所有轮廓并将它们排列到一个两级层次结构中。即对象的外部轮廓(即其边界)位于层次结构-1中。对象内部的孔的轮廓(如果有)位于层次结构-2中。如果其内部有任何对象,则其轮廓再次仅位于层次结构-1中。其孔位于层次结构-2中,依此类推。
只需想象一下黑色背景上一个“大大的白色0”。“0”的外圈属于第一层级,内圈属于第二层级。
我们可以用一张简单的图片来解释。这里我用红色标注了轮廓的顺序,用绿色标注了它们所属的层级(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]。
剩下的你可以自己填写。这是我得到的最终答案
这就是最终结果,完美先生。它检索所有轮廓并创建一个完整的层级关系列表。它甚至能告诉你谁是爷爷、父亲、儿子、孙子,甚至更远……:)。
例如,我使用了上面的图片,为cv.RETR_TREE重写了代码,根据OpenCV给出的结果重新排序轮廓,并对其进行分析。同样,红色字母表示轮廓编号,绿色字母表示层级顺序。
以轮廓-0为例:它位于第0层级。相同层级的下一个轮廓是轮廓-7。没有前一个轮廓。子轮廓是轮廓-1。没有父轮廓。所以数组是[7,-1,1,-1]。
以轮廓-2为例:它位于第1层级。相同层级没有其他轮廓。没有前一个轮廓。子轮廓是轮廓-3。父轮廓是轮廓-1。所以数组是[-1,-1,3,1]。
剩下的部分,请你自己尝试。以下是完整的答案