Bootstrap

OpenCV-Python -- Contours Hierarchy

学习目标

这一部分,我们将学习轮廓的层次结构,比如轮廓中父子关系(parent-child)。

理论

在轮廓的前几部分内容中,OpenCV提供了多个关于轮廓的函数。在轮廓提取函数中,cv2.findContours() ,我们传递参数 Contour Retrieval Mode(轮廓检索模式)。通常的参数为:cv2.RETR_LISTcv2.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)。

考虑下面的图像:

在这里插入图片描述
我们将图中的形状进行的数字标号,22a分别表示最外层边框的最外和最内层的轮廓。这里,0,1,2表示外部(external)或者最外层(outermost)。我们可以简单称它们为 hierarchy-0,它们属于同一层次。

那么,contour-2a 认为是 contour-2 的孩子(反过来说,contour-2是其父母)。所以称之为 hierarchy-1. 同理,contour-3contour-2 的孩子,处于下一个层次(hierarchy-2)。最后,contour-4,5是contour-3a的孩子,它们是最后一个层次。在这种规则下,我们可以说 contour-4contour-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-0previous=-1.

First_Child:表示第一个孩子轮廓

对于 contour-2,孩子轮廓为 contour-2a。所以,相应的值为 contour-2a 的索引值。对于 contour-3a,有2个孩子轮廓,但是我们只取第一个孩子轮廓,也就是 contour-4,所以 contour-3aFirst_Child=4.

Parent:父母轮廓的索引
First_Child情况相反。比如,contour-4contour-5 的父母轮廓为 contour-3a.

Contour Retrieval Mode

1. RETR_LIST

这是最简单的四个参数之一,仅仅检测所有的轮廓,不会创建任何父母-孩子(parent-child)关系。所以,这种情况下,父母和孩子仅仅是轮廓,属于同一层次。

那么,相应的第三个和第四个参数通常为 -1,而且,NextPrevious,具有他们自己的相关的值。

下面的结果:每一行表示相关轮廓具有层次细节。比如,第一行是 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-3Next=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]]]
;