目录
轮廓的定义
轮廓是一系列相连的点组成的曲线,代表了物体的基本外形。
轮廓和边缘的区别
谈起轮廓不免想到边缘,它们确实很像。简单的说,轮廓是连续的,边缘并不全都连续(下图)。其实边缘主要是作为图像的特征使用,比如可以用边缘特征可以区分脸和手,而轮廓主要用来分析物体的形态,比如物体的周长和面积等,可以说边缘包括轮廓。
寻找轮廓的方法和经验
寻找轮廓的操作一般基于二值化图,所以通常会使用阈值分割或Canny边缘检测先得到二值图。
经验之谈:寻找轮廓是针对白色物体的,一定要保证物体是白色,而背景是黑色,不然很多人在
寻找轮廓时会找到图片最外面的一个框。
先看一个demo,图片:
代码:
import cv2
img = cv2.imread('handwriting.jpg')
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 使用Otsu自动阈值,注意用的是cv2.THRESH_BINARY_INV
ret, thresh = cv2.threshold(
img_gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
# cv2.imshow("thresh", thresh)
"""
寻找轮廓方法三个参数:输入图像,轮廓检索模式,轮廓近似方法
返回值contours是轮廓本身contours,还有一个是每条轮廓对应的属性hierarchy,下边详解
"""
contours, hierarchy = cv2.findContours(
thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
print(len(contours)) # 结果应该为2,两个轮廓
cnt = contours[1]
"""
绘制轮廓:
第一个参数是一张图片,可以是原图或者其他。
第二个参数是轮廓,也可以说是cv2.findContours()找出来的点集,一个列表。
第三个参数是对轮廓(第二个参数)的索引,当需要绘制独立轮廓时很有用,若要全部绘制可设为-1。
接下来的参数是轮廓的颜色和厚度。
"""
cv2.drawContours(img, [cnt], 0, (0, 0, 255), 2)
cv2.imshow('contours', img)
cv2.waitKey(0)
findContours()详解
使用 cv2.findContours() 寻找轮廓,findContours()有三个参数:输入图像,轮廓检索模式,轮廓近似方法,返回值contours是轮廓本身contours,还有一个是每条轮廓对应的属性hierarchy,下边详解 。
(敲黑板划重点)理解轮廓层级、等级:
图中总共有8条轮廓,2和2a分别表示外层和里层的轮廓,3和3a也是一样。从图中看得出来:
- 轮廓0/1/2是最外层的轮廓,我们可以说它们处于同一轮廓等级:0级
- 轮廓2a是轮廓2的子轮廓,反过来说2是2a的父轮廓,轮廓2a算一个等级:1级
- 同样3是2a的子轮廓,轮廓3处于一个等级:2级
- 类似的,3a是3的子轮廓,等等…………
如果我们打印出 cv2.findContours() 函数的返回值hierarchy,会发现它是一个包含4个值的数组:
[Next, Previous, First Child, Parent]
- Next:与当前轮廓处于同一层级的下一条轮廓,举例来说,前面图中跟0处于同一层级的下一条轮廓是1,所以Next=1;同理,对轮廓1来说,Next=2;那么对于轮廓2呢?没有与它同一层级的下一条轮廓了,此时Next=-1。
- Previous:与当前轮廓处于同一层级的上一条轮廓,跟前面一样,对于轮廓1来说,Previous=0;对于轮廓2,Previous=1;对于轮廓1,没有上一条轮廓了,所以Previous=-1。
- First Child:当前轮廓的第一条子轮廓,比如对于轮廓2,第一条子轮廓就是轮廓2a,所以First Child=2a;对轮廓3a,First Child=4。
- Parent:当前轮廓的父轮廓
比如2a的父轮廓是2,Parent=2;轮廓2没有父轮廓,所以Parent=-1。
轮廓的检索方法模式4种:
- CV2.RETR_EXTERNAL 表示只检测外轮廓。这种方式只寻找最高层级的轮廓,也就是它只会找到前面我们所说的3条0级轮廓:
- CV2.RETR_LIST 检测的轮廓不建立等级关系。这是最简单的一种寻找方式,它不建立轮廓间的子属关系,也就是所有轮廓都属于同一层级。这样, hierarchy中的后两个值[First Child, Parent]都为-1。比如同样的图,我们使用cv2.RETR_LIST来寻找轮廓:
因为没有从属关系,所以轮廓0的下一条是1,1的下一条是2……
- CV2.RETR_CCOMP 建立两个等级的轮廓,上面的一层为外边界,里面的一层为内孔的边界信息。如果内孔内还有一个连通物体,这个物体的边界也在顶层。cv2.RETR_CCOMP比较难理解,但其实也很简单:它把所有的轮廓只分为2个层级,不是外层的就是里层的。结合代码和图片,我们来理解下:
图中括号里面1代表外层轮廓,2代表里层轮廓。比如说对于轮廓2,Next就是4,Previous是1,它有里层的轮廓3,所以First Child=3,但因为只有两个层级,它本身就是外层轮廓,所以Parent=-1。大家可以针对其他的轮廓自己验证一下。
- CV2.RETR_TREE 建立一个等级树结构的轮廓。前面已经详细说明过了
轮廓的近似办法有4种:
CV2.CHAIN_APPROX_NONE 存储所有的轮廓点,相邻的两个点的像素位置差不超过1,即max(abs(x1-x2), abs(y2-y1))<=1。
CV2.CHAIN_APPROX_SIMPLE 压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息。
CV2.CHAIN_APPROX_TC89_L1 和 CV2.CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法。
绘制轮廓cv2.drawContours()函数比较简单就不多说了,看上边demo就行。