OpenCV-py学习笔记(四)—— 轮廓检测

Posted by 刘知安 on 2019-10-26
文章目录
  1. 轮廓检测
    1. 参考资料
    2. 0. 什么是轮廓?
    3. 1. 基本使用
    4. 2. 基本使用示例
    5. 3. 轮廓的常用特征和性质(外接矩形、等效直径等)
    6. 4. 轮廓的层级(contours hierarchy)

@[toc]
这几天一直在学习特征点检测、角点检测相关的内容,更新的略慢,这篇博文将主要介绍一下图像的轮廓方面的知识和openCV中的相关操作。

轮廓检测

参考资料

0. 什么是轮廓?

所谓轮廓,简单地可以解释为,一些连续的,沿着曲线边界的,点的集合,这些点拥有相同的颜色或者亮度。【In English, Contours can be explained simply as a curve joining all the continuous points (along the boundary), having same color or intensity。】这些轮廓往往在目标检测和目标识别中很有作用。

下面是几个在使用opencv作轮廓检测时需要注意的点:

  • 为了更精确地提取轮廓,请使用二值图。也就是说,在使用轮廓提取函数前,请将源图片运用阈值进行二值化(cv2.threshold())或者采用Canny边缘检测
  • findContours 函数会修改源图片,如果希望在轮廓检测后继续使用源图片,务必提前保存在另一个变量中。
  • 在OpenCV中,轮廓检测视作从黑色背景中提取白色的物体,所以,在结果中,白色表示物体,黑色表示背景。

1. 基本使用

下面是一个轮廓检测的基本使用例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def find_contours_of_binary_image():
im = cv2.imread('rectangle.jpg')
imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray, 127, 255, 0)

image, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)


print("一共检测到%d个轮廓" % len(contours))
for i in range(len(contours)):
print("第%d个轮廓:" % (i + 1))
print(contours[i])

# -1 代表画出所有轮廓
res = cv2.drawContours(im, contours, -1, (0, 255, 0), thickness=3)
img_show(res)

提取轮廓的步骤大概会是这样子:

  1. 读取源图片,并转化为灰度图
  2. 运用threshold将灰度图片二值化(也可以使用Canny边缘检测)
  3. 使用findContours()函数找到所有的轮廓
  4. 使用drawContours()函数将轮廓画出来

这里对findContours()函数drawContours()函数这两个方法的参数做一个解释:

  • image, contours, hierarchy = findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE):

    ==输入参数==,第一个参数是待提取轮廓的源图片(是一个二值图),第二个参数指的是轮廓提取的模式,第三个参数是怎么去近似轮廓的模式;暂时不理解没关系,继续往下看示例部分。

    ==输出参数==,第一个是原图片,第二个是所有的轮廓,用的是python内置的list,如果一张图里面有n个轮廓,那就是一个长度为n的list,而list中的每一项就是一些些点,具体来说,就是一个shape为[?x1x2]的numpy数组,?为轮廓上的点的数目,而后面的1x2自然就是每个点的x,y坐标了。第三个参数是一个轮廓检索时会用到的下标,后面我们会讲到。

  • res = cv2.drawContours(im, contours, -1, (0, 255, 0), thickness=3):
    ==输入参数==,第一个参数是源图片(要在哪个图片上画出轮廓),第二个参数指的是画哪个轮廓,-1表示全部画出,第三个参数是轮廓画笔的颜色,后面有些可选参数,比如画笔的粗细,这里选的是3.

    ==输出参数==,很简答,就是结果图片了。

2. 基本使用示例

所谓一图胜千言,这里是几个示例:
在这里插入图片描述

有点朋友可能对两个函数的输入输出还是有点懵逼,这里再结合例子详细说明。我们说,轮廓是对一个物体的边界的一个描述对吧?比如一条直线,一个矩形的边框。假如有一条直线,我们是不是真的需要把直线上的每个点都找出来呢,或者说,我们是不是真的需要把沿着矩形边框上的每个点也给他找出来?当然不要,2点确定一条直线,4点确地一个矩形。而这就是findContours()中第三个输入参数的意义。(a type of approximation)

我做了一个简单的图片,运行上面的示例代码,输出信息和图片如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
一共检测到2个轮廓
1个轮廓:
[[[233 89]]

[[233 165]]

[[363 165]]

[[363 89]]]
2个轮廓:
[[[ 50 55]]

[[ 50 186]]

[[128 186]]

[[128 55]]]

在这里插入图片描述
假如你说,哎,我就是非要把轮廓上每个点找出来,当然可以!参数改为cv2.CHAIN_APPROX_NONE(不近似的意思),效果是一样的,只不过点多了些(大概几百个)。至于其他的近似方法,可以查看官方文档

