OpenCV 4.10.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。类似地,contour-3是contour-2a的子项,它进入下一个层级。最后,contour 4,5是contour-3a的子项,它们位于最后一个层级级别。按照我对框进行编号的方式,contour-4是contour-3a的第一个子项(也可能是contour-5)。
我提到这些内容是为了了解相同层级、外部轮廓、子轮廓、父轮廓、第一个子项等术语。现在我们来了解一下OpenCV。
因此,每个轮廓都有自己的信息,说明它位于哪个层级、谁是它的子项、谁是它的父项等。OpenCV将它表示为一个四值数组:[Next、Previous、First_Child、Parent]
例如,请看图片中的contour-0。它的相同级别中的下一个轮廓是谁?是contour-1。因此,直接写出:Next = 1。类似地,对于Contour-1,下一个是contour-2。因此,Next = 2。
contour-2的情况如何?在同一级别中没有下一个轮廓。因此,直接写出:Next = -1。 contour-4的情况如何?它与contour-5处于同一级别。因此,它的下一个轮廓是contour-5,即Next = 5。
这与上面相同。在同一级别中,contour-1的上一个轮廓是contour-0。对于contour-2,类似地,它是contour-1。对于contour-0,没有上一个,因此,写为-1。
无需任何解释。对于contour-2,子项是contour-2a。因此,它获取contour-2a的相应索引值。 contour-3a的情况如何?它有两个子项。但我们只关注第一个子项。它是contour-4。因此,对于contour-3a,First_Child = 4。
它恰好与First_Child相反。对于contour-4和contour-5,父轮廓都是contour-3a。对于contour-3a,是contour-3,以此类推。
那么,现在我们了解了OpenCV中使用的层级结构,可以借助上面给出的相同图像来检查OpenCV中的轮廓检索模式。即类如cv.RETR_LIST、cv.RETR_TREE、cv.RETR_CCOMP、cv.RETR_EXTERNAL等的标志代表什么?
这是四面旗帜中最简单的(从解释的角度来看)。它仅检索所有轮廓,但不建立任何父子关系。在该规则下,父母和孩子们是平等的,且它们仅仅是轮廓。即,它们均属于同一层级。
因此,这里,层级数组中的第三项和第四项总是 -1。但很明显,下一个和上一个项将具有自己的相应值。
如果你使用此旗帜,它仅返回最外层的旗帜。所有的子轮廓都被舍弃。我们可以说,根据该规则,仅照顾到了每个家庭的长子。它不关心家庭的其他成员)。
此旗帜检索所有轮廓并将它们排列成两层层级。即,对象的外部轮廓(即其边界)置于第一层级。对象的内部孔洞(如果有)的轮廓置于第二层级。如果内部有任何对象,则其轮廓再次仅置于第一层级。其孔洞置于第二层级,依此类推。
请考虑黑色背景上的“大白零”的图像。零的外圈属于第一层级,零的内圈属于第二层级。
我们可以用一张简单的图像来解释它。这里我用红色标记了轮廓的顺序,用绿色(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 中。在同一层级中没有轮廓。没有先前的轮廓。子轮廓是轮廓-2。父轮廓是轮廓-0。因此数组是 [-1,-1,2,0]。