Matlab实现灰度图像无损编码压缩和游程编码

1. 要求

选择灰度图像,按照行的方式展开像得到一维的向量。按照一维预测的公式:

\widehat{x_{k}}=\sum_{i=1}^{k-1}a_{i}x_{i}

自行设计预测算法实现一维无损预测压缩。将预测压缩后的一维向量(由预测误差组成),进行一维行程/游程编码。计算原图、最终行程/游程编码压缩后数据所需要的存储空间,计算压缩率。

2. 分析

对于灰度图像I的任意一行像素X,其表达式如下,其中n为灰度图像I的列数。

X=[x_{1}, x_{2}, ..., x_{n}]

给定系数A表达式如下

A=[a_{1}, a_{2}, ..., a_{n-1}]

X的第k个元素的预测值\hat{x_{k}}可表示为

\widehat{x_{k}}=\sum_{i=1}^{k-1}a_{i}x_{i}

预测误差e_{k}可表示为

e_{k}=x_{k}-\hat{x_{k}}

特别地,当k=1时,\hat{x_{k}}不存在,则e_{k}=x_{k}

更一般地,用矩阵进行简化可得

\hat{x_{k}}=[x_{1}, x_{2}, ..., x_{k-1}] * [a_{1}, a_{2}, ..., a_{k-1}]^{T}

e_{k}=x_{k}-[x_{1}, x_{2}, ..., x_{k-1}] * [a_{1}, a_{2}, ..., a_{k-1}]^{T}

则可以写作如下代码

clear, close all
I = imread('cameraman.tif');
[row, col] = size(I); % 获取I的行数和列数
a = rand(col-1, 1);   % col-1个系数
e = zeros(size(I));   % 误差矩阵
for i = 1: row        % 遍历所有行
    e(i,1) = I(i,j);  % 第i行的第1列元素误差等于实际值
    for j = 2: col    % 从第2列开始遍历
        e(i,j) = I(i,j) - I(i,1:j-1) * a(1:j-1);
    end
end

为了方便,我使用rand函数生成长度为col-1(对应于上述的n-1)的列向量,其中的元素为0~1之间的随机浮点数。

实际运行上述代码的话,除了精度问题的报错,最大的问题是运行时间长。

3. 编码阶段-矩阵优化

可以观察到,上述循环的本质是逐列计算,因此我们可以从每一列入手,进行优化。

对于灰度图像I的第j列像素Y_{j}, 其表达式如下,其中m为灰度图像I的行数,I_{i,j}表示第i行第j列的值。

Y_{j}=[I_{1,j}, I_{2,j}, ..., I_{m,j}]^{T}

于是可以得到 

[\hat{I_{1,j}}, \hat{I_{2,j}}, ..., \hat{I_{m,j}}]^{T}=\begin{bmatrix} I_{1,1} & I_{1,2}& ...&I_{1,j-1} \\ I_{2,1} & I_{2,1}& ... &I_{2,j-1} \\ ... & ... & ... & ...\\ I_{m,1} & I_{m,2}&... & I_{m,j-1} \end{bmatrix} * [a_{1}, a_{2}, ..., a_{j-1}]^{T}

 即

\hat{Y_{j}}=[Y_{1}, Y_{2}, ..., Y_{j-1}] * [a_{1}, a_{2}, ..., a_{j-1}]^{T}

于是

E_{j}=Y_{j}-[Y_{1}, Y_{2}, ..., Y_{j-1}] * [a_{1}, a_{2}, ..., a_{j-1}]^{T}

其中E_{j}表示误差矩阵中的第j

特别地,当j=1时,\hat{Y_{1}}不存在,则E_{1}=Y_{1}

因此图像编码阶段的代码可写为

clear, close all
I = imread('cameraman.tif');
[row, col] = size(I); % 获取I的行数和列数
a = rand(col-1, 1);   % col-1个系数
e = zeros(size(I));   % 生成误差矩阵
e(:,1) = I(:,1);      % 第一列特殊计算
II = double(I);       % I的副本,类型为double
for j = 2:col         % 遍历列
    e(:,j) = II(:,j) - (II(:,1:j-1) * a(1:j-1));
end
e = round(e);         % 将误差矩阵取整

这里说下要生成一个I的副本,且类型为double,这是因为在下面的矩阵乘法运算的时候,I的默认类型是uint8,范围在0~255,uint8的矩阵和任何矩阵进行乘法运算,结果仍为uint8,即超出255的变为255,低于0的变为0,浮点数自动取整。为了得到较为准确的误差矩阵,需要在double类型上进行运算,于是预先生成一个double类型的I的副本。

4. 图像解码

只需要将上述编码过程逆操作即可得到图像解码对应的代码

J = zeros(size(e));
J(:,1) = e(:,1);
for j = 2: col
    J(:,j) = e(:,j) + (J(:,1:j-1) * a(1:j-1));
end
J = uint8(J); %解码图像

至此,可以解释图像编码的应用场景。比如,张三需要将大量图像通过网络传输给李四,由于网络带宽限制,单张图像都无法直接传输,于是张三和李四事先约定了一个编码系数向量A。

在发送端,每一张图片都使用A编码得到一个误差矩阵e;

传输的过程中只需要传输误差矩阵e;

在接收端,只需要用A对误差矩阵e进行解码即可得到原始图像。

