学习目标
这一部分,我们将学习轮廓的层次结构,比如轮廓中父子关系(parent-child)。
理论
在轮廓的前几部分内容中,OpenCV提供了多个关于轮廓的函数。在轮廓提取函数中,cv2.findContours()
,我们传递参数 Contour Retrieval Mode
(轮廓检索模式)。通常的参数为:cv2.RETR_LIST
,cv2.RETR_TREE
。但是这些参数是什么意思呢?
contours, hierarchy = cv2.findContours(img_bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
contours:表示提取的轮廓。
hierarchy:表示轮廓的层次结构。
但是,之前的教程中并没有利用这个返回值。那么它是什么,代表什么意思?下面我们将介绍该返回值的含义和用法。
What is Hierarchy?
通常我们使用函数cv2.findContours()
查找图像中的目标对象。但是,目标的分布都是不一样的。比如,有的形状分布在其它形状内部,就像网状结构一样。这种情况下,我们称外部的形状为parent
,内部的为child
。在这种规则下,形状之间是存在关系的。那么我们可以一个轮廓是如何与另一个轮廓连接方式,比如其它轮廓孩子,或则是否是父母等。这种关系称为层次结构(Hierarchy
)。
考虑下面的图像:
我们将图中的形状进行的数字标号,2
和2a
分别表示最外层边框的最外和最内层的轮廓。这里,0,1,2表示外部(external)或者最外层(outermost)。我们可以简单称它们为 hierarchy-0,它们属于同一层次。
那么,contour-2a 认为是 contour-2 的孩子(反过来说,contour-2是其父母)。所以称之为 hierarchy-1. 同理,contour-3 是 contour-2 的孩子,处于下一个层次(hierarchy-2)。最后,contour-4,5是contour-3a的孩子,它们是最后一个层次。在这种规则下,我们可以说 contour-4 是 contour-3a 的第一个孩子(当然,contour-5也可以是第一个孩子)。
那么我们可以利用一些术语表达轮廓直接的关系,比如 same hierarchy level
, external contour
, child contour
, parent contour
, first child
等。
Hierarchy Representation in OpenCV
每一个轮廓都有其信息,比如它的孩子是什么,父母是是什么等。OpenCV中使用四元素的数组表达:[Next, Previous, First_Child, Parent]
.
Next: 表示同层次的下一个轮廓
比如,以上图为例,contour-0 的下一个同等层次的轮廓为contour-1,所以
Next=1
,同理,contour-1 的下一个轮廓为contour-2,所以Next=2
. 但是,对于 contour-2 而言,没有同等层次的下一个轮廓,所以Next=-1
. 而 contour-4 的下一个为 contour-5,所以Next=5
.
Previous:表示同等层次下上一个轮廓
与上面的情况类似,contour-1 的上一个轮廓为 contour-0。类似,contour-2 的前一个为 contour-1。对于 contour-0,
previous=-1
.
First_Child:表示第一个孩子轮廓
对于 contour-2,孩子轮廓为 contour-2a。所以,相应的值为 contour-2a 的索引值。对于 contour-3a,有2个孩子轮廓,但是我们只取第一个孩子轮廓,也就是 contour-4,所以 contour-3a,
First_Child=4
.
Parent:父母轮廓的索引
与First_Child情况相反。比如,contour-4 和 contour-5 的父母轮廓为 contour-3a.
Contour Retrieval Mode
1. RETR_LIST
这是最简单的四个参数之一,仅仅检测所有的轮廓,不会创建任何父母-孩子(parent-child)关系。所以,这种情况下,父母和孩子仅仅是轮廓,属于同一层次。
那么,相应的第三个和第四个参数通常为 -1,而且,Next 和 Previous,具有他们自己的相关的值。
下面的结果:每一行表示相关轮廓具有层次细节。比如,第一行是 contour-0 的相关的四元素数组,Next 轮廓是1,所以 Next=1
. 没有前一个轮廓,所以,Previous=0。同理另外两个一样,均为 -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
如果使用这个标志,那么仅仅返回外轮廓。所有的子轮廓放在最后。我们可以说,在这样的规则下,仅仅关注最外的轮廓。不关注内部轮廓。
那么在上面的图中,我们可以找到几个最外部轮廓,例如hierarchy-0,只有3个轮廓,contour,0,1,2. 下面的结果是在该参数下运行的例子:
>>> hierarchy
array([[[ 1, -1, -1, -1],
[ 2, 0, -1, -1],
[-1, 1, -1, -1]]])
如下图,只有三个轮廓返回,均是层次0的轮廓(最外层轮廓):
3. RETR_CCOMP
这个标志表示检索所有的轮廓,并把这些轮廓分为2个层次。比如,目标的外边界为hierarchy-1
,内边界(洞的轮廓)为hierarchy-2
. 如果任何目标在边框的内部,那么边界为hierarchy-1
,内部的洞为hierarchy-2
.
考虑黑色背景上的大的数字0,外圈是第一个层次,内圈是第二个层次,见下图:
我们使用一个简单的图像来解释。下图中,我们用红色标记轮廓的顺序,绿色表示该轮廓属于的层次(1或者2)。
如上图所示,考虑第一个轮廓,contour-0,属于hierarchy-1
,它有2个洞,分别contour-1,2,它们属于层次2。所以,对于contour-0而言,下一个同层次的轮廓是contour-3。没有Previous
没有值,为-1。但是,它的第一个孩子是层次2中的一个洞。没有父母,因为他是层次1,所以层次数组为[3, -1, 1, -1]
.
下面我们考虑contour-1,属于层次2. 同等层次的下一个轮廓为另外一个洞,即是contour-2. 没有Previous
值,没有孩子,但是父母为contour-0,所以数组为[2, -1, -1, 0]
.
Contour-3:Next=5
(同等层次的下一个轮廓编号),Previous=0
,孩子为contour-4,没有父母,所以数组为[5, 0, 4, -1]
.
Contour - 4 : 属于层次2,在contour-3之下,没有兄弟姐妹。所以没有next,也没有previous,没有孩子,父母是contour-3,数组为[-1, -1, -1, 3]
.
最终的数组如下:
[[[ 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
该参数会检索所有的轮廓,并且创建完整的家庭层次表(family hierarchy list). 包含完整的关系,比如父母,祖父母,儿子,孙子等等。
同样以上图为例,得到如下:
contour-0:属于层次0,next值为contour-7,没有previous值,孩子是contour-1,没有父母,所以数组为[7, -1, 1, -1]
.
contour-2:属于层次1,同等层次下没有轮廓。没有previous值,孩子是contour-2,父母为contour-1,数组为[-1 -1 3 1]
.
完整的结果如下:
[[[ 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]]]