【AI数学】SVD奇异值分解篇以及图像压缩应用python(学习记录)

一、奇异值分解(SVD)简介

  SVD适用于对任意矩阵进行矩阵分解,是一种重要的矩阵分解方法。
  SVD对应的公式为: A m × n = U m × n S m × n V m × n T { A_{m \times n}=U_{m \times n } S_{m \times n } V^T_{m \times n}} Am×n=Um×nSm×nVm×nT ,简记为: A = U S V T { A=USV^T} A=USVT,其中 U {U} U V {V} V 是正交矩阵(酉矩阵),即满足 U T U = E m × m {U^TU=E_{m \times m}} UTU=Em×m V T V = E n × n {V^TV=E_{n \times n}} VTV=En×n

   U {U} U矩阵:左奇异矩阵,列由 A A T {AA^T} AAT 的特征向量组成,且特征向量为单位向量;
   S {S} S矩阵:奇异值矩阵,对角元素来源于 A A T {AA^T} AAT A T A {A^TA} ATA 的特征值的平方根,并且按降序排列,值越大可以理解为越重要;
   V {V} V矩阵:右奇异矩阵,列由 A T A {A^TA} ATA 的特征向量组成,且特征向量为单位向量;

二、python实现与验证

import numpy as np
from numpy import linalg as la

A = np.array([[1,5,7,6,1],[2,1,10,4,4],[3,6,7,5,2]])
U, S, VT = la.svd(A)
Sigma = np.zeros(np.shape(A))

Sigma[:len(S),:len(S)] = np.diag(S)  # 生成奇异值矩阵,该矩阵的对角元素为奇异值

print('左奇异值矩阵:\n', U)
print('奇异值:', S)
print('奇异值矩阵:\n', Sigma)
print('右奇异值矩阵的转置:\n', VT)
print('--------------------------------------')
# 利用SVD重构矩阵
B = U.dot(Sigma.dot(VT))
print('原矩阵A:\n', A)
print('重构后的矩阵B:\n', B)
print('原矩阵A与重构后的矩阵B是否相同', np.allclose(A,B))

输出为:

左奇异值矩阵:
 [[-0.55572489  0.40548161 -0.72577856]
 [-0.59283199 -0.80531618  0.00401031]
 [-0.58285511  0.43249337  0.68791671]]
奇异值: [18.53581747  5.0056557   1.83490648]
奇异值矩阵:
 [[18.53581747  0.          0.          0.          0.        ]
 [ 0.          5.0056557   0.          0.          0.        ]
 [ 0.          0.          1.83490648  0.          0.        ]]
右奇异值矩阵的转置:
 [[-0.18828164 -0.37055755 -0.74981208 -0.46504304 -0.22080294]
 [ 0.01844501  0.76254787 -0.4369731   0.27450785 -0.38971845]
 [ 0.73354812  0.27392013 -0.12258381 -0.48996859  0.36301365]
 [ 0.36052404 -0.34595041 -0.43411102  0.6833004   0.30820273]
 [-0.5441869   0.2940985  -0.20822387 -0.0375734   0.7567019 ]]
--------------------------------------
原矩阵A:
 [[ 1  5  7  6  1]
 [ 2  1 10  4  4]
 [ 3  6  7  5  2]]
重构后的矩阵B:
 [[ 1.  5.  7.  6.  1.]
 [ 2.  1. 10.  4.  4.]
 [ 3.  6.  7.  5.  2.]]
原矩阵A与重构后的矩阵B是否相同 True

三、利用SVD进行图像压缩

  实现对原图像进行SVD,使用更少的像素表示原图像,实现图像压缩。
  (1)读取图像,将图片像素分解成三个矩阵,分别为RGB;
  (2)对RGB三个通道分别进行SVD并压缩:先进行SVD得到对应的奇异值,然后按照一定标准进行奇异值筛选,即保留 S {S} S 矩阵的前 k {k} k 个,有两种方式可以进行筛选,一种是取整体奇异值的百分比,另一种是取奇异值之和的百分比,然后进行重构,得到压缩后的图像;

