什么是图像轮廓
在前面的边缘检测中,虽然我们能够检测出边缘信息,但边缘是不连续的,检测到的边缘并不是一个整体。而图像的轮廓负责将边缘连接起来形成一个整体,用于后续的计算。
需要特别注意,图像的轮廓是非常重要的一个特征信息,通过对图像轮廓的操作,我们能够获取目标图像的大小,位置以及方向等信息。
查找图像轮廓
图像中一个轮廓对应着一系列的点,这些点以某种形式表示图像中的一条曲线。在OpenCV中,它给我们提供了函数cv2.findContours()用于查找图像的轮廓,并能够根据参数返回特定表示方式的轮廓。
该函数的完整定义如下:
def findContours(image, mode, method, contours=None, hierarchy=None):
image:原始图像,所有非0值处理为1,所有0值保持不变
mode:轮廓检索模式
参数 |
含义 |
cv2.RETR_EXTERNAL | 只检测外轮廓 |
cv2.RETR_LIST | 对检测到的轮廓不建立等级关系 |
cv2.RETR_CCOMP | 检测所有轮廓并将它们组织成两级层次关系。上层为外边界,下层为内孔边界。如果内孔内还有一个连同物体,那么这个物体的边界仍然位于顶层 |
cv2.RETR_TREE | 建立一个等级树结构轮廓 |
method:轮廓的近似方法
参数 |
含义 |
cv2.CHAIN_APPROX_NONE | 存储所有的轮廓点,相邻两个点的像素位置差不超过1 |
cv2.CHAIN_APPROX_SIMPLE | 压缩水平方向,垂直方向,对象线方向的元素,只保留该方向的终点坐标。例如,在极端的情况下,一个矩形只需要用4个点来保存轮廓信息 |
cv2.CHAIN_APPROX_TC89_L1 | 使用teh_Chinl chain近似算法的一种风格 |
cv2.CHAIN_APPROX_TC89_KCOS |
使用teh_Chinl chain近似算法的一种风格 |
contours:返回的轮廓。比如一个图像中有3个不交叉的形状:圆,矩形以及正反向。那么它就是一个长度为3的list,也就是有3个轮廓,contours[i][j]就是第i个轮廓的第j个点。
当然,图像的轮廓并不一定是不交叉的,假如几个形状的轮廓相互交叉,那么就形成了父子关系,contours[i]用4个元素来说明轮廓的层次关系。
Next:后一个轮廓的索引编号
Previous:前一个轮廓的索引编号
First_Child:第一个子轮廓的索引编号
Parent:父轮廓的索引编号
如果上面参数都为空,也就是前面讲的各个形状轮廓不交叉的情况。且值为“-1”。
特别注意,轮廓的层次结构是由参数mode决定的,也就是说,使用不同的mode,得到的轮廓编号是不一样的,hierarchy也不一样。
hierarchy:图像的拓扑信息
其返回值为3个参数:image,contours以及hierarchy。而在OpenCV4.X版本中,该函数返回值只有两个:contours,hierarchy。
下面,我们重点讲解参数mode。
参数mode
(1) cv2.RETR_EXTERNAL (只检测外轮廓)
假如我们现在我们图像如上图所示,当我们采用 cv2.RETR_EXTERNAL参数时,hierarchy的拓扑信息为:
[[
[1 -1 -1 -1]
[-1 0 -1 -1]
]]
[1 -1 -1 -1]这4个参数分别对应contours的4个元素。
Next1:也就是表示它后一个轮廓,这里0的后一个轮廓为1
Previous-1:第0个轮廓没有前一个轮廓
First_Child-1:0它不存在子轮廓
Parent-1:0它也不存在父轮廓
[-1 0 -1 -1]对应图像1,意思分别如下:
-1 :1轮廓不存在下一个轮廓
0:第1个轮廓有前一个0轮廓,所以为0
-1 :1它不存在子轮廓
-1:1它也不存在父轮廓
其拓扑结构如下:
(2) cv2.RETR_LIST(对检测到的轮廓不建立等级关系)
假设我们使用原图如上所示,那么hierarchy的值为:
[[
[1 -1 -1 -1]
[2 0 -1 -1]
[-1 1 -1 -1]
]]
[1 -1 -1 -1]含义:
1 :0轮廓的下一个轮廓是1,故返回1
-1:第0个轮廓没有前轮廓,所以为-1
-1 :0它不存在子轮廓
-1:0它也不存在父轮廓
[2 0 -1 -1]含义:
2:1轮廓的下一个轮廓是2,故返回2
0:第1个轮廓前轮廓为0,所以值为0
-1 :1它不存在子轮廓
-1:1它也不存在父轮廓
[-1 1 -1 -1]含义:
-1:2轮廓没有下一个轮廓,故返回-1
1:第2个轮廓前轮廓为1,所以值为1
-1 :2它不存在子轮廓
-1:2它也不存在父轮廓
此关系的拓扑结构为:
(3) cv2.RETR_CCOMP(建立两个等级的轮廓)
当mode参数为 cv2.RETR_CCOMP,原图如上所示,其hierarchy值为:
[[
[1 -1 -1 -1]
[-1 0 2 -1]
[-1 -1 -1 1]
]]
[1 -1 -1 -1]含义如下:
1:0轮廓的下一个轮廓为1,故返回1
-1:第0个轮廓没有前轮廓,所以值为-1
-1 :0它不存在子轮廓
-1:0它也不存在父轮廓
[-1 0 2 -1]含义如下:
-1:1轮廓没有下一个轮廓,故返回-1
0:第1个轮廓前轮廓为0,所以值为0
2 :第1个轮廓的子轮廓为2,所以值为2
-1:第1个轮廓它也不存在父轮廓
[-1 -1 -1 1]含义如下:
-1:2轮廓没有下一个轮廓,故返回-1
-1:第2个轮廓前轮廓不存在,所以值为-1
-1:第2个轮廓也不存在子轮廓
1:第2个轮廓父轮廓为1,所以取值为1
其拓扑结构如下:
(4) cv2.RETR_TREE(建立一个等级树结构轮廓)
当图还是(3)图时,其mode取值为 cv2.RETR_TREE,返回的hierarchy的值为:
[[
[1 -1 -1 -1]
[-1 0 2 -1]
[-1 -1 -1 1]
]]
[1 -1 -1 -1]含义如下:
1:0轮廓下一个轮廓为1,故返回1
-1:第0个轮廓前轮廓不存在,所以值为-1
-1:第0个轮廓也不存在子轮廓
-1:第0个轮廓也不存在父轮廓
[-1 0 2 -1]含义如下:
-1:1轮廓没有下一个轮廓,故返回-1
0:第1个轮廓前轮廓为0,所以值为0
2:第1个轮廓存在子轮廓2,所以取值为2
-1:第1个轮廓不存在父轮廓
[-1 -1 -1 1]含义如下:
-1:2轮廓没有下一个轮廓,故返回-1
-1:第2个轮廓没有前轮廓,所以值为-1
-1:第2个轮廓不存在子轮廓
1:第2个轮廓存在父轮廓1,取值为1
拓扑结构也是(3)图。
绘制图像轮廓
在OpenCV中,它给我们提供了cv2.drawContours()来绘制图像轮廓,其完整定义如下:
def drawContours(image, contours, contourIdx, color, thickness=None, lineType=None, hierarchy=None, maxLevel=None, offset=None):
image:待绘制轮廓的图像
contours:需要绘制的轮廓
contourIdx:需要绘制的边缘索引,-1表示绘制所有,0以及正数表示绘制对应的轮廓。
color:绘制的颜色
thickness:绘制轮廓的画笔,取值为“-1”表示绘制实心轮廓
lineType:绘制轮廓的线型
hierarchy:对应函数cv2.findContours()所输出的层次信息
maxLevel:控制所绘制的轮廓层次的深度,如果值为0表示只绘制第0层
offset:偏移参数,是绘制的轮廓偏移一定的位置
下面,我们来实战绘制图像的轮廓,具体代码如下所示:
import cv2 img = cv2.imread("24.jpg") cv2.imshow("img", img) # 转换为灰度图像 img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换为二值图 ret, binary = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY) # 获取图像的轮廓参数 contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) result = cv2.drawContours(img, contours, -1, (0, 0, 255), 5) cv2.imshow("result", result) cv2.waitKey() cv2.destroyAllWindows()
运行之后,效果如下:
不过这种并不是终极目的,我们获取轮廓的目的是将图像分离,也就是我现在向获取这3个轮廓的单一子图怎么办呢?直接上代码吧。
import cv2 import numpy as np img = cv2.imread("24.jpg") cv2.imshow("img", img) # 转换为灰度图像 img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换为二值图 ret, binary = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY) # 获取图像的轮廓参数 contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) result = [] for i in range(len(contours)): temp = np.zeros(img.shape, dtype=np.uint8) result.append(temp) result[i] = cv2.drawContours(result[i], contours, i, (0, 0, 255), 5) cv2.imshow(str(i), result[i]) cv2.waitKey() cv2.destroyAllWindows()
运行之后,我们就能分离出各个图形了。
图像轮廓结合按位与获取图像前景对象
除了分离纯粹的图像轮廓用于机器学习识别之外,我们还可以获取图像的前景对象。这里,我们只需要更换图像,修改最后几行代码。
具体代码如下所示:
import cv2 import numpy as np img = cv2.imread("4.jpg") cv2.imshow("img", img) # 转换为灰度图像 img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换为二值图 ret, binary = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY) # 获取图像的轮廓参数 contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) mask = np.zeros(img.shape, dtype=np.uint8) mask = cv2.drawContours(mask, contours, -1, (255, 255, 255), -1) cv2.imshow("MASK",mask) result=cv2.bitwise_and(img,mask) cv2.imshow("result",result) cv2.waitKey() cv2.destroyAllWindows()
运行之后,效果如下所示:
最左边的是原始图像
中间的是从原始图像得到的人物实心轮廓
最右边的是提取的前景对象人物