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

基于k-means的图像分割

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

  1. 图像即数据点集合

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

    • 目标是将所有像素点(数据点)分成 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值的选择与影响

  1. K 的含义

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

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

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

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

四、特征设计的变体

  1. Lab 颜色空间

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

  2. 加权空间坐标

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

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

五、优缺点总结

优点

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

缺点

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

六、直观例子

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

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

七、代码实现

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

数据准备

python 复制代码
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 聚类算法对图像进行颜色聚类(图像分割的前期步骤)

  1. 导入库
python 复制代码
from sklearn.cluster import KMeans  # K均值聚类算法
from matplotlib.image import imread  # 读取图像
import matplotlib.pyplot as plt      # 绘图
import numpy as np                   # 数值计算
from PIL import Image                # 图像处理(这里未使用)
  1. 读取图像
python 复制代码
image = imread('segmentation.jpeg')[:,:,:3]
  • imread():读取名为 'segmentation.jpeg' 的图像
  • [:,:,:3]:切片操作,只取前3个通道(RGB)
    • 作用:如果图像有RGBA(带透明度)通道,只保留RGB,忽略alpha通道
  1. 归一化像素值
python 复制代码
if np.max(image) > 1:
    image = image / 255
  • 检查图像像素值的最大值
  • 如果最大值 > 1(通常是0-255的整数),则除以255归一化到[0,1]区间
  • 目的 :许多机器学习算法对输入数据的尺度敏感,归一化可以:
    • 加速算法收敛
    • 提高数值稳定性
    • 使不同特征(R、G、B通道)具有相同的重要性
  1. 准备聚类数据
python 复制代码
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值

训练数据

python 复制代码
# 利用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

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

python 复制代码
# 为每一个类别赋予一个对应的颜色,用于展示
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)的标签掩码转换为彩色图像,每个类别有固定的颜色表示。

  1. 颜色定义
python 复制代码
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) # 偏灰色
    • ...依此类推
  1. 创建RGB通道
python 复制代码
r = label_mask.copy()  # 红色通道
g = label_mask.copy()  # 绿色通道  
b = label_mask.copy()  # 蓝色通道
  • 创建三个与输入标签相同形状的数组
  • 初始值就是类别索引(0-5)
  1. 颜色映射
python 复制代码
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值
  1. 合并为RGB图像
python 复制代码
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三个通道
  1. 返回结果
python 复制代码
return rgb
  • 返回一个形状为(H, W, 3)的NumPy数组
  • 可以直接用matplotlib显示或保存为图像

显示分割结果

python 复制代码
# 获得预测的标签
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聚类对图像进行分割,并将分割结果显示出来。

  1. 获取预测标签并重建分割图像
python 复制代码
segmented_img = kmeans.cluster_centers_[kmeans.labels_]
  • kmeans.labels_:每个像素点所属的聚类标签(0到n_cluster-1)
  • kmeans.cluster_centers_:每个聚类的中心颜色值(RGB值)
  • segmented_img:用聚类中心颜色替换每个像素的原始颜色,得到一个颜色简化的图像
  1. 将标签转换为彩色分割图
python 复制代码
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:得到用固定颜色表示每个类别区域的彩色分割图
  1. 显示原始图像
python 复制代码
plt.imshow(image[:,:,:3])
plt.title('Original image')
plt.axis('off')
plt.show()
  • image[:,:,:3]:显示图像的前三个通道(通常是RGB,如果是RGBA图像则忽略Alpha通道)
  • plt.axis('off'):隐藏坐标轴
  • 显示原始彩色图像
  1. 显示分割结果
python 复制代码
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:用固定颜色显示,每个类别有固定颜色,便于识别不同区域

输出

相关推荐
阿湯哥2 小时前
ReActAgent reasoning() 方法深度解析
人工智能
aircrushin9 小时前
三分钟说清楚 ReAct Agent 的技术实现
人工智能
技术狂人16810 小时前
工业大模型工程化部署实战!4 卡 L40S 高可用集群(动态资源调度 + 监控告警 + 国产化适配)
人工智能·算法·面试·职场和发展·vllm
好奇龙猫10 小时前
【人工智能学习-AI入试相关题目练习-第三次】
人工智能
柳杉10 小时前
建议收藏 | 2026年AI工具封神榜:从Sora到混元3D,生产力彻底爆发
前端·人工智能·后端
狮子座明仔10 小时前
Engram:DeepSeek提出条件记忆模块,“查算分离“架构开启LLM稀疏性新维度
人工智能·深度学习·语言模型·自然语言处理·架构·记忆
阿湯哥10 小时前
AgentScope Java 集成 Spring AI Alibaba Workflow 完整指南
java·人工智能·spring
Java中文社群11 小时前
保姆级喂饭教程:什么是Skills?如何用Skills?
人工智能