图像压缩是为了节省存储空间,增加传输速度。图像压缩的理想标准是信息丢失的最少,压缩比例的最大。
在无损编码(不损失图像的质量的压缩)中,有一种编码很常见,被称作 Huffman 编码。哈夫曼编码是一种无损编码,依据信源符号出现的概率来构造其码字,对出现概率大的字符使用较短的码字,对出现概率低的字符则使用较长的码字,从而达到压缩数据的目的,哈夫曼编码又称为最佳编码。
1、统计信源字符中各符号出现的概率,将各字符出现的概率由大到小的顺序排列;
2、将最小的两个概率相加,合并成新的概率,与其他概率重新按由大到小的顺序排列;
3、重新排列后将两个最小概率合并相加合并为新的概率,即重复步骤2,直到最后两个概率之和为 1;
4、每个相加的组合中,概率大的指定为 0,概率小的指定为 1,相等则任意指定;
5、找出由每一个信源字符到达概率为 1.0 处的路径,顺序记录路径的每一个 1 和 0 的数字编码;
6、反向写出编码,即为该信源字符的哈夫曼编码。
function [H] = Huffman_code(NK)
p_sum = sum(sum(NK));
n = length(NK);
p = zeros(1,n);
i = 1:n ;
p(1,i) = NK(i,1);
p = p/p_sum;
% p为概率分布,此函数功能是进行哈夫曼编码
% h为各个元素的码子
% e为输出的平均码长
q = p;
m = zeros(n-1,n);%m中存储的是每次合并的和没合并的概率,用于后面求编码组合
[q,e]=sort(q);
[k,m(1,:)]=sort(e);
q=[q(1)+q(2),q(3:n),1];
for i=2:n-1
[q,e]=sort(q);
[k,e]=sort(e);
e = [e(1:n-i+1),zeros(1,i-1)];%找到元素在排序后数组的位置
for j = 1:n
if(m(i-1,j) > (e(1)+1))%每轮按照其值记录,后面有这一思路的解释
m(i,j) = m(i - 1,j) - 1;
elseif (m(i-1,j) > 2)
m(i,j) = m(i - 1,j) - 2;
else
m(i,j) = e(1);
end
end
%由数组l 构建一个矩阵,该矩阵表明概率合并时的顺序,用于后面的编码
q=[q(1)+q(2),q(3:n),1];
end
%% 每一列中的1和2表达编码中的0和1,其余数字为空,可以得到每个灰度的霍夫曼编码
H = cell(n,1);%字符串数组
for i = 1:n
H{i} = '';
for j = n-1:-1:1
temp = m(j,i);
if(temp == 1)
H{i} = [H{i},'0'];
elseif(temp == 2)
H{i} = [H{i},'1'];
end
end
end
这里面代码的思想如下:
例如对于一个霍夫曼编码(数字大小代表频数):4 65 9 6 7。则第一次从小到大排序,4 65 9 6 7在排序数组的顺序:1 5 4 2 3;也就是说会将4 6合并成10,之后再排序:3 4 2 3 1;这里我们可以得到,10在数组10 65 9 7中是第三位,因此4 6都是排在3号,7是1号,9是2号。之后依次这样迭代:1 3 2 1 2;1 2 1 1 1;当只有1和2时就结束。
上面的数组组合成二维数组,我们纵向看每一列:1 3 1 1;5 4 3 2;4 2 2 1;2 3 1 1;3 1 2 1;这里面看每一列,从右向左,1代表是0,2代表1。也就是说4:000 65:1 9:011 6:001 7:010;完成霍夫曼编码。
function Str_im = HuffCompress(im_b,H)
[h,w] = size(im_b);
im_b = uint16(im_b);
Str_im = '';
for i = 1:h
for j = 1:w
k = im_b(i,j) + 1;
Str_im = [ Str_im , H{k} ];%检测每一个像素点,将值对应的编码加入字符串中
end
end
end
这里代码的思想就是根据上面得到的霍夫曼编码来进行图像的压缩。
得到图像压缩之后的结果需要存进二进制文件中。存入二进制文件的方法有很多,这里使用下面这种方法:
function dataWrite(Str_im,fileName)
%% 格式转换,为了能够以二进制存储
k = 8 - mod(length(Str_im),8);
for i = 1:k
Str_im = [ Str_im , '0']; %补零
end
k = uint32(length(Str_im)/8); %按8位划分存储
im_com = zeros(1,k);
for i = 1:k
sub = Str_im(8*i-7:8*i);
temp = uint8((sub(1)-'0')*128+(sub(2)-'0')*64+(sub(3)-'0')*32+...
(sub(4)-'0')*16+(sub(5)-'0')*8+(sub(6)-'0')*4+(sub(7)-'0')*2+(sub(8)-'0'));
im_com(1,i) = temp;%转换成uint8类型存储
end
data = uint8(im_com);
%% 二进制存储
fid=fopen(fileName,'wb');
[w,h] = size(data);
if(fid>0)
for i = 1 : h
fwrite(fid,data(i),'uint8');%将编码存进文件
end
end
fclose(fid);
end
使用这种方法可以得到图像二进制的存储文件。
一套图像的压缩系统需要读取并完成解压,这里是读取图像的二进制文件的方法代码:
function [data]=dataRead(fileName)
fid=fopen(fileName,'rb');
if(fid>0)
[data,num]=fread(fid,inf,'uint8');%以uint8格式进行读取
end
fclose(fid);
%% 格式转换,转换成字符串
[h,w] = size(data);
Str_im = '';
for i = 1:h
temp = dec2bin(data(i,1));
k = 8 - length(temp);
for j = 1:k
temp = ['0',temp];
end
Str_im = [Str_im,temp];
end
data = Str_im;
end
利用这个就可以得到图像的二进制编码,但是由于我们在存储的时候是以8个bit为一个单位的,因此尾部会有多余的0,一般不会产生干扰。
function [im,num] = iHuff(data,H)
L = length(data);
h = length(H);
im = zeros(uint32(L/8),1);%先预分配图像空间
num = 0;i = 1;
while(i <= L)
for j = i:L
sub = data(i:j);%之后截取一段编码
for k = 1:h
if strcmp(H{k},sub)%比较是否和霍夫曼编码一致
i = j+1;%一致,则向后挪动,跳出
break
end
end
if(i>j)
break;
end
end
if (i>j)
num = num + 1;%记录解压出的图像元素的个数
im(num,1) = k - 1;%记录灰度值
end
if j == L
break;
end
end
end
这样就解压出来的图像,最后在主函数中利用图像的宽和高的信息对上面解压出的信息进行形状的整合,就得到了解压后的图像。
代码上面已经给出,下面是这样进行实验的。
1、首先训练图像有100张,根据图片灰度值的分布进行huffman编码的函数的计算,之后对测试图像进行压缩和解压观察效果怎么样,并记录压缩比的大小。
2、之后不再使用训练图像,只使用当前压缩图像的分布来进行huffman编码的函数的计算,之后进行图像的压缩和解压,记录压缩比。
3、将上述的压缩图像全部存储在二进制文件中。
上面四张图像,压缩比的计算结果如下:
第一张:0.988066324268627,第二张:0.988321296170797,第三张:1.002851967398436,第四张:0.972685104053423。
如果不使用测试集进行评估,直接使用该图像的灰度值统计进行压缩,可以得到下面的压缩比:
第一张:1.040974801995827,第二张:1.114752556086055,第三张:1.085749905256160,第四张:1.415766199936345。
由于本身灰度值是0~255,使用8bit来表示,利用率本身比较高,使用huffman编码来压缩,压缩效果不是很理想。
使用训练集的图像来进行频率的计算,可能无法当做当前的图像频率,从而导致压缩的图像无法达到更优的压缩结果,对于一些图像可能反而使压缩比小于1。
不使用训练集图像,仅仅使用当前图像虽然可以得到好一点的压缩结果,但是压缩的结果只有最后第四幅图像结果好一些。这是因为当图像灰度值分布不均匀时,哈夫曼编码效率比较高。
本次实验主要学习了huffman编码的压缩和解压缩的处理方法,了解图像编码的目的及意义,加深对图像编码的感性认识。
通过相关编码的实现,和图像的测试结果,进一步了解了huffman编码的压缩结果和什么有关。即图像灰度值分布不均匀时,哈夫曼编码效率比较高。