看完之后,掌握以下知识:
Singular Value Decomposition(单值分解): 产生一个压缩的图像近似值
from scipy import misc
img = misc.face() # 使用scipy.misc 中的 face image
img is a Numpy array,as we can see when using the type function
img 是一个numpy的数组,可以使用type这个函数查看
type(img)
# 输出 numpy.ndarray
我们可以使用matplotlib.pyplot.imshow
函数和特殊的iPython命令–%matplotlib inline
来显示内嵌式图画。
import matplotlib.pyplot as plt
%matplotlob inline
# or
plt.imshow(img)
plt.show()
NOTE THAT!!! 在线性代数中,向量的维数是指数组中的条目数。在NumPy中,它反而定义了轴的数量。例如,一维数组是一个矢量,如[1, 2, 3],二维数组是一个矩阵,以此类推
shape
这个属性,可以看到不一样的答案。img.shape
# 输出 (768,1024,3)
这个输出是一个包含三个元素的元组,代表这是一个三维的array。从上图可知,这是一个有色彩的图像,可以使用imread
函数去查看它,这个数据是由三个二维的矩阵组合起来的。三维代表的是颜色的通道(RGB-red,green,blue)
总之:三个矩阵,每个矩阵shape是768 * 1024
使用ndim
属性可以查看维度
img.ndim
# 输出 3
NumPy将每个维度称为一个轴 由于imread
的工作方式,第三轴的第一个索引是我们图像的红色像素数据。我们可以通过使用以下语法来访问它
img[:,:,0]
array([[121, 138, 153, ..., 119, 131, 139],
[ 89, 110, 130, ..., 118, 134, 146],
[ 73, 94, 115, ..., 117, 133, 144],
...,
[ 87, 94, 107, ..., 120, 119, 119],
[ 85, 95, 112, ..., 121, 120, 120],
[ 85, 97, 111, ..., 120, 119, 118]], dtype=uint8)
img[:,:,0].shape
(768,1024)
从上述输出可以看到img[: , : , 0]输出的数据都是整数且在[0,255]之间,每个数字代表着每个相应图像像素中的红色程度。
归一化
由于我们要对这些数据进行线性代数运算,因此在矩阵的每个条目中用0和1之间的实数来表示RGB值可能更有意思。我们可以通过设置
img_array = img / 255 # 将一个array 除以一个标量(scalar)得奏效,这得益于Numpy's 传播规则(broadcasting rules)
img_array.max(),img_array.min()
# 输出(1.0,0.0)
# checking the type of data in array:
img_array.dtype
# 输出dtype('float64')
注意,我们可以使用切片语法将每个颜色通道分配给一个单独的矩阵
red_array = img_array[:, :, 0]
green_array = img_array[:, :, 1]
blue_array = img_array[:, :, 2]
使用线性代数的方法(SVD,单值分解)从现有的数据集中提取近似特征。作用:重建一个比原始图像使用更少单值信息的图像,同时仍然保留它的一些特征。(特征提取,像卷积嗷)
img_gray = img_array @ [0.2126, 0.7152, 0.0722]# @运算符(NumPy数组的矩阵乘法运算)
img_gray.shape # (768,1024)
为了看看这在我们的图像中是否有意义,我们应该使用matplotlib的颜色映射,与我们希望在图像中看到的颜色相对应(否则,matplotlib将默认为一个与真实数据不一致的颜色映射)。(不是很理解!)
在我们的例子中,我们要接近图像的灰度部分,所以我们将使用灰色的颜色映射。
plt.imshow(img_gray, cmap="gray")
plt.show()
现在使用linalg.svd 函数对这个矩阵进行操作,进行如下分解(decomposition):
#svd 是一个相当密集的计算过程
U, s, Vt = linalg.svd(img_gray)
# SVD 的数学表示
# U,Vt 是squre 正方形矩阵
# s 是一个对角矩阵(diagonal matrix) 包含singular values(单值) of A from largest to smallest 称为矩阵img_gray的奇异值并且是从大到小排列的
# 这三个量相乘可以得到原始矩阵,也就是img_gray
U.shape,s.shape,Vt.shape
#输出 ((768, 768), (768,), (1024, 1024))
NOTE!!! s 的shape很奇怪,只有一维,这意味着一些期望得到2d数组的线性代数函数可能无法工作。
从理论上将s @ Vt 在乘法上应该是兼容的,然而s没有第二轴,肯定不行!
这种情况下我们自己构造一个对角线矩阵就玩完了
import numpy as np
Sigma = np.zero((U.shape[1],Vt.shape[0]))
np.fill_diagonal(Sigma, s)
#先构造了了一个指定维度的0矩阵
#然后再调用fill_diagonal方法使用s的值将Sigma变成对角矩阵
linalg.norm
函数 计算以numpy数组表示的向量或矩阵的范数。 例如,从上面的SVD解释来看,我们希望img_gray和重建的SVD乘积之间的差值的规范是小的。正如预期的那样,你应该看到这样的东西
linalg.norm(img_gray - U @ Sigma @ Vt)
# 输出 1.4372373828273098e-12 数值很小,近似相等
np.allclose
函数可以用来确保reconstructed的乘积实际上很接近我们的原始矩阵(两个数组之间的差异很小)
np.allclose(img_gary, U @ Sigma @ Vt)
# 输出 True
要看一个近似值是否合理,我们可以检查s 中的值
plt.plot(s)
plt.show()
在图中,我们可以看到,尽管我们在s中有768个奇异值,但其中大部分(在第150条左右)是相当小的。因此,只使用与第一个(比如说,50个)奇异值有关的信息来建立一个更经济的图像近似值可能是有意义的。
这个想法是将Sigma中除前k个奇异值(与s中的奇异值相同)外的所有奇异值视为零,保持U和Vt不变,并计算这些矩阵的乘积作为近似值。
e.t. k = 10
approx = U @ Sigma[: , :k] @ Vt[ : k, :]
#NOTE:我们必须只使用Vt的前k行,因为所有其他行都会被我们从这个近似中消除的奇异值对应的零所乘。
plt.imshow(approx,cmap="gray")
plt.show()
如果我们的数组有两个以上的维度,那么SVD可以一次性应用到所有的轴上。然而,NumPy中的线性代数函数希望看到一个形式为(n, M, N)
的数组,其中第一轴n代表堆栈中MxN矩阵的数量。
所以我们先要将原来的(M,N,n)转换为(n,M,N)可以使用np.transpose
np.transpose(x,axes=(i,j,k))
# 表示轴将被重新排序,这样转置的数组的最终形状将根据索引(i, j, k)重新排序。
img_array_transposed = np.transpose(img_array,(2,0,1))
img_array_transpose.shape
# 输出(3,768,1024)
#现在可以用linalg.svd方法了
U,s,Vt = linalg.svd(img_array_transposed)
最后,为了得到完整的近似图像,我们需要将这些矩阵重新组合成近似值。
NOTE 为了建立最终的近似矩阵,我们必须了解跨不同轴的乘法是如何进行的。
首先要构造一个三维的Sigma矩阵其中包含原始图像的奇异值
Sigma = np.zero((3,768,1024))
for j in range(3):
np.fill_diagonal(Sigma[j, :, :],s[j, :])
# rebulid the full SVD
reconstructed = U @ Sigma @ Vt
reconstructed.shape
# 输出(3,768,1024)
重建的图像应该与原始图像没有区别,除了由于重建的浮点误差造成的差异。回顾一下,我们的原始图像由范围为[0., 1.]的浮点值组成。重建过程中浮点误差的积累会导致数值稍微超出这个原始范围
reconstructed.min(),reconstructed.max()
# 输出 (-5.527858986525969e-15, 1.0000000000000056)
reconstructed = np.clip(reconstructed,0,1) 由于imshow期望的是范围内的值,我们可以使用 clip 来切除浮点误差
'''
clip: 将输入数据截断到RGB数据的有效范围(浮点数为[0...1],整数为[0...255]),用于imshow。
'''
plt.imshow(np.transpose(reconstructed,(1,2,0))
plt.show()
approx_img = U @ Sigma[..., : k] @ Vt[..., :k, : ]
'''
我们只选择了Sigma最后一个轴的前k个分量(这意味着我们只使用了堆栈中三个矩阵的前k列),
我们只选择了Vt倒数第二个轴的前k个分量(这意味着我们只选择了堆栈Vt中每个矩阵的前k行和所有列)。
省略号,它是其他轴的占位符。
'''
approx_img.shape
# 输出(3,768,1024)which is not the right shape for showing the image
#finally,recodering the axes back to our original shape of(7668,1027,3)
plt.imshow(np.transpose(approx_img,(1,2,0)))
plt,show()
尽管图像没有那么清晰,但使用少量的k个奇异值(与原始的768个值的集合相比),我们可以从这个图像中恢复许多区别特征。
当然,这不是对图像进行近似的最佳方法。然而,事实上,在线性代数中存在一个结果,即我们在上面建立的近似是我们对原始矩阵在差值的规范方面所能得到的最好结果
参考网站:https://numpy.org/numpy-tutorials/content/tutorial-svd.html