分水岭算法(WatershedAlgorithm)教程:硬币分割实例
- 游戏开发
- 2025-09-12 00:51:02

import cv2 import numpy as np # 1. 图像预处理 img = cv2.imread("./water/water_coins.jpeg") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) kernel = np.ones((3, 3), np.int8) open1 = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2) # 2. 创建标记 (Markers) # 2.1 确定是背景的区域 (bg) bg = cv2.dilate(open1, kernel, iterations=1) # 2.2 确定是前景的区域 (fg) dist = cv2.distanceTransform(open1, cv2.DIST_L2, 5) ret, fg = cv2.threshold(dist, 0.7 * dist.max(), 255, 0) # 2.3 未知区域 (unknown) unknown = cv2.subtract(bg, fg) # 2.4 创建标记图像 (markers) ret, markers = cv2.connectedComponents(fg) markers = markers + 1 markers[unknown == 255] = 0 # 3. 应用分水岭算法 result = cv2.watershed(img, markers) # 4. 可视化结果 img[result == -1] = [255, 0, 0] cv2.imshow("Result", img) cv2.waitKey(0) cv2.destroyAllWindows()
分水岭算法(Watershed Algorithm)教程:硬币分割实例
目标: 从图像中分割出多个硬币。
原理:
分水岭算法是一种基于图像形态学的分割算法。它将图像视为“地形图”,其中像素的灰度值(或颜色)代表“高度”。算法从预定义的“标记”(markers)开始“注水”,模拟水流从低洼处(标记区域)向周围蔓延的过程。当来自不同标记区域的“水”相遇时,就形成“分水岭”(watersheds),也就是分割边界。
步骤详解及关键 API 解读:
图像预处理:
读取图像并转换为灰度图:
img = cv2.imread("./water/water_coins.jpeg") # 读取图像 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转为灰度图cv2.imread(filename):
功能: 读取图像文件。参数: filename - 图像文件的路径。返回值: 一个 NumPy 数组,表示图像。如果读取失败,返回 None。cv2.cvtColor(img, code):
功能: 进行颜色空间转换。参数: img - 输入图像(NumPy 数组)。code - 颜色空间转换代码。例如: cv2.COLOR_BGR2GRAY:将 BGR 彩色图像转换为灰度图像。cv2.COLOR_BGR2RGB:将 BGR 彩色图像转换为 RGB 彩色图像。 返回值: 转换后的图像(NumPy 数组)。对灰度图进行二值化处理:
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) cv2.threshold(src, thresh, maxval, type): 功能: 对图像进行阈值处理(二值化)。参数: src - 输入图像(通常是灰度图)。thresh - 阈值。maxval - 当像素值满足阈值条件时(例如,大于阈值),赋予的新值。type - 阈值处理的类型。常用的类型包括: cv2.THRESH_BINARY:二值化。像素值大于阈值的设为 maxval,小于阈值的设为 0。cv2.THRESH_BINARY_INV:反二值化。像素值大于阈值的设为 0,小于阈值的设为 maxval。cv2.THRESH_TRUNC:截断。像素值大于阈值的设为阈值,小于阈值的不变。cv2.THRESH_TOZERO:像素值大于阈值的不变,小于阈值的设为 0。cv2.THRESH_TOZERO_INV:像素值大于阈值的设为 0,小于阈值的不变。cv2.THRESH_OTSU:使用大津法(Otsu’s method)自动确定最佳阈值(与前面的 thresh 参数一起使用,thresh 通常设为 0)。 返回值: ret - 实际使用的阈值(如果使用了 cv2.THRESH_OTSU,则返回自动确定的阈值)。thresh - 二值化后的图像。对二值化图像进行开运算(先腐蚀后膨胀):
kernel = np.ones((3, 3), np.int8) open1 = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)np.ones((rows, cols), dtype):
功能: 创建一个所有元素都为 1 的 NumPy 数组。参数: rows - 行数。cols - 列数。dtype - 数据类型(例如 np.uint8、np.int8、np.float32 等)。cv2.morphologyEx(src, op, kernel, iterations):
功能: 执行高级形态学操作。参数: src - 输入图像。op - 形态学操作的类型。常用的类型包括: cv2.MORPH_OPEN:开运算(先腐蚀后膨胀)。cv2.MORPH_CLOSE:闭运算(先膨胀后腐蚀)。cv2.MORPH_GRADIENT:形态学梯度(膨胀 - 腐蚀)。cv2.MORPH_TOPHAT:顶帽运算(原图 - 开运算)。cv2.MORPH_BLACKHAT:黑帽运算(闭运算 - 原图)。 kernel - 结构元素(通常是一个 NumPy 数组)。iterations - 操作的迭代次数。 返回值: 形态学操作后的图像。创建标记 (Markers):
分水岭算法需要一个初始的标记图像。标记图像中,不同的整数值表示不同的“种子”区域:
0: 表示“未知区域”。这些区域需要分水岭算法来确定它们属于前景还是背景。
1: 表示“确定是背景”的区域。
大于 1 的整数: 表示不同的“确定是前景”的区域(每个硬币一个标记)。
2.1 确定是背景的区域 (bg):
bg = cv2.dilate(open1, kernel, iterations=1) cv2.dilate(src, kernel, iterations): 功能: 执行膨胀操作(使白色区域扩张)。参数: src - 输入图像。kernel - 结构元素。iterations - 膨胀的迭代次数。 返回值: 膨胀后的图像。 原理: 通过膨胀open1图像, 使得硬币之间的空隙被填充, 从而得到确定的背景区域。2.2 确定是前景的区域 (fg):
dist = cv2.distanceTransform(open1, cv2.DIST_L2, 5) ret, fg = cv2.threshold(dist, 0.7 * dist.max(), 255, 0) cv2.distanceTransform(src, distanceType, maskSize): 功能: 计算图像中每个非零像素到最近零像素的距离(距离变换)。参数: src - 输入图像(通常是二值图像,非零像素表示前景,零像素表示背景)。distanceType - 距离类型。常用的类型包括: cv2.DIST_L1:曼哈顿距离(城市街区距离)。cv2.DIST_L2:欧几里得距离。cv2.DIST_C:棋盘距离。 maskSize - 距离变换的掩码大小。常用的值有 3 和 5。 返回值: 距离变换后的图像(灰度图像)。每个像素的值表示该像素到最近背景像素的距离。 原理: 硬币中心的像素距离背景最远,因此距离变换值最大。通过阈值处理,可以将这些中心区域提取出来,作为“确定是前景”的区域。2.3 未知区域 (unknown):
unknown = cv2.subtract(bg, fg) cv2.subtract(src1, src2): 功能: 从 src1 图像中减去 src2 图像(逐像素相减)。参数: src1 - 第一个输入图像。src2 - 第二个输入图像。 返回值: 相减后的图像。 原理: 未知区域是指既不属于“确定是背景”也不属于“确定是前景”的区域。2.4 创建标记图像 (markers):
ret, markers = cv2.connectedComponents(fg) markers = markers + 1 markers[unknown == 255] = 0cv2.connectedComponents(image):
功能: 对二值图像进行连通组件标记。参数: image - 输入的二值图像(通常是“确定是前景”的图像)。 返回值: ret - 连通组件的数量(不包括背景)。markers - 标记图像。每个连通区域被标记为一个唯一的整数(从 1 开始),背景标记为 0。markers = markers + 1: 将标记图像中的所有值加 1。这是因为分水岭算法要求背景标记为非零值(通常为 1)。
markers[unknown == 255] = 0: 将未知区域的标记设置为 0。
处理后的 markers 图像示例:
1 1 1 1 1 1 1 1 (1: 背景) 1 1 1 0 0 0 1 1 (0: 未知区域) 1 1 0 2 2 2 0 1 (2: 第一个硬币的中心) 1 1 0 2 2 2 0 1 1 1 0 2 2 2 0 1 1 1 1 0 0 0 1 1 1 1 0 3 3 3 0 1 (3: 第二个硬币的中心) 1 1 0 3 3 3 0 1 1 1 1 1 1 1 1 1应用分水岭算法:
result = cv2.watershed(img, markers)cv2.watershed(image, markers):
功能: 应用分水岭算法进行图像分割。参数: image - 输入的原始图像(通常是彩色图像)。markers - 标记图像。 返回值: result - 分割结果图像。 -1:表示分割边界(watersheds)。其他值:表示不同的分割区域(通常与 markers 中的标记值对应)。result 图像示例:
1 1 1 1 1 1 1 1 1 1 1-1-1-1 1 1 1 1-1 2 2 2-1 1 1 1-1 2 2 2-1 1 1 1-1 2 2 2-1 1 1 1 1-1-1-1 1 1 1 1-1 3 3 3-1 1 1 1-1 3 3 3-1 1 1 1 1 1 1 1 1 1 “淹没”过程解释: 分水岭算法模拟“注水”过程,水流从标记区域(markers)开始向外蔓延,不同标记区域的水流相遇,形成分割边界(-1)。未知区域(标记为0)相当于“无主之地”,会被相邻的标记区域的水流“淹没”,并最终归属于某个标记区域。“淹没”的含义是,未知区域的像素会被赋予相邻标记区域的标记值。水流蔓延的方向和速度受原始图像“地形”的影响,也就是原始图像素的灰度值/颜色。可视化结果:
img[result == -1] = [255, 0, 0] # 将分割边界标记为红色 将原始图像中与分割边界对应的像素设置为红色,以便于观察分割结果。总结:
分水岭算法是一种强大的图像分割工具。它通过模拟“注水”过程,结合预定义的标记图像,将图像分割成不同的区域。理解分水岭算法的关键在于:
标记图像: 为算法提供“种子”区域,指导算法的“注水”过程。“注水”过程: 模拟水流从标记区域蔓延,并在不同区域的水流相遇处形成分割边界。“地形”: 原始图像的灰度或颜色变化会影响“水流”的蔓延,从而影响分割边界的位置。距离变换: 在硬币分割的例子中,距离变换帮助我们找到“确定是前景”的区域(硬币中心)。分水岭算法(WatershedAlgorithm)教程:硬币分割实例由讯客互联游戏开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“分水岭算法(WatershedAlgorithm)教程:硬币分割实例”