@[toc]
这几天一直在学习特征点检测、角点检测相关的内容,更新的略慢,这篇博文将主要介绍一下图像的轮廓方面的知识和openCV中的相关操作。
轮廓检测
参考资料
- OpenCV-Python Tutorial
- Computer Vision: Algorithms and Applications
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 | def find_contours_of_binary_image(): |
提取轮廓的步骤大概会是这样子:
- 读取源图片,并转化为灰度图
- 运用threshold将灰度图片二值化(也可以使用Canny边缘检测)
- 使用
findContours()函数
找到所有的轮廓 - 使用
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:
- 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 | 一共检测到8个轮廓 |
- 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]]]
各轮廓的标记如下,红色数字表示该轮廓的索引,黄色数字表示概论课所处的层级,白色的四元组是该轮廓与其他轮廓间的关系表达方式。
- RETR_CCOMP
- RETR_EXTERNAL
这两个好像用的不多,详情参见官网吧,我也没仔细看。
如有不对,欢迎指出,thank you~