欢迎光临
我们一直在努力

计算机视觉入门到实战系列(十五)基于k-means的图像分割

基于k-means的图像分割

    • 一、核心思想:把图像分割问题转化为数据聚类问题
    • 二、关键概念与步骤
      • 1. 特征向量的构建
      • 2. K-means 算法的基本步骤(应用于图像)
    • 三、K值的选择与影响
    • 四、特征设计的变体
    • 五、优缺点总结
      • 优点
      • 缺点
    • 六、直观例子
    • 七、代码实现
      • 数据准备
      • 训练数据
      • 语义分割标签的可视化函数
      • 显示分割结果

一、核心思想:把图像分割问题转化为数据聚类问题

  • 图像即数据点集合

    • 想象一张彩色图片,每个像素都有颜色(如RGB值)和位置(x, y坐标)。
    • 我们可以把每个像素看作一个数据点,其属性可以是颜色、位置等。
  • 聚类的目标

    • 目标是将所有像素点(数据点)分成 K 个组(簇)。
    • 同一组内的像素在颜色/空间上尽可能相似,不同组之间尽可能不同。
    • 最终,每个组在图像上就表现为一个分割区域。

  • 二、关键概念与步骤

    1. 特征向量的构建

    要进行聚类,首先要定义每个像素的“特征”。常用方法:

    • 纯颜色特征:只使用颜色信息(如 RGB 或 Lab 值)。 例如:一个像素的红色=200,绿色=100,蓝色=50,则其特征向量为 [200, 100, 50]。

    • 颜色 + 空间特征:为了得到空间上连续的分割区域,可以把像素坐标也作为特征的一部分。 例如:[R, G, B, x, y]。此时相似性既考虑颜色相近,也考虑位置靠近。

    2. K-means 算法的基本步骤(应用于图像)

    步骤 1:初始化

    • 设定要分成 K 个簇(比如 K=3,代表希望分割成3个主色/区域)。
    • 随机选择 K 个像素的特征向量作为初始簇中心(也称为质心)。

    步骤 2:分配阶段

    • 遍历每个像素,计算其特征向量到 K 个簇中心 的距离(通常用欧氏距离)。
    • 将该像素分配给距离最近的那个簇中心所在的簇。
    • 完成后,所有像素都被标记为属于某一个簇(1到K之间的一个标签)。

    步骤 3:更新阶段

    • 对每个簇,重新计算其簇中心:取该簇内所有像素特征向量的平均值。
    • 新的簇中心可能不再是某个实际像素的特征,而是一个平均值向量。

    步骤 4:迭代

    • 重复 步骤 2 和 步骤 3,直到满足停止条件:
      • 簇中心不再显著变化(收敛)。
      • 达到最大迭代次数。

    步骤 5:输出分割结果

    • 每个像素都有一个簇标签(1到K)。
    • 将同一标签的像素在图像上显示为同一种颜色(如用该簇中心的颜色替换),就得到了分割后的图像。

    三、K值的选择与影响

  • K 的含义 K 代表期望的分割区域数量。例如:

    • K=2:将图像分为前景和背景。
    • K=5-10:可能分离出不同的物体或颜色区域。
  • K 的影响

    • K 太小:分割粗糙,不同物体可能被合并。
    • K 太大:分割过细,同一物体可能被分成多块,且计算量增加。
  • 如何选择 K

    • 通常需要预先设定,这是 K-means 的主要缺点之一。
    • 可尝试不同的 K 值,根据结果选择最合理的。

  • 四、特征设计的变体

  • Lab 颜色空间 由于 Lab 比 RGB 更符合人眼感知差异,在 Lab 空间聚类往往比 RGB 效果更好。

  • 加权空间坐标 在特征 [L, a, b, x, y] 中,对 x, y 乘以一个权重系数,以控制空间连续性对分割影响的强弱:

    • 权重=0:只按颜色聚类,空间上可能不连续。
    • 权重较大:强烈要求空间上靠近的像素分在同一簇,分割块更紧凑。

  • 五、优缺点总结

    优点

    • 原理简单,易于理解和实现。
    • 计算效率相对较高,适合快速原型。
    • 无需训练数据,是无监督方法。

    缺点

    • 需要预先指定 K 值,且 K 的选择对结果影响大。
    • 初始质心随机选择,可能导致每次结果略有不同。
    • 倾向于产生凸形、大小相似的簇,对复杂形状分割效果有限。
    • 仅考虑特征距离,缺乏对图像边缘、纹理等高层语义的利用。

    六、直观例子

    想象一张风景图:蓝天、白云、绿树。

  • 我们设定 K=3。
  • 算法运行后,可能会将:
    • 簇1 → 所有蓝色像素(天空)
    • 簇2 → 所有白色/浅灰像素(云)
    • 簇3 → 所有绿色像素(树和草地)
  • 每个簇内的像素用其平均颜色(天蓝、纯白、草绿)填充,得到分割图。

  • 七、代码实现

    本次代码直接使用sklearn中的k-means方法。如果想要进一步了解k-means的原理,我们提供k-menas从零开始的编程实现。

    数据准备

    from sklearn.cluster import KMeans
    from matplotlib.image import imread
    import matplotlib.pyplot as plt
    import numpy as np
    from PIL import Image

    image = imread('segmentation.jpeg')[:,:,:3]
    # 将RGB值统一到0-1内
    if np.max(image)>1:
    image = image / 255

    X = image.reshape(1, image.shape[2])

    这段代码使用 scikit-learn 的 KMeans 聚类算法对图像进行颜色聚类(图像分割的前期步骤)

  • 导入库
  • from sklearn.cluster import KMeans # K均值聚类算法
    from matplotlib.image import imread # 读取图像
    import matplotlib.pyplot as plt # 绘图
    import numpy as np # 数值计算
    from PIL import Image # 图像处理(这里未使用)

  • 读取图像
  • image = imread('segmentation.jpeg')[:,:,:3]

    • imread():读取名为 'segmentation.jpeg' 的图像
    • [:,:,:3]:切片操作,只取前3个通道(RGB)
      • 作用:如果图像有RGBA(带透明度)通道,只保留RGB,忽略alpha通道
  • 归一化像素值
  • if np.max(image) > 1:
    image = image / 255

    • 检查图像像素值的最大值
    • 如果最大值 > 1(通常是0-255的整数),则除以255归一化到[0,1]区间
    • 目的:许多机器学习算法对输入数据的尺度敏感,归一化可以:
      • 加速算法收敛
      • 提高数值稳定性
      • 使不同特征(R、G、B通道)具有相同的重要性
  • 准备聚类数据
  • X = image.reshape(1, image.shape[2])

    • image.shape:原始图像的维度,例如 (height, width, 3)
    • reshape(-1, image.shape[2]):
      • -1:自动计算该维度的大小(= height × width)
      • image.shape[2]:通道数(3,对应RGB)
      • 结果:将三维数组 (h, w, 3) 转换为二维数组 (h*w, 3)
      • 每个像素变成一行数据,包含3个特征:[R, G, B]值

    数据转换示例 假设原图是 100×100 像素:

    • 转换前:image.shape = (100, 100, 3),共10000个像素点
    • 转换后:X.shape = (10000, 3),10000行数据,每行是一个像素的RGB值

    训练数据

    # 利用k-means算法进行聚类
    segmented_imgs = []

    # 设定聚类中心个数
    n_cluster= 4
    kmeans = KMeans(n_clusters=n_cluster, random_state=42).fit(X)
    print(np.unique(kmeans.labels_))

    输出

    [0 1 2 3]

    语义分割标签的可视化函数

    # 为每一个类别赋予一个对应的颜色,用于展示
    def decode_segmap(label_mask, plot=False):
    label_colours = np.asarray([[79, 103, 67], [143, 146, 126],
    [129, 94, 64], [52, 53, 55],
    [96, 84, 70], [164, 149, 129]])
    r = label_mask.copy()
    g = label_mask.copy()
    b = label_mask.copy()
    # 为每个类别赋予对应的R、G、B值
    for ll in range(0, 6):
    r[label_mask == ll] = label_colours[ll, 0]
    g[label_mask == ll] = label_colours[ll, 1]
    b[label_mask == ll] = label_colours[ll, 2]

    rgb = np.zeros((label_mask.shape[0], label_mask.shape[1], 3))
    rgb[:, :, 0] = r
    rgb[:, :, 1] = g
    rgb[:, :, 2] = b

    return rgb

    这段代码实现了一个语义分割标签的可视化函数,将分类标签映射为对应的RGB颜色图像。将包含类别索引(0-5)的标签掩码转换为彩色图像,每个类别有固定的颜色表示。

  • 颜色定义
  • label_colours = np.asarray([[79, 103, 67], [143, 146, 126],
    [129, 94, 64], [52, 53, 55],
    [96, 84, 70], [164, 149, 129]])

    • 定义了6个类别的RGB颜色值(每个类别一个颜色)
    • 颜色值范围:0-255
    • 对应关系:
      • 类别0 → RGB(79, 103, 67) # 偏绿色
      • 类别1 → RGB(143, 146, 126) # 偏灰色
      • …依此类推
  • 创建RGB通道
  • r = label_mask.copy() # 红色通道
    g = label_mask.copy() # 绿色通道
    b = label_mask.copy() # 蓝色通道

    • 创建三个与输入标签相同形状的数组
    • 初始值就是类别索引(0-5)
  • 颜色映射
  • for ll in range(0, 6):
    r[label_mask == ll] = label_colours[ll, 0] # 红色分量
    g[label_mask == ll] = label_colours[ll, 1] # 绿色分量
    b[label_mask == ll] = label_colours[ll, 2] # 蓝色分量

    • 遍历6个类别
    • 将标签中等于当前类别ll的所有位置
      • 在r通道中设置为该类别颜色的R值
      • 在g通道中设置为该类别颜色的G值
      • 在b通道中设置为该类别颜色的B值
  • 合并为RGB图像
  • rgb = np.zeros((label_mask.shape[0], label_mask.shape[1], 3))
    rgb[:, :, 0] = r # 红色通道
    rgb[:, :, 1] = g # 绿色通道
    rgb[:, :, 2] = b # 蓝色通道

    • 创建三维数组(高度×宽度×3通道)
    • 分别填充R、G、B三个通道
  • 返回结果
  • return rgb

    • 返回一个形状为(H, W, 3)的NumPy数组
    • 可以直接用matplotlib显示或保存为图像

    显示分割结果

    # 获得预测的标签
    segmented_img = kmeans.cluster_centers_[kmeans.labels_]
    segmented_imgs = decode_segmap(kmeans.labels_.reshape(
    image.shape[0],image.shape[1]))

    # 展示结果
    plt.imshow(image[:,:,:3])
    plt.title('Original image')
    plt.axis('off')
    plt.show()

    plt.imshow(segmented_imgs.astype(np.uint8))
    plt.title('{} center'.format(n_cluster))
    plt.axis('off')
    plt.show()

    这段代码使用K-means聚类对图像进行分割,并将分割结果显示出来。

  • 获取预测标签并重建分割图像
  • segmented_img = kmeans.cluster_centers_[kmeans.labels_]

    • kmeans.labels_:每个像素点所属的聚类标签(0到n_cluster-1)
    • kmeans.cluster_centers_:每个聚类的中心颜色值(RGB值)
    • segmented_img:用聚类中心颜色替换每个像素的原始颜色,得到一个颜色简化的图像
  • 将标签转换为彩色分割图
  • segmented_imgs = decode_segmap(kmeans.labels_.reshape(
    image.shape[0],image.shape[1]))

    • kmeans.labels_.reshape(image.shape[0], image.shape[1]):将一维的标签数组重新变回二维图像形状
    • decode_segmap():调用之前定义的颜色映射函数,将类别标签转换为预设的固定颜色
    • segmented_imgs:得到用固定颜色表示每个类别区域的彩色分割图
  • 显示原始图像
  • plt.imshow(image[:,:,:3])
    plt.title('Original image')
    plt.axis('off')
    plt.show()

    • image[:,:,:3]:显示图像的前三个通道(通常是RGB,如果是RGBA图像则忽略Alpha通道)
    • plt.axis('off'):隐藏坐标轴
    • 显示原始彩色图像
  • 显示分割结果
  • plt.imshow(segmented_imgs.astype(np.uint8))
    plt.title('{} center'.format(n_cluster))
    plt.axis('off')
    plt.show()

    • segmented_imgs.astype(np.uint8):将图像转换为uint8类型(matplotlib显示需要)
    • '{} center'.format(n_cluster):标题显示使用了多少个聚类中心
    • 显示K-means聚类后的分割结果

    segmented_img vs segmented_imgs:

    • segmented_img:用聚类中心颜色显示,颜色来自图像本身
    • segmented_imgs:用固定颜色显示,每个类别有固定颜色,便于识别不同区域

    输出 在这里插入图片描述

    赞(0)
    未经允许不得转载:171主机测评 » 计算机视觉入门到实战系列(十五)基于k-means的图像分割
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址