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

2. 理论基础
2.1 图像的数字表示
在计算机中,彩色图像通常被表示为三维矩阵。对于一张分辨率为 $H \times W$ 的 RGB 图像,其数据结构可以看作是一个 $H \times W \times 3$ 的数组。其中,前两个维度代表像素的行和列,第三个维度代表红(R)、绿(G)、蓝(B)三个颜色通道。每个通道的像素值通常在 0 到 255 之间。
2.2 奇异值分解 (SVD)
任何实数矩阵 $A$ 都可以分解为三个矩阵的乘积: $$ A = U \Sigma V^T $$ 其中:
- $U$ 是左奇异向量矩阵,列向量为正交单位向量。
- $\Sigma$ 是对角矩阵,对角线上的元素称为奇异值,按从大到小排列。
- $V^T$ 是右奇异向量矩阵的转置。
2.3 SVD 在图像压缩中的应用
根据 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$ 所需的数据量将远小于原始图像。虽然会丢失部分细节(产生模糊或噪点),但在人眼可接受的范围内,这实现了有效的压缩。
3. 环境准备
本案例基于 Python 语言实现,需要安装以下第三方库:
Pillow: 用于图像读取和处理。NumPy: 用于矩阵运算和 SVD 分解。Matplotlib: 用于可视化展示结果。
安装命令如下:
pip install pillow numpy matplotlib
4. 代码实现
以下是完整的 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'] = []
plt.rcParams[] =
os.path.exists():
FileNotFoundError()
img = Image.()
a = np.array(img)
u_r, sigma_r, v_r = np.linalg.svd(a[:, :, ])
u_g, sigma_g, v_g = np.linalg.svd(a[:, :, ])
u_b, sigma_b, v_b = np.linalg.svd(a[:, :, ])
():
m = (u)
n = (v)
reconstructed = np.zeros((m, n))
i (k):
ui = u[:, i].reshape(m, )
vi = v[i, :].reshape(, n)
reconstructed += sigma[i] * np.dot(ui, vi)
reconstructed[reconstructed < ] =
reconstructed[reconstructed > ] =
np.rint(reconstructed).astype()
plt.figure(facecolor=, figsize=(, ))
K =
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=)
plt.subplot(, , k)
plt.imshow(I)
plt.axis()
plt.title( % k)
plt.suptitle(, fontsize=)
plt.tight_layout(, rect=(, , , ))
plt.show()


