基于k-means的图像分割
-
- 一、核心思想:把图像分割问题转化为数据聚类问题
- 二、关键概念与步骤
-
- [1. 特征向量的构建](#1. 特征向量的构建)
- [2. K-means 算法的基本步骤(应用于图像)](#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从零开始的编程实现。
数据准备
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 聚类算法对图像进行颜色聚类(图像分割的前期步骤)
- 导入库
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 # 图像处理(这里未使用)
- 读取图像
python
image = imread('segmentation.jpeg')[:,:,:3]
imread():读取名为'segmentation.jpeg'的图像[:,:,:3]:切片操作,只取前3个通道(RGB)- 作用:如果图像有RGBA(带透明度)通道,只保留RGB,忽略alpha通道
- 归一化像素值
python
if np.max(image) > 1:
image = image / 255
- 检查图像像素值的最大值
- 如果最大值 > 1(通常是0-255的整数),则除以255归一化到[0,1]区间
- 目的 :许多机器学习算法对输入数据的尺度敏感,归一化可以:
- 加速算法收敛
- 提高数值稳定性
- 使不同特征(R、G、B通道)具有相同的重要性
- 准备聚类数据
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)的标签掩码转换为彩色图像,每个类别有固定的颜色表示。
- 颜色定义
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) # 偏灰色
- ...依此类推
- 创建RGB通道
python
r = label_mask.copy() # 红色通道
g = label_mask.copy() # 绿色通道
b = label_mask.copy() # 蓝色通道
- 创建三个与输入标签相同形状的数组
- 初始值就是类别索引(0-5)
- 颜色映射
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值
- 在
- 合并为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三个通道
- 返回结果
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聚类对图像进行分割,并将分割结果显示出来。
- 获取预测标签并重建分割图像
python
segmented_img = kmeans.cluster_centers_[kmeans.labels_]
kmeans.labels_:每个像素点所属的聚类标签(0到n_cluster-1)kmeans.cluster_centers_:每个聚类的中心颜色值(RGB值)segmented_img:用聚类中心颜色替换每个像素的原始颜色,得到一个颜色简化的图像
- 将标签转换为彩色分割图
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:得到用固定颜色表示每个类别区域的彩色分割图
- 显示原始图像
python
plt.imshow(image[:,:,:3])
plt.title('Original image')
plt.axis('off')
plt.show()
image[:,:,:3]:显示图像的前三个通道(通常是RGB,如果是RGBA图像则忽略Alpha通道)plt.axis('off'):隐藏坐标轴- 显示原始彩色图像
- 显示分割结果
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:用固定颜色显示,每个类别有固定颜色,便于识别不同区域
输出
