Python 机器学习案例:利用 SVD 压缩梵高《星空》图片
本文介绍如何使用奇异值分解(SVD)技术对图像进行压缩。通过 Python 的 NumPy 和 Pillow 库,将彩色图像转换为矩阵并进行 SVD 分解。通过保留前 k 个奇异值重构图像,实现数据量的减少。实验展示了不同 k 值下图像质量与压缩率的平衡,适用于理解降维与近似在图像处理中的应用。文章详细解析了代码逻辑、数学原理及结果分析,帮助读者掌握 SVD 在机器学习中的基础应用。

本文介绍如何使用奇异值分解(SVD)技术对图像进行压缩。通过 Python 的 NumPy 和 Pillow 库,将彩色图像转换为矩阵并进行 SVD 分解。通过保留前 k 个奇异值重构图像,实现数据量的减少。实验展示了不同 k 值下图像质量与压缩率的平衡,适用于理解降维与近似在图像处理中的应用。文章详细解析了代码逻辑、数学原理及结果分析,帮助读者掌握 SVD 在机器学习中的基础应用。

在数字图像处理领域,图像压缩是一项基础且关键的技术。随着多媒体数据的爆发式增长,如何在保证视觉质量的前提下减少存储空间和传输带宽,成为了计算机视觉与机器学习的重要应用场景之一。
本案例将奇异值分解(Singular Value Decomposition, SVD)应用于处理图像压缩任务。我们要处理的图片是文森特·梵高的著名作品《星空》。通过数学手段对图像矩阵进行降维近似,我们可以直观地观察到数据压缩的效果。

在计算机中,彩色图像通常被表示为三维矩阵。对于一张分辨率为 $H \times W$ 的 RGB 图像,其数据结构可以看作是一个 $H \times W \times 3$ 的数组。其中,前两个维度代表像素的行和列,第三个维度代表红(R)、绿(G)、蓝(B)三个颜色通道。每个通道的像素值通常在 0 到 255 之间。
任何实数矩阵 $A$ 都可以分解为三个矩阵的乘积: $$ A = U \Sigma V^T $$ 其中:
根据 Eckart-Young-Mirsky 定理,使用秩为 $k$ 的矩阵来近似原矩阵 $A$ 时,SVD 截断提供了最小二乘意义下的最优解。
如果我们只保留前 $k$ 个最大的奇异值及其对应的左右奇异向量,就可以重构出一个低秩矩阵 $A_k$: $$ A_k = U_k \Sigma_k V_k^T $$ 这里 $U_k$ 包含前 $k$ 列,$\Sigma_k$ 包含前 $k$ 个对角元素,$V_k$ 包含前 $k$ 行。
当 $k$ 远小于矩阵的秩(即图像的实际信息量)时,存储 $A_k$ 所需的数据量将远小于原始图像。虽然会丢失部分细节(产生模糊或噪点),但在人眼可接受的范围内,这实现了有效的压缩。
本案例基于 Python 语言实现,需要安装以下第三方库:
Pillow: 用于图像读取和处理。NumPy: 用于矩阵运算和 SVD 分解。Matplotlib: 用于可视化展示结果。安装命令如下:
pip install pillow numpy matplotlib
以下是完整的 Python 实现代码。代码逻辑分为图像加载、通道分离、SVD 分解、图像重构及可视化展示五个步骤。
from PIL import Image
import os
import numpy as np
import matplotlib.pyplot as plt
if __name__ == '__main__':
# 设置绘图字体支持中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 1. 读取图像并转换为 NumPy 数组
# 假设当前目录下存在 'starry_night.jpg' 文件
if not os.path.exists('starry_night.jpg'):
raise FileNotFoundError("未找到图片文件 'starry_night.jpg',请确保文件在当前目录")
img = Image.open('starry_night.jpg')
a = np.array(img) # 转换成矩阵
# 由于是彩色图像,所以有 3 个通道。a 的最内层数组为三个数,分别表示 RGB,用来表示一个像素
# 对 R, G, B 三个通道分别进行 SVD 分解
u_r, sigma_r, v_r = np.linalg.svd(a[:, :, 0])
u_g, sigma_g, v_g = np.linalg.svd(a[:, :, 1])
u_b, sigma_b, v_b = np.linalg.svd(a[:, :, 2])
def restore(u, sigma, v, k):
"""
使用保留的前 k 个奇异值重构图像
:param u: 左奇异向量矩阵
:param sigma: 奇异值向量
:param v: 右奇异向量矩阵
:param k: 保留的奇异值个数
:return: 重构后的图像矩阵
"""
m = len(u)
n = len(v)
# 初始化全零矩阵
reconstructed = np.zeros((m, n))
# 重构图像公式:A_k = U_k * Sigma_k * V_k^T
# 等价于累加前 k 个秩 1 矩阵的和
for i in range(k):
ui = u[:, i].reshape(m, 1)
vi = v[i, :].reshape(1, n)
reconstructed += sigma[i] * np.dot(ui, vi)
# 限制像素值范围在 [0, 255]
reconstructed[reconstructed < 0] = 0
reconstructed[reconstructed > 255] = 255
return np.rint(reconstructed).astype('uint8')
# 2. 可视化不同 k 值下的重构效果
plt.figure(facecolor='w', figsize=(10, 10))
# 保留的奇异值个数依次为:1, 2, ..., K
K = 12
for k in range(1, K + 1):
print(f'Processing k={k}')
# 分别重构三个通道
R = restore(u_r, sigma_r, v_r, k)
G = restore(u_g, sigma_g, v_g, k)
B = restore(u_b, sigma_b, v_b, k)
# 合并通道
I = np.stack((R, G, B), axis=2)
# 显示重构后的图片
plt.subplot(3, 4, k)
plt.imshow(I)
plt.axis('off')
plt.title(u'奇异值个数:%d' % k)
plt.suptitle(u'SVD 与图像分解', fontsize=20)
plt.tight_layout(0.1, rect=(0, 0, 1, 0.92))
plt.show()
运行上述代码后,程序会生成一系列子图,展示随着保留奇异值数量 $k$ 的增加,图像清晰度的变化过程。

假设原图尺寸为 $M \times N$,则原始数据量为 $M \times N \times 3$(RGB)。 使用 SVD 压缩后,我们需要存储的数据量为: $$ M \times k + k + N \times k = k(M + N + 1) $$ 压缩比约为: $$ \text{Ratio} = \frac{k(M + N + 1)}{3MN} $$ 由此可见,当 $k$ 远小于 $\min(M, N)$ 时,压缩效果显著。
本案例通过 Python 实现了基于奇异值分解的图像压缩算法。我们不仅掌握了 SVD 的数学原理,还学会了如何利用 NumPy 进行矩阵操作以及 Matplotlib 进行可视化。
该技术的核心思想是利用矩阵的低秩近似来去除冗余信息。在实际应用中,SVD 不仅可以用于图像压缩,还可以广泛应用于推荐系统(矩阵补全)、噪声过滤、主成分分析(PCA)降维等领域。理解这一机制,有助于深入掌握线性代数在机器学习中的实际价值。
通过调整参数 $k$,开发者可以在存储空间和图像质量之间找到最佳平衡点,满足不同的业务需求。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online