本文将会以图表的形式为你们讲解怎么在NumPy中进行多维数据的线性代数运算。python
多维数据的线性代数一般被用在图像处理的图形变换中,本文将会使用一个图像的例子进行说明。数组
熟悉颜色的朋友应该都知道,一个颜色能够用R,G,B来表示,若是更高级一点,那么还有一个A表示透明度。一般咱们用一个四个属性的数组来表示。app
对于一个二维的图像来讲,其分辨率能够看作是一个X*Y的矩阵,矩阵中的每一个点的颜色均可以用(R,G,B)来表示。spa
有了上面的知识,咱们就能够对图像的颜色进行分解了。code
首先须要加载一个图像,咱们使用imageio.imread方法来加载一个本地图像,以下所示:orm
import imageio img=imageio.imread('img.png') print(type(img))
上面的代码从本地读取图片到img对象中,使用type能够查看img的类型,从运行结果,咱们能够看到img的类型是一个数组。对象
class 'imageio.core.util.Array'
经过img.shape能够获得img是一个(80, 170, 4)的三维数组,也就是说这个图像的分辨率是80*170,每一个像素是一个(R,B,G,A)的数组。blog
最后将图像画出来以下所示:教程
import matplotlib.pyplot as plt plt.imshow(img)
对于三维数组来讲,咱们能够分别获得三种颜色的数组以下所示:图片
red_array = img_array[:, :, 0] green_array = img_array[:, :, 1] blue_array = img_array[:, :, 2]
有了三个颜色以后咱们可使用下面的公式对其进行灰度变换:
Y=0.2126R + 0.7152G + 0.0722B
上图中Y表示的是灰度。
怎么使用矩阵的乘法呢?使用 @ 就能够了:
img_gray = img_array @ [0.2126, 0.7152, 0.0722]
如今img是一个80 * 170的矩阵。
如今使用cmap="gray"做图:
plt.imshow(img_gray, cmap="gray")
能够获得下面的灰度图像:
灰度图像是对图像的颜色进行变换,若是要对图像进行压缩该怎么处理呢?
矩阵运算中有一个概念叫作奇异值和特征值。
设A为n阶矩阵,若存在常数λ及n维非零向量x,使得Ax=λx,则称λ是矩阵A的特征值,x是A属于特征值λ的特征向量。
一个矩阵的一组特征向量是一组正交向量。
即特征向量被施以线性变换 A 只会使向量伸长或缩短而其方向不被改变。
特征分解(Eigendecomposition),又称谱分解(Spectral decomposition)是将矩阵分解为由其特征值和特征向量表示的矩阵之积的方法。
假如A是m * n阶矩阵,q=min(m,n),A*A的q个非负特征值的算术平方根叫做A的奇异值。
特征值分解能够方便的提取矩阵的特征,可是前提是这个矩阵是一个方阵。若是是非方阵的状况下,就须要用到奇异值分解了。先看下奇异值分解的定义:
\(A=UΣV^T\)
其中A是目标要分解的m * n的矩阵,U是一个 m * m的方阵,Σ 是一个m * n 的矩阵,其非对角线上的元素都是0。\(V^T\)是V的转置,也是一个n * n的矩阵。
奇异值跟特征值相似,在矩阵Σ中也是从大到小排列,并且奇异值的减小特别的快,在不少状况下,前10%甚至1%的奇异值的和就占了所有的奇异值之和的99%以上了。也就是说,咱们也能够用前r大的奇异值来近似描述矩阵。r是一个远小于m、n的数,这样就能够进行压缩矩阵。
经过奇异值分解,咱们能够经过更加少许的数据来近似替代原矩阵。
要想使用奇异值分解svd能够直接调用linalg.svd 以下所示:
U, s, Vt = linalg.svd(img_gray)
其中U是一个m * m矩阵,Vt是一个n * n矩阵。
在上述的图像中,U是一个(80, 80)的矩阵,而Vt是一个(170, 170) 的矩阵。而s是一个80的数组,s包含了img中的奇异值。
若是将s用图像来表示,咱们能够看到大部分的奇异值都集中在前的部分:
这也就意味着,咱们能够取s中前面的部分值来进行图像的重构。
使用s对图像进行重构,须要将s还原成80 * 170 的矩阵:
# 重建 import numpy as np Sigma = np.zeros((80, 170)) for i in range(80): Sigma[i, i] = s[i]
使用 U @ Sigma @ Vt 便可重建原来的矩阵,能够经过计算linalg.norm来比较一下原矩阵和重建的矩阵之间的差别。
linalg.norm(img_gray - U @ Sigma @ Vt)
或者使用np.allclose来比较两个矩阵的不一样:
np.allclose(img_gray, U @ Sigma @ Vt)
或者只取s数组的前10个元素,进行从新绘图,比较一下和原图的区别:
k = 10 approx = U @ Sigma[:, :k] @ Vt[:k, :] plt.imshow(approx, cmap="gray")
能够看到,差别并非很大:
上一节咱们讲到了如何进行灰度图像的压缩,那么如何对原始图像进行压缩呢?
一样可使用linalg.svd对矩阵进行分解。
可是在使用前须要进行一些处理,由于原始图像的img_array 是一个(80, 170, 3)的矩阵--这里咱们将透明度去掉了,只保留了R,B,G三个属性。
在进行转换以前,咱们须要把不须要变换的轴放到最前面,也就是说将index=2,换到index=0的位置,而后进行svd操做:
img_array_transposed = np.transpose(img_array, (2, 0, 1)) print(img_array_transposed.shape) U, s, Vt = linalg.svd(img_array_transposed) print(U.shape, s.shape, Vt.shape)
一样的,如今s是一个(3, 80)的矩阵,仍是少了一维,若是重建图像,须要将其进行填充和处理,最后将重建的图像输出:
Sigma = np.zeros((3, 80, 170)) for j in range(3): np.fill_diagonal(Sigma[j, :, :], s[j, :]) reconstructed = U @ Sigma @ Vt print(reconstructed.shape) plt.imshow(np.transpose(reconstructed, (1, 2, 0)))
固然,也能够选择前面的K个特征值对图像进行压缩:
approx_img = U @ Sigma[..., :k] @ Vt[..., :k, :] print(approx_img.shape) plt.imshow(np.transpose(approx_img, (1, 2, 0)))
从新构建的图像以下:
对比能够发现,虽然损失了部分精度,可是图像仍是能够分辨的。
图像的变化会涉及到不少线性运算,你们能够以此文为例,仔细研究。
本文已收录于 http://www.flydean.com/08-python-numpy-linear-algebra/
最通俗的解读,最深入的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注个人公众号:「程序那些事」,懂技术,更懂你!