SVD在图像压缩上的应用(基于matlab和python)

图形压缩示例

一个图形矩阵,我们总可以将它分解为以下形式,通过选取不同个数的Σ中的奇异值,就可以实现图像的压缩。
在这里插入图片描述
一个简单的示例如下:
原图形
通过选取不同个数的奇异值,我们的压缩图像可以越来越接近我们的真实图像。
在这里插入图片描述
可以发现,在选取10个奇异值时,能大概看出蝴蝶轮廓;在选取30个奇异值时,蝴蝶已经成型,但是存在很明显的噪声点;而在取50个奇异值或者更多时,图像基本还原,不会再发生很大的改变。奇异值个数是AA’特征值个数即243个,由图分析可以认为,选取前50个奇异值向量就可以代表该图形矩阵的决定部分信息。
我们用奇异值的累积占比记作贡献率,贡献率越大,说明越能代表图像的绝大部分信息。先不讲代码部分,我们将图像分为R,G,B三个通道,得到三个通道的累积贡献率如下图:
在这里插入图片描述
可以看出来,在R,G,B三个不同通道中,虽然前10个奇异值占比(贡献率)不同,但是在大约选取50个奇异值的时候,贡献率几乎都达到了100%,贡献率曲线开始趋于平稳。这点和之前图4-1表现的内容是一样的,即我们可以认为50个奇异值就可以代表该图片矩阵的绝大部分内容。

从特征值分解到奇异值分解(推导)

如果一个向量v是矩阵A的特征向量,将一定可以表示成下面的形式:
在这里插入图片描述
其中,λ是特征向量v对应的特征值,一个矩阵的一组特征向量是一组正交向量。进一步,对于矩阵A,有一组特征向量v,将这组向量进行正交化单位化,就能得到一组正交单位向量。特征值分解,就是将矩阵A分解为如下式:
在这里插入图片描述
其中,Q是矩阵A的特征向量组成的矩阵,Σ则是一个对角阵,对角线上的元素就是特征值。但是,特征值分解只适合于方阵,如果我们遇到的矩阵(在大多数情况下)不是方阵,奇异值分解就很有必要了。奇异值分解是一个能适用于任意矩阵的一种分解的方法,对于任意矩阵A总是存在一个奇异值分解:
(式1-3)
假设A是一个mn的矩阵,那么得到的U是一个mm的方阵,U里面的正交向量被称为左奇异向量。Σ是一个mn的矩阵,Σ除了对角线其它元素都为0,对角线上的元素称为奇异值。是v的转置矩阵,是一个nn的矩阵,它里面的正交向量被称为右奇异值向量。而且一般来讲,我们会将Σ上的值按从大到小的顺序排列。其中,把奇异值和特征值联系起来,各部分求解过程如下:
首先,我们用矩阵A的转置乘以A,得到一个方阵,用这样的方阵进行特征分解,得到的特征值和特征向量满足下面的等式:
(式1-4推导)
对照特征值分解可以看出,A’A的特征向量组成的矩阵就是我们SVD中的V矩阵,而AA’的特征向量组成的就是我们SVD中的U矩阵。而AA’的特征值开方则为A的奇异值,A’A的特征值开方也为A的奇异值,因为A’A和AA’的特征值相同。
在实验中,通过以上原理设计算法实现图形矩阵的奇异值分解。将奇异值从大到小进行排序,并调整特征矩阵U和V使其和奇异值一一对应。选取不同个数的奇异值个数,比较图形效果和累积贡献率变化,确定最优的压缩参数,这种压缩满足:既代表了图形的绝大部分内容,奇异值个数又少。

奇异值分解算法步骤

理论实现方法:
(1) 求AA’的特征值和特征向量,用单位化的特征向量构成 U。
(2) 求A’A的特征值和特征向量,用单位化的特征向量构成 V。
(3) 将A’A或者AA’的特征值求平方根,然后构成 Σ。取不同数量的奇异值,得到不同的压缩效果。
实际实现方法:
(1) 求AA’的特征值和特征向量,用单位化的特征向量构成 U,用特征值构成 Σ
(2) 通过A=U ΣV’反向求V。取不同数量的奇异值,就得到不同的压缩效果。

这里之所以分为理论和实际,是我在实验中发现的,如果用python,理论实现方法很难实现,我们会发现各部分拼接起来不能还原原矩阵,因为每部分的求解标准化参数不同,而在matlab中则不会出现类似的问题。

SVD图像压缩的matlab/python实现

如果只想实现图像压缩,我们大可以直接使用matlab中的svd函数或者python numpy库中linalg.svd对图像矩阵进行分解,进而提取前K个奇异值便能实现SVD图像压缩的效果,但是为了能加强对原理的理解,这里我们通过自编svd分解函数来实现。

python code

import numpy as np #科学计算库
import matplotlib.pyplot as plt # 绘图库
import cv2 # 图形处理库,这里用于通道的合并