我们知道,这里的误差矩阵其实和图像的规模是一致的,即行数和列数都等于图像的行数和列数。

灰度图像中的每一个像素都由8位2进制进行编码表示,实际范围在0~256。

想要传输误差矩阵e,一个方法是使用调优过的系数向量A,使得生成的每一个误差矩阵中的任何一个元素,都可以用小于8位的2进制编码进行表示;另一个方法就是题目要求的,对误差矩阵进行游程编码。

游程编码(Run Length Coding,RLC)是一种简单的熵编码(无损压缩编码)方法。其基本原理是,将具有相同数值的、连续出现的信源符号用“符号+符号出现次数”的形式表示。如“zzxxxxyyyyyzzz”将被编码为“2z4x5y3z”。

这样就存在一个问题:假如图像中相邻的像素均不相等,那么游程编码非但起不到压缩作用,还会因为需要传输像素的出现次数,而使数据量增加一倍。因此,直接对图像进行编码时,游程编码只适用于有较多灰度相同的图像,也就是说图像最好包含大片的平坦区。

想要得到效果出色的游程编码结果,就必须使误差矩阵中出现较多连续的值,而误差矩阵中值分布情况,只与图像本身和编码系数向量A有关。

这里就有几个引人深思的问题:

  1. 能不能找出一个A,并且给出证明,任意的图像经过A编码得到的误差矩阵,均适合使用游程编码,或者误差矩阵中的每一个元素可以用7位2进制甚至6位2进制进行编码。
  2. 退而求其次,对于一个图像,能不能通过特定算法,花费较少计算代价计算出一个A,将这个特定的A和它对应的图像一起传输。

5. 游程编码

对于一个列向量K,对其游程编码后得到矩阵L,其中第一列表示值,第二列表示这个值连续出现的次数。

K = \begin{bmatrix} 12\\ 12\\ 13 \\ 14 \\ 14 \\ 13 \\ 12 \end{bmatrix}\Rightarrow L=\begin{bmatrix} 12 & 2\\ 13 & 1\\ 14 & 2\\ 13 & 1\\ 12 & 1 \end{bmatrix}

 代码如下

%% 游程编码
function [L, size1, size2] = RLC(K)
L = [K(1) 1];
Lj = 1;
for i = 2: length(K)
    if K(i) == L(Lj,1)
        L(Lj,2) = L(Lj,2) + 1;
    else
        Lj = Lj + 1;
        L(Lj,:) = [K(i) 1];
    end
end
L_1 = ceil(log2(max(L(:,1))-min(L(:,1))+1));
L_2 = ceil(log2(max(L(:,2))-min(L(:,2))+1));
size1 = L_1 * length(L);
size2 = L_2 * length(L);
end

这里举例说明一下L_1的含义,如果矩阵L的第一列中最大值为260,最小值为20,则max(L(:,1))-min(L(:,1))+1的结果为260-20+1=241,显然20-260之间有241个连续的值,可以被8位2进制所编码表示,ceil(log2(X))计算的就是X能被几位二进制所表示。

L_1和L_2分别表示第一列和第二列能被几位二进制表示,size1和size2分别表示第一列和第二列占用的总位数。

size1+size2就是编码L矩阵所需要的总位数(总位数除以8就是总字节数)

6. 完整代码

clear, close all
I = imread('cameraman.tif');
subplot(1,3,1), imshow(I), title('原始图像')
[row, col] = size(I);
a = -1 + 2 * rand(col-1, 1);
a = a / 200;
e = zeros(size(I));
%% encode
%  由原图像I和预测系数a生成误差矩阵e
e(:,1) = I(:,1);      % 第一列特殊计算
II = double(I);       % I的副本,类型为double
for j = 2:col         % 遍历列
    e(:,j) = II(:,j) - (II(:,1:j-1) * a(1:j-1));
end
e = round(e);         % 将误差矩阵取整
subplot(1,3,2), imshow(uint8(e)), title('误差矩阵')
%% decode 
%  该过程只用到误差矩阵e和预测系数a
J = zeros(size(e));
J(:,1) = e(:,1);
for j = 2: col
    J(:,j) = e(:,j) + (J(:,1:j-1) * a(1:j-1));
end
J = uint8(J);          % 解码图像
subplot(1,3,3), imshow(J), title('解码图像')
%% run length encode
K = e';
K = K(:);
[L, size11, size12] = RLC(K);       % 对K进行游程编码
[L2, size21, size22] = RLC(L(:,2)); % 对游程编码的矩阵的第二列再次进行游程编码
total_size = 8 * length(I(:));
% 图像压缩率 = 原始图像所需空间 / 压缩图像所需空间
ratio = total_size / (size11+size21+size22);

%% 游程编码函数
function [L, size1, size2] = RLC(K)
L = [K(1) 1];
Lj = 1;
for i = 2: length(K)
    if K(i) == L(Lj,1)
        L(Lj,2) = L(Lj,2) + 1;
    else
        Lj = Lj + 1;
        L(Lj,:) = [K(i) 1];
    end
end
L_1 = ceil(log2(max(L(:,1))-min(L(:,1))+1));
L_2 = ceil(log2(max(L(:,2))-min(L(:,2))+1));
size1 = L_1 * length(L);
size2 = L_2 * length(L);
end

  • 7
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Toblerone_Wind

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值