3. 轮廓的常用特征和性质(外接矩形、等效直径等)

这里就不细说了,看>>官方文档<<吧!

4. 轮廓的层级(contours hierarchy)

这个非常有必要详细说明,这也是findContours()函数第二个输入参数和最后一个输出参数的含义,这表明了各轮廓的父-子关系。

什么是轮廓的层级?
一般来说,我们用findContours()函数来找一个图像中的所有物体的轮廓,对吧? 当一张图里面有多个物体,就算是一个物体,它也会有多个组成部分,所以,在一定程度上,这些物体的轮廓之间必然相互之间存在一个关系。那怎么表示这种关系呢?可以类似层级的思想,在外面的是父亲,在里面的是孩子。

打个比方,一个人,有两个眼睛,在找轮廓时,我肯定会先找最大的那个,也就是人这个整体,然后人的眼睛又是在人这个整体中的一部分,于是我们就称眼睛是人体的子轮廓,反过来就是一个父亲的关系了。当然,有父子肯定就会存在兄弟,左眼睛和右眼睛就是兄弟关系了。

看下面这个图片:
在这里插入图片描述

最外面一层,记为第0层,有轮廓0、1、2,轮廓2的下一级是轮廓2a,轮廓2a的下一级是3,轮廓3的下一级是轮廓3a,3a的下一级有轮廓4、5。

在OpenCV中,用一个4个数字的元组来表示它们的层级关系,分别是[Next, Previous, First_Child, Parent],next是同一级的下一个轮廓,previous是同一级的上一个轮廓,first_child是下一级的第一个子轮廓,parent则是父轮廓了。

==说明:如果不存在,则用-1表示。==

还记得findContours()的第二个输入参数和输出hierarchy吗?

Contour Retrieval Mode:

  1. CV2.RETR_LIST
    这是最简单的提取轮廓的方式了,这种方式下,也不管你什么父-子关系,提取出来所有找到的轮廓就完事儿了,然后全部统统简单的用一个list返回给你,所以,安装OpenCV的表达父子关系的方式,元组的最后两个取值必然都是-1了。而且前两个元素的取值在各个item中必然是“连续的”。

在这里插入图片描述

继续这个图,得到的hierarchy结果如下:

1
2
3
4
5
6
7
8
[[[ 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]]]

而8个轮廓的像素依次如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
一共检测到8个轮廓
0个轮廓:
[[[365 174]]
[[365 283]]
[[564 283]]
[[564 174]]]
1个轮廓:
[[[148 128]]
[[148 277]]
[[273 277]]
[[273 128]]]
2个轮廓:
[[[ 83 94]]
[[ 84 93]]
[[589 93]]
[[590 94]]
[[590 316]]
[[589 317]]
[[ 84 317]]
[[ 83 316]]]
3个轮廓:
[[[ 79 89]]
[[ 79 321]]
[[594 321]]
[[594 89]]]
4个轮廓:
[[[ 25 62]]
[[ 26 61]]
[[622 61]]
[[623 62]]
[[623 344]]
[[622 345]]
[[ 26 345]]
[[ 25 344]]]
5个轮廓:
[[[ 21 57]]
[[ 21 349]]
[[627 349]]
[[627 57]]]
6个轮廓:
[[[ 18 13]]
[[ 17 14]]
[[ 17 16]]
[[ 18 17]]
[[135 17]]
[[136 16]]
[[136 14]]
[[135 13]]]
7个轮廓:
[[[217 11]]
[[216 12]]
[[216 14]]
[[217 15]]
[[509 15]]
[[510 14]]
[[510 12]]
[[509 11]]]
  1. CV2.RETR_TREE

这种模式下,OpenCV会为我们处理好各轮廓之间的父-子关系,hierarchy的结果如下:

1
2
3
4
5
6
7
8
[[[ 6 -1  1 -1]
[-1 -1 2 0]
[-1 -1 3 1]
[-1 -1 4 2]
[ 5 -1 -1 3]
[-1 4 -1 3]
[ 7 0 -1 -1]
[-1 6 -1 -1]]]

各轮廓的标记如下,红色数字表示该轮廓的索引,黄色数字表示概论课所处的层级,白色的四元组是该轮廓与其他轮廓间的关系表达方式。
在这里插入图片描述

  1. RETR_CCOMP
  2. RETR_EXTERNAL

这两个好像用的不多,详情参见官网吧,我也没仔细看。

如有不对,欢迎指出,thank you~