了解轮廓层次结构,这次,我们了解轮廓的层次结构,即Contours中的父子关系。
在有关轮廓的最后几篇文章中,我们使用了与OpenCV提供的轮廓相关的一些功能。但是,当我们使用 cv.findContours() 函数在图像中找到轮廓时,我们传递了一个参数,即 Contour Retrieval Mode 。我们通常通过 cv.RETR_LIST 或 cv.RETR_TREE ,效果很好。但这实际上是什么意思?
另外,在输出中,我们得到了三个数组,第一个是图像,第二个是轮廓,另一个是我们命名为层次结构的输出(请检查上一篇文章中的代码)。但是,我们从未在任何地方使用此层次结构。那么,这个层次结构是什么呢?它与前面提到的函数参数有什么关系?
这就是本文要处理的内容。
什么是轮廓层次结构
通常我们使用 cv.findContours() 函数来检测图像中的对象,对吗?有时对象位于不同的位置。但是在某些情况下,某些形状位于其他形状内。就像嵌套的数字一样。在这种情况下,我们将外部的一个称为 父级 ,将内部的一个称为 子级 。这样,图像中的轮廓彼此之间就具有某种关系。并且我们可以指定一个轮廓如何相互连接,例如是其他轮廓的子轮廓,还是父轮廓等。这种关系的表示称为 层次结构 。
考虑下面的示例图像:
在此图像中,我从 0-5 编号了一些形状。2和2a表示最外面的盒子的外部和内部轮廓。
在此,轮廓0,1,2在 外部或最外部 。我们可以说,它们处于 0层次结构 中,或者只是处于 相同的层次结构级别 中。
接下来是 轮廓2a 。可以将其视为 轮廓2的子级 (或者相反,轮廓2是轮廓2a的父级)。因此,将其设置为 hierarchy-1 。同样,contour-3是contour-2的子级,位于下一个层次结构中。最后,轮廓4,5是轮廓3a的子级,它们位于最后的层次结构级别。从编号方式上来说,轮廓4是轮廓3a的第一个子元素(也可以是轮廓5)。
我提到这些东西是为了理解诸如相同的层次结构级别,外部轮廓,子轮廓,父轮廓,第一个孩子等术语。现在让我们进入OpenCV。
OpenCV中的轮廓层次结构表示
因此,每个轮廓都有关于其层次结构,其子级,其父级等的信息。OpenCV将其表示为四个值的数组:[Next,Previous,First_Child,Parent]
Next 表示相同等级的下一个轮廓
例如,在我们的图片中选择轮廓0。谁是同一级别的下一个轮廓?它是轮廓1。因此,只需将Next = 1放进去。同样对于Contour-1,下一个就是轮廓线2。所以下一个= 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中的轮廓检索模式。即像 cv.RETR_LIST ,cv.RETR_TREE ,cv.RETR_CCOMP,cv.RETR_EXTERNAL 等标志是什么意思?
轮廓检索模式
RETR_LIST
这是四个标志中最简单的一个(从解释的角度来看)。它仅检索所有轮廓,但不创建任何父子关系。在这个规则下,父母和孩子是平等的,他们只是轮廓。即它们都属于同一层次结构级别。
因此,在这里,层次结构数组中的第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]]])
如果不使用任何层次结构功能,这是在代码中使用的不错选择。
RETR_EXTERNAL
如果使用此标志,则仅返回极端的外部标志。保留所有子轮廓。可以说,根据这项法律,只有每个家庭中的老大才能得到照顾。它不在乎家庭其他成员 :)。
那么,在我们的图像中,有多少个极端的外部轮廓?即在等级0级别?只有3个,即轮廓0,1,2,对吗?现在尝试使用该标志查找轮廓。在此,赋予每个元素的值也与上述相同。与上面的结果进行比较。以下是我得到的:
>>> 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]]])
如果只想提取外部轮廓,则可以使用此标志。在某些情况下可能有用。
RETR_CCOMP
该标志检索所有轮廓并将它们排列为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下的相同层次结构中没有下一个轮廓。所以没有下一步。上一个是轮廓1。没有孩子,父母的轮廓为0。因此数组为 [-1,1,-1,0]。
轮廓-3:等级1中的下一个是轮廓5。上一个是轮廓0。孩子是轮廓4,没有父母。因此数组为 [5,0,4,-1]。
轮廓-4:位于轮廓3下的层次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]]])
RETR_TREE
这是最后一个家伙,Perfect先生。它检索所有轮廓并创建完整的族层次列表。它甚至告诉,谁是爷爷,父亲,儿子,孙子甚至更远… :)。
例如,我拍摄了上面的图片,重写了 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]]])