OpenCV-Python开发指南(24)---查找并绘制轮廓

什么是图像轮廓

在前面的边缘检测中,虽然我们能够检测出边缘信息,但边缘是不连续的,检测到的边缘并不是一个整体。而图像的轮廓负责将边缘连接起来形成一个整体,用于后续的计算。

需要特别注意,图像的轮廓是非常重要的一个特征信息,通过对图像轮廓的操作,我们能够获取目标图像的大小,位置以及方向等信息。

查找图像轮廓

图像中一个轮廓对应着一系列的点,这些点以某种形式表示图像中的一条曲线。在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个元素来说明轮廓的层次关系。

  1. Next:后一个轮廓的索引编号
  2. Previous:前一个轮廓的索引编号
  3. First_Child:第一个子轮廓的索引编号
  4. Parent:父轮廓的索引编号

如果上面参数都为空,也就是前面讲的各个形状轮廓不交叉的情况。且值为“-1”。

特别注意,轮廓的层次结构是由参数mode决定的,也就是说,使用不同的mode,得到的轮廓编号是不一样的,hierarchy也不一样。

hierarchy:图像的拓扑信息

其返回值为3个参数:image,contours以及hierarchy。而在OpenCV4.X版本中,该函数返回值只有两个:contours,hierarchy。

下面,我们重点讲解参数mode。

参数mode

(1) cv2.RETR_EXTERNAL (只检测外轮廓)

1
假如我们现在我们图像如上图所示,当我们采用 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它也不存在父轮廓

其拓扑结构如下:

11
(2) cv2.RETR_LIST(对检测到的轮廓不建立等级关系)

2
假设我们使用原图如上所示,那么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它也不存在父轮廓

此关系的拓扑结构为:

22
(3) cv2.RETR_CCOMP(建立两个等级的轮廓)

3

当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

其拓扑结构如下:

33

(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()

运行之后,效果如下:

5
不过这种并不是终极目的,我们获取轮廓的目的是将图像分离,也就是我现在向获取这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()

运行之后,我们就能分离出各个图形了。

555

图像轮廓结合按位与获取图像前景对象

除了分离纯粹的图像轮廓用于机器学习识别之外,我们还可以获取图像的前景对象。这里,我们只需要更换图像,修改最后几行代码。

具体代码如下所示:

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()

运行之后,效果如下所示:
66
最左边的是原始图像

中间的是从原始图像得到的人物实心轮廓

最右边的是提取的前景对象人物

猜你喜欢

转载自blog.csdn.net/liyuanjinglyj/article/details/113881398
今日推荐