前面我们通过形态学操作、边缘检测得到了一些图像的边缘。
边缘检测虽然能够检测出边缘,但边缘是不连续的,检测到的边缘并不是一个整体。图像轮廓是指将边缘连接起来形成的一个整体,用于后续的计算。图像轮廓是图像中非常重要的一个特征信息,通过对图像轮廓的操作,我们能够获取目标图像的大小、位置、方向等信息。
轮廓是一些列相连的点组成的曲线,代表了物体的基本外形,相对于边缘,轮廓是连续的,边缘并不全是连续的。
1 图像轮廓的概念
轮廓是一系列相连的点组成的曲线,代表了物体的基本外形。
谈起轮廓不免想到边缘,它们确实很像。简单的说,轮廓是连续的,边缘并不全都连续。
其实边缘主要是作为图像的特征使用,比如可以用边缘特征可以区分脸和手;
而轮廓主要用来分析物体的形态,比如物体的周长和面积等,可以说边缘包括轮廓。
2 轮廓的查找与绘制
2.1 cv2.findContours()寻找轮廓
寻找轮廓的操作一般用于二值图像,所以通常会使用阈值分割或Canny边缘检测先得到二值图。
注意:寻找轮廓是针对白色物体的,一定要保证物体是白色,而背景是黑色,不然很多人在寻找轮廓时会找到图片最外面的一个框。
函数原型:
contours,hierarchy=cv2.findContours(img,mode,method)
参数:
img: 输入原始图。8位单通道。
mode: 轮廓检 索模式,决定了轮廓的提取方式。
- cv2.RETR_EXTERNAL 只检测外轮廓,所有子轮廓被忽略
- cv2.RETR_LIST检测的轮廓不建立等级关系,所有轮廓属于同一等级
- cv2.RETR_CCOMP返回所有的轮廓,只建立两个等级的轮廓。一个对象的外轮廓为第 1 级组织结构。而对象内部中空洞的轮廓为 第 2 级组织结构,空洞中的任何对象的轮廓又是第 1 级组织结构。
- cv2.RETR_TREE建立一个等级树结构的轮廓,返回所有的轮廓,建立一个完整的组织结构的轮廓。
method: 轮廓的近似方法,决定了如何表达轮廓。
- cv2.CHAIN_APPROX_NONE存储所有的轮廓点,相邻的两个点的像素位置差不超过1
- cv2.CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息。
返回值:
1 binary:返回的二值图像
2 contours: 图像中的轮廓,所有轮廓的列表结构,每个轮廓是目标对象边界点的坐标的数组。
- len(contours)是轮廓的个数。
- contours[i]表示第i个轮廓中的像素个数
3 hierarchy: 图像的拓扑信息(轮廓层次)。
在检测轮廓时:有时对象可能位于不同的位置,也有可能一个形状在另外一个形状的内部,这种情况下我们称外部的形状为父,内部的形状为子。
按照这种方式分类,一幅图像中的所有轮廓之间就建立父子关系。这样我们就可以确定一个轮廓与其他轮廓是怎样连接的,比如它是不是某个轮廓的子轮廓,或者是父轮廓。这种关系就是轮廓的层次关系。
在这幅图像中,我给这几个形状编号为 0-5。2 和 2a 分别代表最外边矩形的外轮廓和内轮廓。
在这里边轮廓 0,1,2 在外部或最外边。我们可以称他们为 0 级,简单来说就是他们属于同一级,接下来轮廓 2a,把它当成轮廓 2 的子轮廓。它就成为第 1 级。轮廓 3 是轮廓 2a 的子轮廓,成为第 3 级。轮廓 3a 是轮廓 3 的子轮廓,成为第 4 级,最后轮廓 4,5 是轮廓 3a 的子轮廓,成为5级,这样我们就构建的轮廓的层级关系。
我们再回到返回值中,不管层次结构是什么样的, 每一个轮廓都包含自己的信息。hierarchy使用包含四个元素的数组来表示:
[Next,Previous, First_Child,Parent]。
其中:
Next 表示同一级组织结构中的下一个轮廓。以上图中的轮廓 0 为例,轮廓 1 就是他的 Next。同样,轮廓 1 的 Next 是 2,Next=2。 那轮廓 2 呢?在同一级没有 Next。这时 Next=-1。而轮廓 4 的 Next 为 5,所以它的 Next=5。
Previous 表示同一级结构中的前一个轮廓。轮廓 1 的 Previous 为轮廓 0,轮廓 2 的 Previous 为轮 廓 1。轮廓 0 没有 Previous,所以 Previous=-1。
First_Child 表示它的第一个子轮廓。轮廓 2 的子轮廓为 2a。 所以它的 First_Child 为 2a。那轮廓 3a 呢?它有两个子轮廓。但是我们只要第一个子轮廓,所以是轮 廓 4(按照从上往下,从左往右的顺序排序)。
Parent 表示它的父轮廓。与 First_Child 刚好相反。轮廓 4 和 5 的父轮廓是轮廓 3a。而轮廓 3a 的父轮廓是 3。
注意:如果轮廓没有父轮廓或子轮廓时,则将其置为-1。
2.2 cv.drawContours()绘制轮廓
函数原型:
img=cv2.drawContours(img, contours, contourIdx, color, thickness=None, lineType=None, hierarchy=None, maxLevel=None, offset=None)
参数:
- img: 待绘制轮廓的图像。
- contours: 需要绘制的轮廓。
- contourIdx: 需要绘制的边缘索引,如果为-1,则表示绘制全部轮廓。
- color: 绘制的颜色,opencv中使用的是BGR格式,不是我们平时说的RGB。色彩空间转换。
- thickness: 可选参数,表示绘制轮廓时使用画笔粗细,如果为-1,则表示填充,画出实心轮廓。
- line Type: 可选参数,表示绘制轮廓时所使用的线型。
- hierarchy: 对应函数cv2.findContours()所输出的层次信息。
- maxLevel: 绘制轮廓层次的深度。
- offset: 偏移参数,该参数使轮廓偏移到不同的位置展示出来。
2.3 示例
import cv2
import numpy as npimg = cv2.imread('C:\\Users\\xxx\\Downloads\\contourImg.png') #读取图像
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #转为灰度值图
ret, binary = cv2.threshold(gray,0,255,cv2.THRESH_BINARY) #转为二值图#######轮廓查找############
'''
contours,hierarchy=cv2.findContours(img,mode,method)
参数:
img: 输入原始图。8位单通道。
mode: 轮廓检 索模式,决定了轮廓的提取方式。
cv2.RETR_EXTERNAL 只检测外轮廓
cv2.RETR_LIST检测的轮廓不建立等级关系
cv2.RETR_CCOMP建立两个等级的轮廓
cv2.RETR_TREE建立一个等级树结构的轮廓
method: 轮廓的近似方法,决定了如何表达轮廓。
cv2.CHAIN_APPROX_NONE存储所有的轮廓点
cv2.CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,
只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息。
返回值:
contours: 图像中的轮廓,数组。
len(contours)是轮廓的个数。
contours[i]表示第i个轮廓中的像素个数
hierarchy: 图像的拓扑信息(轮廓层次)。'''
contours, hierarchy = cv2.findContours(binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
n=len(contours) #轮廓个数
print(n)
for i in range(n):
print(len(contours[i])) #轮廓i像素数目
print(contours[i]) #轮廓i每个像素的位置#######轮廓绘制############
'''
img=cv2.drawContours(img, contours, contourIdx, color, thickness=None,
lineType=None, hierarchy=None, maxLevel=None, offset=None)
img: 待绘制轮廓的图像。
contours: 需要绘制的轮廓。
contourIdx: 需要绘制的边缘索引,如果为-1,则表示绘制全部轮廓。
color: 绘制的颜色,opencv中使用的是BGR格式,不是我们平时说的RGB。色彩空间转换。
thickness: 可选参数,表示绘制轮廓时使用画笔粗细,如果为-1,则表示填充,画出实心轮廓。
line Type: 可选参数,表示绘制轮廓时所使用的线型。
hierarchy: 对应函数cv2.findContours()所输出的层次信息。
maxLevel: 绘制轮廓层次的深度。
offset: 偏移参数,该参数使轮廓偏移到不同的位置展示出来。
'''
img2 = img.copy()
img2 = cv2.drawContours(img2,contours,-1,(0,255,0),5)#显示原图,灰度图,二值图,轮廓图
names = ['Original','gray','binary','contours']
images = [img,gray,binary,img2]for i in range(4):
cv2.imshow(names[i],images[i])cv2.waitKey(0)
cv2.destroyAllWindows()
运行结果如下: