OpenCV 例程200篇 总目录-202205更新
【youcans 的 OpenCV 例程200篇】194.寻找图像轮廓(cv.findContours)
1. 轮廓
轮廓是一系列相连的像素点组成的曲线,代表了物体的基本外形。轮廓常用于形状分析和物体的检测和识别。
边缘检测根据灰度的突变检测边界,但检测到的边缘通常还是零散的片段,并未构成整体。从背景中分离目标,就要将边缘像素连接构成轮廓。也就是说,轮廓是连续的,边缘不一定都连续。边缘主要是作为图像的特征使用,而轮廓主要用来分析物体的形态。
1.1 轮廓的查找
OpenCV 提供函数 cv.findContours() 从二值图像中寻找轮廓,函数 cv2.drawContours() 绘制轮廓。
函数说明:
cv.findContours(image, mode, method[, contours[, hierarchy[, offset]]]) → contours, hierarchy
cv.findContours() 对黑色背景的二值图像操作,通常会先使用阈值分割或边缘检测得到二值图像。
参数说明:
- image:原始图像,8 位单通道二值图像
- mode: 轮廓检索模式
- cv.RETR_EXTERNAL:只检索最外层轮廓
- cv.RETR_LIST:检索所有轮廓,不建立任何层次关系
- cv.RETR_CCOMP:检索所有轮廓,并将其组织为两层, 顶层是各部分的外部轮廓,次层是内层轮廓
- cv.RETR_TREE:检索所有轮廓,并重建嵌套轮廓的完整层次结构
- cv.RETR_FLOODFILL:漫水填充法(泛洪填充)
- method: 轮廓近似方法
- cv.CHAIN_APPROX_NONE:输出轮廓的每个像素点
- cv.CHAIN_APPROX_SIMPLE:压缩水平、垂直和斜线,仅保留这些线段的端点
- cv.CHAIN_APPROX_TC89_L1:应用 Teh-Chin 链近似算法 L1
- cv.CHAIN_APPROX_TC89_KCOS:应用 Teh-Chin 链近似算法 KCOS
- contours:检测到的所有轮廓,列表格式,每个轮廓存储为包含边界点坐标 (x,y) 的点向量
- 列表(LIST)长度为 L,对应于找到的 L 个轮廓,按 0,…L-1 顺序排列
- 列表中的第 i 个元素是一个形如 (k,1,2) 的 Numpy 数组,表示第 i 个轮廓,k 是第 i 个轮廓的边界点的数量
- 数组 contours[i] 是构成第 i 个轮廓的各边界点坐标 (x,y) 的点向量
- 注意边界点的坐标表达形式是 (x,y),而不是 OpenCV 中常用的像素坐标表达形式 (y,x)。
- hierarchy:轮廓的层次结构和拓扑信息,是一个形如 (1,k,4) 的 Numpy 数组
- k 对应于找到的轮廓数量
- hierarchy[0][i] 表示第 i 个轮廓的层次结构,是包含 4个值的数组 [Next, Previous, First Child, Parent],分别代表第 i 个轮廓的同层的后一个轮廓、同层的前一个轮廓、第一个子轮廓、父轮廓的编号
- offset:每个轮廓点的偏移量,可选项,
注意事项:
- 原始图像 image 必须是单通道图像,其中的非 0 像素都被视为 1,因此图像被视为二值图像。在查找轮廓之前,可以使用 threshold、inRange、Canny 等方法从灰度或彩色图像中创建二值图像。
- 通常将轮廓近似方法 method 设为 CHAIN_APPROX_SIMPLE,对轮廓的水平、垂直和斜线进行压缩,只保留这些线段的端点,因此轮廓是由一系列线段连接而成的闭合曲线。
- 每一个轮廓都是一个 Numpy 数组,包含对象边界点 (x,y) 的坐标,注意不是 (y,x)。
- 如果从图像 ROI 中提取轮廓,然后在图像上下文中进行分析,偏移量 offset 将非常有用。
- 轮廓的层次结构的表达嵌套在顶级数组中,可以使用 hierarchy[0][i] 访问第 i 个轮廓的层次结构元素。
- 对应于第 i 个轮廓 contours[i],hierarchy [i][0]~hierarchy[i][3] 分别代表它的同层的后一个轮廓、同层的前一个轮廓、第一个子轮廓、父轮廓的编号。
使用说明:
函数 cv.findContours() 是常用的寻找轮廓函数,但在使用中要注意几个问题:
**注意 1:**OpenCV不同版本中本函数的返回值不同。OpenCV 3 中返回 3个值 image, contours, hierarchy,OpenCV2、OpenCV4、OpenCV5 中函数返回 2个值 contours, hierarchy。
**注意2:**OpenCV 3.2 之前的早期版本中,函数会对输入图像“原地”修改——这是 OpenCV5 官方文档中的提示,但本文作者认为 OpenCV2 及其它版本到底是否修改原图存疑。为了避免不同版本带来的错误,建议都采用原始图像的 copy 作为函数的输入图像。
**注意3:**cv.findContours() 对黑色背景的二值图像操作,也就是默认需要提取轮廓的目标为亮色、浅色。如果背景为亮色、浅色,如书本通常是白色背景深色文字,要在二值处理时选择取反操作。
**注意4:**轮廓边界点的坐标表达形式是 (x,y),而不是 OpenCV 中常用的像素坐标表达形式 (y,x)。
**注意5:**第 i 个轮廓 contours[i],hierarchy [i] 按照先外层后内层的顺序从 0~L-1 依次编号,但编号顺序并不体现嵌套结构关系。轮廓之间的嵌套结构关系是由 hierarchy [i] 的数值来表达的。
**注意6:**查找轮廓时,推荐使用边缘检测 而不是阈值处理 cv.threshold() 得到二值图,其原因是:
- 提取边缘与阈值处理不同,边缘提取可以识别图片中目标的形状、轮廓,而不是简单的区分出图片中的高光与暗调;
- 使用边缘检测提取结果的白点数量更少,可以提高轮廓检测的性能;
- 边缘检测也可以使用 cv.Sobel,cv.Scharr,cv.Laplacian 等方法,但 cv.Canny 的边缘检测效果往往更好。
例程 12.1:查找图像轮廓
img = cv2.imread("../images/pattern1.png", flags=1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_OTSU+cv2.THRESH_BINARY_INV)
plt.figure(figsize=(9, 6))
plt.subplot(131), plt.axis('off'), plt.title("Origin")
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.subplot(132), plt.axis('off'), plt.title("BinaryInv")
plt.imshow(binary, 'gray')
binary, contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contourPic = img.copy()
contourPic = cv2.drawContours(contourPic, contours, -1, (0, 0, 255), 2)
print("len(contours) = ",len(contours))
for i in range(len(contours)):
print("i=", i, contours[i].shape)
print("hierarchy.shape : ", hierarchy.shape)
print(hierarchy)
plt.subplot(133), plt.axis('off'), plt.title("External contour")
plt.imshow(cv2.cvtColor(contourPic, cv2.COLOR_BGR2RGB))
plt.tight_layout()
plt.show()
运行结果:
len(contours) = 6
hierarchy.shape : (1, 6, 4)
i= 0 ,contours[i]: (5, 1, 2) ,hierarchy[0][i] = [ 1 -1 -1 -1]
i= 1 ,contours[i]: (5, 1, 2) ,hierarchy[0][i] = [ 2 0 -1 -1]
i= 2 ,contours[i]: (4, 1, 2) ,hierarchy[0][i] = [-1 1 3 -1]
i= 3 ,contours[i]: (8, 1, 2) ,hierarchy[0][i] = [-1 -1 4 2]
i= 4 ,contours[i]: (4, 1, 2) ,hierarchy[0][i] = [ 5 -1 -1 3]
i= 5 ,contours[i]: (5, 1, 2) ,hierarchy[0][i] = [-1 4 -1 3]
原图为白色背景深色目标,先进行二值化处理,并取反成为黑色背景白色目标。用 findContours() 寻找轮廓并显示,关于构造和显示轮廓图像函数 drawContours() 在下一节介绍。
轮廓检索模式 mode 设为 RETR_TREE,查找所有的轮廓(树状结构);mode 设为 RETR_EXTERNAL,只查找最外层的轮廓,不查找图中内层菱形目标的轮廓。
返回值 contours 是所有轮廓的列表:
- 列表长度为 6,对应于找到的 6 个轮廓。
- 列表中的第 i 个元素表示第 i 个轮廓,是一个形如 (k,1,2) 的 Numpy 数组。
- 数组 contours[i] 是构成第 i 个轮廓的各边界点坐标 (x,y) 的点向量,k 是描述第 i 个轮廓的边界点的个数。
- 注意边界点的坐标表达形式是 (x,y),而不是 OpenCV 中常用的像素坐标表达形式 (y,x)。
通常将轮廓近似方法 method 设为 CHAIN_APPROX_SIMPLE,对轮廓的水平、垂直和斜线进行压缩,只保留这些线段的端点,因此轮廓是由一系列线段连接而成的闭合曲线。一个矩形轮廓最少可以用 4 个边界点表示,如例程的结果中 i=2 的轮廓只有 4个顶点。但由于图像本身和轮廓查找的算法的影响,一个看起来是矩形的目标,其轮廓也可能多于 4个边界点。
返回值 hierarchy 是轮廓的层次结构和拓扑信息,是一个形如 (1,k,4) 的 Numpy 数组:
- k 对应于找到的轮廓数量。
- hierarchy[0][i]表示第 i 个轮廓的层次结构,hierarchy [0][i][0]~hierarchy[0][i][3] 分别代表第 i 个轮廓的同层的后一个轮廓、同层的前一个轮廓、第一个子轮廓、父轮廓的编号。-1 表示没有/不适用。
第 i 个轮廓 contours[i],hierarchy [i] 按照 0~L-1 顺序编号,编号顺序并不体现嵌套结构关系。轮廓之间的嵌套结构关系是由 hierarchy [i] 的数值来表达的。
例程找到 6个轮廓,返回值 hierarchy 的形状为 (1, 6, 4) 。 hierarchy[0][0]=[ 1 -1 -1 -1],表示第 0 个轮廓同层后一个轮廓编号为 1,没有同层前一个轮廓,没有子轮廓,没有父轮廓; hierarchy[0][1]=[ 2 0 -1 -1],表示第 1 个轮廓同层后一个轮廓编号为 2,同层前一个轮廓编号为 0,没有子轮廓,没有父轮廓; hierarchy[0][2]=[-1 1 3 -1],表示第 2 个轮廓没有同层后一个轮廓,同层前一个轮廓编号为 1,子轮廓编号为 3,没有父轮廓; hierarchy[0][3]=[-1 -1 4 2],表示第 3 个轮廓没有同层后一个轮廓,没有同层前一个轮廓,子轮廓编号为 4,父轮廓编号为 2; hierarchy[0][4]=[ 5 -1 -1 3],表示第 4 个轮廓同层后一个轮廓编号为 5,没有同层前一个轮廓,没有子轮廓,父轮廓编号为 3; hierarchy[0][5]= [-1 4 -1 3],表示第 5 个轮廓没有同层后一个轮廓,同层前一个轮廓编号为4,没有子轮廓,父轮廓编号为 3。
(本节完)
版权声明:
OpenCV 例程200篇 总目录-202205更新 youcans@xupt 原创作品,转载必须标注原文链接:(https://blog.csdn.net/youcans/article/details/124970610)
Copyright 2022 youcans, XUPT Crated:2022-5-25
欢迎关注 『youcans 的 OpenCV 例程 200 篇』 系列,持续更新中 欢迎关注 『youcans 的 OpenCV学习课』 系列,持续更新中
【youcans 的 OpenCV 例程200篇】147. 图像分割之孤立点检测 【youcans 的 OpenCV 例程200篇】148. 图像分割之线检测 【youcans 的 OpenCV 例程200篇】149. 图像分割之边缘模型 【youcans 的 OpenCV 例程200篇】150. 边缘检测梯度算子 【youcans 的 OpenCV 例程200篇】151. 边缘检测中的平滑处理 【youcans 的 OpenCV 例程200篇】152. 边缘检测之 LoG 算子 【youcans 的 OpenCV 例程200篇】153. 边缘检测之 DoG 算子 【youcans 的 OpenCV 例程200篇】154. 边缘检测之 Canny 算子 【youcans 的 OpenCV 例程200篇】155. 边缘连接的局部处理方法 【youcans 的 OpenCV 例程200篇】156. 边缘连接局部处理的简化算法 【youcans 的 OpenCV 例程200篇】157. 霍夫变换直线检测 【youcans 的 OpenCV 例程200篇】158. 阈值处理之固定阈值法 【youcans 的 OpenCV 例程200篇】159. 图像分割之全局阈值处理 【youcans 的 OpenCV 例程200篇】160. 图像处理之OTSU 方法 【youcans 的 OpenCV 例程200篇】161. OTSU 阈值处理算法的实现 【youcans 的 OpenCV 例程200篇】162. 全局阈值处理改进方法 【youcans 的 OpenCV 例程200篇】163. 基于边缘信息改进全局阈值处理 【youcans 的 OpenCV 例程200篇】164.使用 Laplace 边缘信息改进全局阈值处理 【youcans 的 OpenCV 例程200篇】165.多阈值 OTSU 处理方法 【youcans 的 OpenCV 例程200篇】166.自适应阈值处理 【youcans 的 OpenCV 例程200篇】167.基于移动平均的可变阈值处理 【youcans 的 OpenCV 例程200篇】168.图像分割之区域生长 【youcans 的 OpenCV 例程200篇】169.图像分割之区域分离 【youcans 的 OpenCV 例程200篇】170.图像分割之K均值聚类 【youcans 的 OpenCV 例程200篇】171.SLIC 超像素区域分割 【youcans 的 OpenCV 例程200篇】172.SLIC 超像素区域分割算法比较 【youcans 的 OpenCV 例程200篇】173.SEEDS 超像素区域分割 【youcans 的 OpenCV 例程200篇】174.LSC 超像素区域分割 【youcans 的 OpenCV 例程200篇】175.超像素区域分割方法比较 【youcans 的 OpenCV 例程200篇】176.图像分割之均值漂移算法 【youcans 的 OpenCV 例程200篇】177.图像分割之 GraphCuts 图割法 【youcans 的 OpenCV 例程200篇】178.图像分割之 GrabCut 图割法(框选前景) 【youcans 的 OpenCV 例程200篇】179.图像分割之 GrabCut 图割法(掩模图像) 【youcans 的 OpenCV 例程200篇】180.基于距离变换的分水岭算法 【youcans 的 OpenCV 例程200篇】181.基于 Sobel 梯度的分水岭算法 【youcans 的 OpenCV 例程200篇】182.基于形态学梯度的分水岭算法 【youcans 的 OpenCV 例程200篇】183.基于轮廓标记的分水岭算法 【youcans 的 OpenCV 例程200篇】184.鼠标交互标记的分水岭算法 【youcans 的 OpenCV 例程200篇】185.图像金字塔之高斯金字塔 【youcans 的 OpenCV 例程200篇】186.图像金字塔之拉普拉斯金字塔 【youcans 的 OpenCV 例程200篇】187.由拉普拉斯金字塔还原图像 【youcans 的 OpenCV 例程200篇】188.基于拉普拉斯金字塔的图像融合 【youcans 的 OpenCV 例程200篇】189.基于掩模的拉普拉斯金字塔图像融合 【youcans 的 OpenCV 例程200篇】190.基于图像分割的图像融合 【youcans 的 OpenCV 例程200篇】191.基于图像分割的金字塔图像融合 【youcans 的 OpenCV 例程200篇】192.Gabor 滤波器组的形状 【youcans 的 OpenCV 例程200篇】193.基于Gabor 滤波器的特征提取 【youcans 的 OpenCV 例程200篇】194.寻找图像轮廓(cv.findContours) 【youcans 的 OpenCV 例程200篇】195.绘制图像轮廓(cv.drawContours)
|