这是对于 OpenCV 官方文档中 图像处理 的学习笔记。学习笔记中会记录官方给出的例子,也会给出自己根据官方的例子完成的更改代码,同样彩蛋的实现也会结合多个知识点一起实现一些小功能,来帮助我们对学会的知识点进行结合应用。
如果有喜欢我笔记的请麻烦帮我关注、点赞、评论。谢谢诸位。
学习笔记:
学习笔记目录里面会收录我关于OpenCV系列学习笔记博文,大家如果有什么不懂的可以通过阅读我的学习笔记进行学习。
【OpenCV学习笔记】- 学习笔记目录
内容
- 了解轮廓的层次结构,即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 的父级)。所以让它位于层次结构1中。同样,轮廓3 是轮廓2 的子级,它位于下一个层次结构中。最后,轮廓 4,5 是轮廓 3a 的子级,它们位于最后一个层次结构级别。从我给盒子编号的方式来看,我会说轮廓4是轮廓3a的第一个子元素(也可以是轮廓5)。
我提到这些是为了理解诸如相同层次结构级别、外部轮廓、子轮廓、父轮廓、第一个子轮廓等术语。现在让我们进入 OpenCV。
OpenCV 中的层次结构表示
每个轮廓都有自己的信息,包括它的层次结构、谁是其子级、谁是其父级等。OpenCV 将其表示为包含四个值的数组:[Next、Previous、First_Child、Parent]
- Next 表示相同等级的下一个轮廓。例如,在我们的图片中取轮廓 0。谁是同一级别的下一个轮廓?它是轮廓1。因此,只需输入 Next = 1。类似地,对于 轮廓1,下一个是轮廓-2。所以Next = 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 等标志是什么意思?
轮廓检索模式
1.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]]])
如果不使用任何层次结构功能,这是在代码中使用的不错选择。
2.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]]])
如果只想提取外部轮廓,则可以使用此标志。在某些情况下可能有用。
3.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]]])
4.RETR_TREE
这就是最后一个人,完美先生。它检索所有轮廓并创建完整的家庭层次结构列表。它甚至告诉我们,谁是爷爷、父亲、儿子、孙子,甚至更远……😃。
例如,我拍摄了上面的图像,重写了 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]]])
总结
通过上面的轮廓检索模式说明,我们了解了每个模式的检索结果分布。在不同场景下我们可以采用不同的轮廓检索模式。
例如:
RETR_TREE模式: 我们需要针对每个轮廓进行分析并且需要知道轮廓层级的时候,可以通过这种模式获取每一个轮廓及层级所属;
RETR_LIST模式: 我们需要对每个轮廓进行特征提取,并且不需要知道轮廓层级的时候,可以使用该模式;
RETR_EXTERNAL模式: 我们仅需要检测内容外部轮廓的时候,可以采用该模式检索最外层的轮廓;
RETR_CCOMP模式: 抱歉,我还没有想好使用场景。希望以后可以学习到。