分水岭算法(Watershed Algorithm)教程:硬币分割实例

python 复制代码
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 解读:

  1. 图像预处理:

    • 读取图像并转换为灰度图:

      python 复制代码
      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 数组)。
    • 对灰度图进行二值化处理:

      python 复制代码
      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 - 二值化后的图像。
    • 对二值化图像进行开运算(先腐蚀后膨胀):

      python 复制代码
      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.uint8np.int8np.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 - 操作的迭代次数。
        • 返回值: 形态学操作后的图像。
  2. 创建标记 (Markers):

    分水岭算法需要一个初始的标记图像。标记图像中,不同的整数值表示不同的"种子"区域:

    • 0: 表示"未知区域"。这些区域需要分水岭算法来确定它们属于前景还是背景。

    • 1: 表示"确定是背景"的区域。

    • 大于 1 的整数: 表示不同的"确定是前景"的区域(每个硬币一个标记)。

    • 2.1 确定是背景的区域 (bg):

      python 复制代码
      bg = cv2.dilate(open1, kernel, iterations=1)
      • cv2.dilate(src, kernel, iterations) :
        • 功能: 执行膨胀操作(使白色区域扩张)。
        • 参数:
          • src - 输入图像。
          • kernel - 结构元素。
          • iterations - 膨胀的迭代次数。
        • 返回值: 膨胀后的图像。
      • 原理 : 通过膨胀open1图像, 使得硬币之间的空隙被填充, 从而得到确定的背景区域。
    • 2.2 确定是前景的区域 (fg):

      python 复制代码
      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):

      python 复制代码
      unknown = cv2.subtract(bg, fg)
      • cv2.subtract(src1, src2) :
        • 功能:src1 图像中减去 src2 图像(逐像素相减)。
        • 参数:
          • src1 - 第一个输入图像。
          • src2 - 第二个输入图像。
        • 返回值: 相减后的图像。
      • 原理: 未知区域是指既不属于"确定是背景"也不属于"确定是前景"的区域。
    • 2.4 创建标记图像 (markers):

      python 复制代码
      ret, markers = cv2.connectedComponents(fg)
      markers = markers + 1
      markers[unknown == 255] = 0
      • cv2.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
  3. 应用分水岭算法:

    python 复制代码
    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)相当于"无主之地",会被相邻的标记区域的水流"淹没",并最终归属于某个标记区域。
        • "淹没"的含义是,未知区域的像素会被赋予相邻标记区域的标记值。
        • 水流蔓延的方向和速度受原始图像"地形"的影响,也就是原始图像素的灰度值/颜色。
  4. 可视化结果:

    python 复制代码
    img[result == -1] = [255, 0, 0]  # 将分割边界标记为红色
    • 将原始图像中与分割边界对应的像素设置为红色,以便于观察分割结果。

总结:

分水岭算法是一种强大的图像分割工具。它通过模拟"注水"过程,结合预定义的标记图像,将图像分割成不同的区域。理解分水岭算法的关键在于:

  • 标记图像: 为算法提供"种子"区域,指导算法的"注水"过程。
  • "注水"过程: 模拟水流从标记区域蔓延,并在不同区域的水流相遇处形成分割边界。
  • "地形": 原始图像的灰度或颜色变化会影响"水流"的蔓延,从而影响分割边界的位置。
  • 距离变换: 在硬币分割的例子中,距离变换帮助我们找到"确定是前景"的区域(硬币中心)。
相关推荐
Dave.B28 分钟前
vtkPolyDataConnectivityFilter 实用指南
算法·vtk
细节处有神明1 小时前
开源数据之历史气象数据的获取与使用
人工智能·python·算法
【赫兹威客】浩哥1 小时前
基于 YOLO 多版本模型的路面缺陷识别实践与分析
人工智能·计算机视觉·目标跟踪
小白开始进步1 小时前
JAKA Zu12 机械臂运动学算法深度解析(含可视化方案)
python·算法·numpy
梵刹古音1 小时前
【C语言】 递归函数
c语言·数据结构·算法
yongui478342 小时前
混凝土二维随机骨料模型 MATLAB 实现
算法·matlab
酉鬼女又兒2 小时前
JAVA牛客入门11~20
算法
代码游侠2 小时前
C语言核心概念复习(二)
c语言·开发语言·数据结构·笔记·学习·算法
XX風2 小时前
2.1_binary_search_tree
算法·计算机视觉