def svdCompression(imgFile, K):
    # 读取图片
    img = plt.imread(imgFile)
    # 提取三个通道
    imgR, imgG, imgB = img[:,:,0], img[:,:,1], img[:,:,2]
    # 计算三个通道提取前K个奇异值的压缩矩阵
    R1,contriR = mySVDcompression(imgR,K)
    G1,contriG = mySVDcompression(imgG,K)
    B1,contriB = mySVDcompression(imgB,K)
    #合并三个通道
    img1 = cv2.merge([R1,G1,B1]) 
    img1 = np.rint(img1).astype('uint8')
    return img1,contriR,contriG, contriB


def mySVDcompression(img_matrix,K):
    m,n = img_matrix.shape
    A = np.mat(img_matrix, dtype = float)
    # 计算AA'特征值和向量
    lambda1, U = np.linalg.eig(A.dot(A.T))
    # 计算累积贡献率
    contri = contribution(lambda1)
    S = np.ones((m, n))
    S[:m,:m] = np.diag(np.sqrt(lambda1))
    US = U.dot(S)
    V = US.I.dot(A)
    S[K:,:] = 0
    S[:,K:] = 0
    return U.dot(S).dot(V),contri

def contribution(L):
    a = []
    b = 0
    for ii in L:
        b = b+ii/L.sum()
        a.append(b)
    return a
    
    
if __name__ == "__main__":
    for K in range(10,20,20):
       # 这里图像可以设置为其他图像
        Img,contriR,contriG, contriB= svdCompression('butterfly.bmp', K)
        plt.subplot(2,3,(K-10)/20+1)
        plt.imshow(Img)
        plt.title(('using %d singular values'%K))
    plt.figure()
    plt.plot(contriR)
    plt.plot(contriG)
    plt.plot(contriB)
    plt.legend(['Tunnel R','Tunnel G','Tunnel B'])
    plt.title('Variation of Singular Value Contribution Rate')
    plt.xlabel('Number of Singular Values')
    plt.ylabel('Contribution Rate')

matlab code

在matlab中,我们可以直接使用[U,S,V] = svd(imgMatrix)直接获取对应的U,S,V.然后,我们可以选择保留前多少个最大的奇异值。如果是灰度图片,我们直接处理即可。如果是彩色图片,我们可以按照rgb三个通道依次做svd分解并保留同样个数的奇异值,最后再将三个通道合并即可。
Note: uint8和double类型的相互转换

以下函数可以实现单通道/灰度图片的压缩处理
输入:rawGrayImage 单通道图片/灰度图片
singularValueCount 要保留的前singularValueCount 个最大的奇异值个数
输出:compressedImage 压缩后的图片

function compressedImage = svdCompress(rawGrayImage,singularValueCount)
% get the size of primitive image
[rowCount,~] = size(rawGrayImage);
% we can get U,S,V directly in matlab
[U,S,V] = svd(rawGrayImage);
% big idea here,select the number of K maximum singular values
% use 0 to denote that we discard  the remained small singular values
S1 = S;
S1(singularValueCount:rowCount,:) = 0;
S1(:,singularValueCount:rowCount) = 0;
compressedImage = U*S1*V';

以下为在rgb图上的测试代码

% clear workspace
clear,clc
% read the image, get size(rowCount, columnCount, dimensionCount)
img = imread('cute_girl.jpg');
img = double(img);
[r,c,d] = size(img);
% get the red,green,blue channels respectively 
red = img(:,:,1);
green = img(:,:,2);
blue = img(:,:,3);
% set the number of selected singular values
numOfSV = 10;
% get the compressed image of each channel respectively
cRed = svdCompress(red,numOfSV);
cGreen = svdCompress(green,numOfSV);
cBlue = svdCompress(blue,numOfSV);
% combination of three channels to get a RGB image
img1(:,:,1) = cRed;
img1(:,:,2) = cGreen;
img1(:,:,3) = cBlue;
imshow(uint8(img1));

在这里插入图片描述

python 实现SVD图像压缩注意事项

  • 读取后数据格式未转换
    使用imread读取图片后,格式为uint8,发现做矩阵乘积的时候数值明显不对。后来将其转换为mat时修改格式为float就好了。

  • 不适合分部求取U,S,V来组合构造
    求取的A’A,AA’的特征值和特征向量来构造,发现很难还原A,可能因为每部分的特征向量标准化的模式不一样。所以没有选择理论上的求解,而是选择了第二种求解方法。

  • 三个通道压缩后的矩阵不能imshow
    想了很久,后来理解了。和问题一是对应的,应该把转换为float类型的矩阵变为uint8就可以了,事实上也是这样的。

  • 矩阵中型的变化
    这也是python编程和matlab编程的一个区别了,在matlab中我们很容易看出矩阵行的变化,而在python中由于基于向量就涉及到很多格式的互相转换才可以方便我们的计算。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 撸撸猫 设计师:C马雯娟 返回首页