from PIL import Image
import numpy as np
import matplotlib.pyplot as plt

'''
函数功能:SVD并还原压缩后的数据。
参数说明:data代表原始矩阵percent代表奇异值总和的百分比
'''
def get_approx_SVD1(data,percent):
    U, s, VT = np.linalg.svd(data)   # 进行SVD
    Sigma = np.zeros(np.shape(data))
    Sigma[:len(s),:len(s)] = np.diag(s)
    count = (int)(sum(s))*percent
    k = -1      # k是奇异值总和的百分比的个数
    curSum = 0  # 初值为第一个奇异值
    while curSum <= count :
        k += 1
        curSum += s[k]
    D = U[:,:k].dot(Sigma[:k,:k].dot(VT[:k,:])) #SVD还原后的数据
    D[D<0] = 0
    D[D>255] = 255
    return np.rint(D).astype("uint8")


'''
函数功能:SVD并还原压缩后的数据。
参数说明:data代表原始矩阵percent代表奇异值个数的百分比
'''
def get_approx_SVD2(data,percent):
    U, s, VT = np.linalg.svd(data)      # 进行SVD
    Sigma = np.zeros(np.shape(data))
    Sigma[:len(s),:len(s)]=np.diag(s)   # 获得奇异值矩阵
    k = (int)(percent*len(s))    # k是奇异值个数的百分比的个数
    D = U[:,:k].dot(Sigma[:k,:k].dot(VT[:k,:]))  # SVD还原后的数据
    D[D < 0] = 0 
    D[D > 255] = 255
    return np.rint(D).astype("uint8")

'''
函数功能:导入图像,进行SVD压缩,并重构图像
参数说明:filename代表文件名,p代表百分比,
          get_approx_SVD代表调用的SVD筛选方法
'''
def rebuild_img(filename,p,get_approx_SVD):
    img = Image.open(filename, 'r')  # 打开文件
    a = np.array(img)  # 获得色素值
    R0 = a[:, :, 0]    # 获得红色的色素值
    G0 = a[:, :, 1]    # 获得绿色的色素值
    B0 = a[:, :, 2]    # 获得蓝色的色素值
    R = get_approx_SVD(R0,p)  # 对红色进行SVD还原
    G = get_approx_SVD(G0,p)  # 对绿色进行SVD还原
    B = get_approx_SVD(B0,p)  # 对蓝色进行SVD还原
    I = np.stack((R, G, B), 2)
    img_svd = Image.fromarray(I)
    return img_svd


filename="qin.jpg"
img = Image.open(filename).convert('RGB')
img_svd1 = rebuild_img(filename,0.2,get_approx_SVD1)
img_svd2 = rebuild_img(filename,0.4,get_approx_SVD1)
img_svd3 = rebuild_img(filename,0.6,get_approx_SVD1)
img_svd4 = rebuild_img(filename,0.8,get_approx_SVD1)
img_svd5 = rebuild_img(filename,1.0,get_approx_SVD1)
plt.figure(1)
plt.subplot(2,3,1)
plt.imshow(img)
plt.axis('off'),plt.title('原图')
plt.subplot(2,3,2)
plt.imshow(img_svd1)
plt.axis('off'),plt.title('p=0.2') 
plt.subplot(2,3,3)
plt.imshow(img_svd2)
plt.axis('off'),plt.title('p=0.4') 
plt.subplot(2,3,4)
plt.imshow(img_svd3)
plt.axis('off'),plt.title('p=0.6') 
plt.subplot(2,3,5)
plt.imshow(img_svd4)
plt.axis('off'),plt.title('p=0.8') 
plt.subplot(2,3,6)
plt.imshow(img_svd5)
plt.axis('off'),plt.title('p=1.0')  

输出为:

利用get_approx_SVD2,同样可以得到:

由此可知,在相同的百分比下,按奇异值个数重构,可以保留更多的信息。

参考资料
人工智能数学基础(北京大学出版社)

你可能感兴趣的:(python,图像处理,信息压缩)