一、项目背景
传统图像存储中,每个像素的RGB三通道各占8位(共24位/像素),高分辨率图像会占用大量存储空间,且传输时消耗更多带宽。本项目基于矢量量化(Vector Quantization) 思想,采用K-Means聚类算法实现图像压缩:将图像中相似的颜色像素聚为一类,用聚类中心(码本)替代原像素的颜色值,仅存储每个像素对应的聚类标签(而非完整RGB值),从而大幅降低存储体积;解压时通过标签索引聚类中心,重构出原始图像的近似版本。

二、解决方案
1. 压缩阶段(img_compress.py)
- 读取原始图像并可视化,确认图像完整性;
- 将图像从「行×列×3」的三维矩阵重塑为「总像素数×3」的一维像素列表(每个元素为一个RGB像素点);
- 用K-Means对所有RGB像素点聚类(设置128个聚类中心),得到每个像素的聚类标签和聚类中心(码本);
- 保存聚类中心(码本)为.npy文件,将标签重塑为原图像尺寸后保存为压缩图像.png文件。
2. 解压阶段(image_decompress.py)
- 加载保存的聚类中心(码本)和压缩后的标签图像;
- 遍历标签图像的每个像素,通过标签索引码本中的RGB值,重构完整的RGB图像;
- 保存并可视化重构后的图像。
三、代码版本
(一)带详细注释的版本
1. img_compress.py
# -*- coding: utf-8 -*-
# 图像压缩脚本:基于K-Means聚类实现图像颜色量化压缩
# 核心逻辑:将相似颜色聚类,用聚类中心替代原像素,仅存储标签降低存储量
# 导入必要库:skimage.io用于图像读写/可视化,KMeans用于聚类,numpy用于数组操作
from skimage import io
from sklearn.cluster import KMeans
import numpy as np
# 1. 读取原始图像(tiger.png需放在脚本同目录)
image = io.imread('tiger.png')
# 2. 可视化原始图像(验证读取成功)
io.imshow(image)
io.show()
# 3. 获取图像的行、列尺寸(用于后续重塑标签)
rows = image.shape[0] # 图像高度(行数)
cols = image.shape[1] # 图像宽度(列数)
print(f"图像尺寸:{rows} 行 × {cols} 列")
# 4. 重塑图像数组:将三维矩阵(rows, cols, 3)转为二维矩阵(rows*cols, 3)
# 目的:K-Means仅支持二维数据输入,每一行代表一个像素的RGB值
image = image.reshape(image.shape[0] * image.shape[1], 3)
# 5. 初始化并训练K-Means模型
# n_clusters=128:设置128个颜色聚类中心(聚类数越少压缩比越高,图像失真越明显)
# n_init=10:多次初始化聚类中心(避免局部最优),取最优结果
# max_iter=200:单次聚类的最大迭代次数(保证聚类收敛)
kmeans = KMeans(n_clusters=128, n_init=10, max_iter=200)
kmeans.fit(image) # 对所有像素的RGB值聚类
print("K-Means聚类训练完成")
# 6. 提取聚类结果:聚类中心(码本)和每个像素的聚类标签
# 聚类中心:128个RGB值,作为颜色码本;转换为uint8(符合图像像素值范围0-255)
clusters = np.asarray(kmeans.cluster_centers_, dtype=np.uint8)
# 聚类标签:每个像素对应的聚类中心索引;转换为uint8节省空间
labels = np.asarray(kmeans.labels_, dtype=np.uint8)
# 将标签重塑为原图像的行列尺寸(用于保存压缩图像)
labels = labels.reshape(rows, cols)
# 7. 保存压缩结果:码本和标签图像
np.save('codebook_tiger.npy', clusters) # 保存聚类中心(解压时需要)
io.imsave('compressed_tiger.png', labels) # 保存标签图像(压缩后的核心文件)
print("图像压缩完成,已生成:codebook_tiger.npy(码本)、compressed_tiger.png(压缩图像)")
2. image_decompress.py
# -*- coding: utf-8 -*-
# 图像解压脚本:基于K-Means聚类中心重构原始图像
# 导入必要库:skimage.io用于图像读写/可视化,numpy用于数组操作
from skimage import io
import numpy as np
# 1. 加载压缩阶段保存的聚类中心(码本)
# 码本存储了128个聚类中心的RGB值,是重构图像的核心
centers = np.load('codebook_tiger.npy')
# 2. 加载压缩后的标签图像
# 标签图像的每个像素值是聚类中心的索引(0-127)
c_image = io.imread('compressed_tiger.png')
# 3. 初始化重构图像的数组
# 尺寸与压缩图像一致,通道数为3(RGB),数据类型uint8(0-255)
image = np.zeros((c_image.shape[0], c_image.shape[1], 3), dtype=np.uint8)
# 4. 遍历每个像素,通过标签索引码本重构RGB值
for i in range(c_image.shape[0]): # 遍历每一行
for j in range(c_image.shape[1]): # 遍历每一列
# 用当前像素的标签(c_image[i,j])索引码本,赋值给重构图像的对应位置
image[i, j, :] = centers[c_image[i, j], :]
# 5. 保存并可视化重构后的图像
io.imsave('reconstructed_tiger.png', image) # 保存重构图像
io.imshow(image) # 可视化重构图像
io.show()
print("图像解压完成,已生成:reconstructed_tiger.png(重构图像)")
(二)无注释的版本
1. img_compress.py
# -*- coding: utf-8 -*-
from skimage import io
from sklearn.cluster import KMeans
import numpy as np
image = io.imread('tiger.png')
io.imshow(image)
io.show()
rows = image.shape[0]
cols = image.shape[1]
print(rows, cols)
image = image.reshape(image.shape[0]*image.shape[1], 3)
kmeans = KMeans(n_clusters=128, n_init=10, max_iter=200)
kmeans.fit(image)
print("finished kmeans")
clusters = np.asarray(kmeans.cluster_centers_, dtype=np.uint8)
labels = np.asarray(kmeans.labels_, dtype=np.uint8)
labels = labels.reshape(rows, cols)
np.save('codebook_tiger.npy', clusters)
io.imsave('compressed_tiger.png', labels)
print("done")
2. image_decompress.py
# -*- coding: utf-8 -*-
from skimage import io
import numpy as np
centers = np.load('codebook_tiger.npy')
c_image = io.imread('compressed_tiger.png')
image = np.zeros((c_image.shape[0], c_image.shape[1], 3), dtype=np.uint8)
for i in range(c_image.shape[0]):
for j in range(c_image.shape[1]):
image[i, j, :] = centers[c_image[i, j], :]
io.imsave('reconstructed_tiger.png', image)
io.imshow(image)
io.show()
四、补充说明
- 压缩比:原图像每个像素占24位,压缩后标签仅占7位(128个聚类中心需2^7=128),理论压缩比约3.4:1(未计算码本体积,码本仅128×3×8=3072位,可忽略);
- 聚类数调整:n_clusters越小,压缩比越高,但图像失真越明显;反之则失真降低,压缩比下降;
- 依赖安装:需执行
pip install scikit-image scikit-learn numpy安装依赖库; - 图像路径:确保
tiger.png放在脚本同目录,否则需修改文件路径。

1万+

被折叠的 条评论
为什么被折叠?



