0
回顾
奇异值分解 (singular value decomposition, SVD) 就是一个“旋转-拉缩-旋转”的过程。用下图来进一步说明:
用矩阵 A 一步做线性转换
等价于
用三个矩阵先旋转 (VT),再拉缩 (∑),最后旋转 (U),那么
A = U∑VT
本贴主要讲 SVD 在图片压缩上的应用。
1
公式可视化
将上面数学公式做可视化,旋转矩阵 U 用列向量表示,拉缩矩阵 ∑ 是只有对角线上有值,旋转矩阵 VT 用行向量表示,如下图所示:
两点需要留意:
进一步将 U∑VT 矩阵相乘分解成
相乘来累加,可以得到矩阵 A。如下图展示:
上述分解可不仅仅在于将公式以另一种方式等价写出来,其意义是,如果某个“拉缩” σ 很小,比如 σ4,那么舍弃 σ4u4v4,也能近似还原 A。
在实际图片压缩问题中,图片可用像素矩阵 A 表示,做完 SVD 后发现如果很多“拉缩” σ 很小,全部舍弃不久减少很多数据存储了吗?还能近似还原 A 不就对图片成功做了压缩处理的么?
下面来几个 SVD 实操,先来看最简单的例子,仅仅为了便于明晰 SVD 的原理;了解完全原理后,再用一张真实照片来做图片压缩。
首先引入必要的 Python 的工具包。
import numpy as np
from numpy.linalg import svd
import matplotlib as mpl
import matplotlib.pyplot as plt
2
简单例子
下面用于可视化 SVD 的代码在本帖会重复使用,对 Python 有兴趣的小朋友可以看看。代码主要做的事情就三件:
例一:心形图片
用 NumPy 数组表示图片,0 代表白色,1 代表黑色。
D = np.array([[0,1,1,0,1,1,0],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[0,1,1,1,1,1,0],
[0,0,1,1,1,0,0],
[0,0,0,1,0,0,0],
])
U,S,V = plot_svd(D)
图片像素矩阵 D 做完 SVD 后得到的 ∑ 对角线有 6 个值,最后两个是 0。
不难发现,从第四幅重叠图起就已经完美的还原原图了。
此外也可将 U, S, V 打印出来检查它们的值。
print(np.round(U,2))
print()
sigma = np.diag(S)
print(np.round(sigma,2))
print()
print(np.round(V,2))
[[-0.36 -0. -0.73 -0.05 0.56 0.13]
[-0.54 0.35 0.27 -0.08 -0.16 0.69]
[-0.54 0.35 0.27 -0.08 0.16 -0.69]
[-0.45 -0.35 -0.27 0.52 -0.56 -0.13]
[-0.28 -0.71 0.18 -0.62 -0. -0. ]
[-0.08 -0.35 0.46 0.57 0.56 0.13]]
[[4.74 0. 0. 0. 0. 0. ]
[0. 1.41 0. 0. 0. 0. ]
[0. 0. 1.41 0. 0. 0. ]
[0. 0. 0. 0.73 0. 0. ]
[0. 0. 0. 0. 0. 0. ]
[0. 0. 0. 0. 0. 0. ]]
[[-0.23 -0.4 -0.46 -0.4 -0.46 -0.4 -0.23]
[ 0.5 0.25 -0.25 -0.5 -0.25 0.25 0.5 ]
[ 0.39 -0.32 -0.19 0.65 -0.19 -0.32 0.39]
[-0.22 0.42 -0.44 0.42 -0.44 0.42 -0.22]
[ 0.56 -0.43 0.03 0. -0.03 0.43 -0.56]
[-0.42 -0.55 -0.16 0. 0.16 0.55 0.42]
[-0.12 -0.11 0.69 -0. -0.69 0.11 0.12]]
例二:小心形图片
换一张小一点的心形图。
D = np.array([[0,0,0,0,0,0,0,0,0],
[0,0,1,1,0,1,1,0,0],
[0,1,1,1,1,1,1,1,0],
[0,1,1,1,1,1,1,1,0],
[0,0,1,1,1,1,1,0,0],
[0,0,0,1,1,1,0,0,0],
[0,0,0,0,1,0,0,0,0],
[0,0,0,0,0,0,0,0,0]
])
U,S,V = plot_svd(D)
最后 4 个 σ 都为零,从第四幅重叠图起也已经完美的还原原图了。
例三:十字形图片
D = np.array([[0,0,1,1,0,0],
[0,1,1,1,1,0],
[1,1,1,1,1,1],
[1,1,1,1,1,1],
[0,1,1,1,1,0],
[0,0,1,1,0,0],
])
U,S,V = plot_svd(D)
最后 3 个 σ 都为零,从第三幅重叠图起也已经完美的还原原图了。
弄清楚以上 SVD 分解原理后,最后看一个实际例子。先引入必要的 Python 工具包。
import os
from matplotlib.image import imread
mpl.rcParams['font.sans-serif'] = ['Microsoft YaHei']
mpl.rcParams['axes.unicode_minus'] = False
3
真实例子
设置正确路径,从文件夹中用 imread() 函数读取图片并转成像素,该图片是彩照,有 RGB 三个色道。为了仅说明压缩过程,用黑白图片即可,因此将彩照转成黑白照, np.mean(A, -1),在最后一维,即在色道维度上求均值。
file = os.getcwd() + '\\niannian.jpg'
A = imread(file)
X = np.mean(A, -1); # Convert RGB to grayscale
plt.rcParams['figure.figsize'] = [16, 8]
img = plt.imshow(X)
img.set_cmap('gray')
plt.axis('off')
plt.show()
该图片矩阵尺寸很大,我们就看保留前 100,200 和 500 个 σiuivi 的图片长什么样,代码如下:
将结果整理成一排好比较,我们发现用前 500 个特征值已经能基本还原原图了,用 100 个特征值效果也不差,而且仅仅只用了原图 5.79% 的存储量,大大压缩了图片。
打印出特征个数和累计能量值发现,前 100 个特征已经还原 70% 原图,前 500 个特征已经还原 90% 原图,后面 2500 个特征只能贡献 10% 的还原度。这样根据个人的“还原原图”的需求,选取一个合适特征数量,舍弃后面的特征即可。
小朋友们,现在你们可以在自己电脑里找照片,将下图高亮处改成“照片名称和格式”就可以自己玩玩用 SVD 压缩图片咯。