霍夫曼 解压缩

作者&投稿:年会 (若有异议请与网页底部的电邮联系)
求助:关于霍夫曼压缩图像~

clear;
f0=imread('lena.jpg');
subplot(121)
imshow(uint8(f0));
xlabel('\fontsize{16}原始图像');f=abs(f0/4)-10;
[M,N]=size(f);p=zeros(1,61);for t=1:61
count=0;
for i=1:M
for j=1:N
if f(i,j)==t-1
count=count+1;
end
end
end
p(t)=count;p0=p;
endcore=cell(61,1);
sign=zeros(61);for hh=1:60
re=M*N;
for t=1:61
if (p(t)0)
re=p(t);
end
end
t=1;
while (p(t)~=re)&(t<61)
t=t+1;
end
if sign(t,1)==0
core{t}='0';
else
core{t}=['0',core{t}];
i=1;
while (sign(t,i)~=0)&(i<61)
core{sign(t,i)}=['0',core{sign(t,i)}];
i=i+1;
end
end
p(t)=0;
cou=t;re1=M*N;
for t=1:61
if (p(t)0)
re1=p(t);
end
end
t=1;
while (p(t)~=re1)&(t<61)
t=t+1;
end
if sign(t,1)==0
core{t}='1';
else
core{t}=['1',core{t}];
i=1;
while (sign(t,i)~=0)&(i<61)
core{sign(t,i)}=['1',core{sign(t,i)}];
i=i+1;
end
end
p(t)=p(t)+re;
cou1=t;
i=1;
while (sign(t,i)~=0)&(i<61)
i=i+1;
end
sign(t,i)=cou;
i=i+1;
j=1;
while (sign(cou,j)~=0)&(j<61)
sign(t,i)=sign(cou,j);
i=i+1;
j=j+1;
end
end %产生huffman码fc=cell(M,N);
for i=1:M
for j=1:N
if f(i,j)<61
fc{i,j}=core{f(i,j)+1};
else
fc{i,j}='0';
end
end
end %fc
imcore=char();
for i=1:M
for j=1:N
imcore=[imcore,fc{i,j}];
end
endsave picture imcore core; %保存图片码流和编码对应表
解码:clear;
load picture.mat %载入图片码流和编码对应表
Nc=size(core);
Nic=size(imcore);
flag=0;
i=1;
j=1;
n=1;
cz=char();
f=zeros(128);
for n=1:400930
if flag==0
cz=[cz,imcore(n)];
else
cz=imcore(n);
flag=0;
end
for t=1:61
if strcmp(cz,core{t})
f(j,i)=t;
flag=1;
if i>127;
i=1;
j=j+1;
else
i=i+1;
end
break;
end
end
end
f=uint8(f*4+35);
subplot(122)
imshow(f);xlabel('\fontsize{16}解码后的图像');

霍夫曼算法不是一个流式压缩算法,需要统计整个文件的所有信息,才能开始压缩,所以速度上不行。一般的流式压缩算法,例如LZMA,他只要一段文件的局部信息就可以进行压缩了,所以速度上有优势。而正因为它用的局部信息,压缩比又不如霍夫曼。

正所谓凡事有利必有弊,速度快的算法,压缩比不够高,压缩比高的算法,速度上又不行。计算机编程之难点,不是什么高深的算法,而是要根据需求找到事物之间的平衡点,选取最适合的策略。

哈夫曼编码(Huffman Coding)是一种编码方式,以哈夫曼树—即最优二叉树,带权路径长度最小的二叉树,经常应用于数据压缩。 在计算机信息处理中,“哈夫曼编码”是一种一致性编码法(又称"熵编码法"),用于数据的无损耗压缩。这一术语是指使用一张特殊的编码表将源字符(例如某文件中的一个符号)进行编码。这张编码表的特殊之处在于,它是根据每一个源字符出现的估算概率而建立起来的(出现概率高的字符使用较短的编码,反之出现概率低的则使用较长的编码,这便使编码之后的字符串的平均期望长度降低,从而达到无损压缩数据的目的)。这种方法是由David.A.Huffman发展起来的。 例如,在英文中,e的出现概率很高,而z的出现概率则最低。当利用哈夫曼编码对一篇英文进行压缩时,e极有可能用一个位(bit)来表示,而z则可能花去25个位(不是26)。用普通的表示方法时,每个英文字母均占用一个字节(byte),即8个位。二者相比,e使用了一般编码的1/8的长度,z则使用了3倍多。倘若我们能实现对于英文中各个字母出现概率的较准确的估算,就可以大幅度提高无损压缩的比例。

本文描述在网上能够找到的最简单,最快速的哈夫曼编码。本方法不使用任何扩展动态库,比如STL或者组件。只使用简单的C函数,比如:memset,memmove,qsort,malloc,realloc和memcpy。
因此,大家都会发现,理解甚至修改这个编码都是很容易的。

背景
哈夫曼压缩是个无损的压缩算法,一般用来压缩文本和程序文件。哈夫曼压缩属于可变代码长度算法一族。意思是个体符号(例如,文本文件中的字符)用一个特定长度的位序列替代。因此,在文件中出现频率高的符号,使用短的位序列,而那些很少出现的符号,则用较长的位序列。
编码使用
我用简单的C函数写这个编码是为了让它在任何地方使用都会比较方便。你可以将他们放到类中,或者直接使用这个函数。并且我使用了简单的格式,仅仅输入输出缓冲区,而不象其它文章中那样,输入输出文件。
bool CompressHuffman(BYTE *pSrc, int nSrcLen, BYTE *&pDes, int &nDesLen);
bool DecompressHuffman(BYTE *pSrc, int nSrcLen, BYTE *&pDes, int &nDesLen);
要点说明
速度
为了让它(huffman.cpp)快速运行,我花了很长时间。同时,我没有使用任何动态库,比如STL或者MFC。它压缩1M数据少于100ms(P3处理器,主频1G)。
压缩
压缩代码非常简单,首先用ASCII值初始化511个哈夫曼节点:
CHuffmanNode nodes[511];
for(int nCount = 0; nCount < 256; nCount++)
nodes[nCount].byAscii = nCount;
然后,计算在输入缓冲区数据中,每个ASCII码出现的频率:
for(nCount = 0; nCount < nSrcLen; nCount++)
nodes[pSrc[nCount]].nFrequency++;
然后,根据频率进行排序:
qsort(nodes, 256, sizeof(CHuffmanNode), frequencyCompare);
现在,构造哈夫曼树,获取每个ASCII码对应的位序列:
int nNodeCount = GetHuffmanTree(nodes);
构造哈夫曼树非常简单,将所有的节点放到一个队列中,用一个节点替换两个频率最低的节点,新节点的频率就是这两个节点的频率之和。这样,新节点就是两个被替换节点的父节点了。如此循环,直到队列中只剩一个节点(树根)。
// parent node
pNode = &nodes[nParentNode++];
// pop first child
pNode->pLeft = PopNode(pNodes, nBackNode--, false);
// pop second child
pNode->pRight = PopNode(pNodes, nBackNode--, true);
// adjust parent of the two poped nodes
pNode->pLeft->pParent = pNode->pRight->pParent = pNode;
// adjust parent frequency
pNode->nFrequency = pNode->pLeft->nFrequency + pNode->pRight->nFrequency;
这里我用了一个好的诀窍来避免使用任何队列组件。我先前就直到ASCII码只有256个,但我分配了511个(CHuffmanNode nodes[511]),前255个记录ASCII码,而用后255个记录哈夫曼树中的父节点。并且在构造树的时候只使用一个指针数组(ChuffmanNode *pNodes[256])来指向这些节点。同样使用两个变量来操作队列索引(int nParentNode = nNodeCount;nBackNode = nNodeCount –1)。
接着,压缩的最后一步是将每个ASCII编码写入输出缓冲区中:
int nDesIndex = 0;
// loop to write codes
for(nCount = 0; nCount < nSrcLen; nCount++)
{
*(DWORD*)(pDesPtr+(nDesIndex>>3)) |=
nodes[pSrc[nCount]].dwCode << (nDesIndex&7);
nDesIndex += nodes[pSrc[nCount]].nCodeLength;
}
(nDesIndex>>3): >>3 以8位为界限右移后到达右边字节的前面
(nDesIndex&7): &7 得到最高位.
注意:在压缩缓冲区中,我们必须保存哈夫曼树的节点以及位序列,这样我们才能在解压缩时重新构造哈夫曼树(只需保存ASCII值和对应的位序列)。
解压缩
解压缩比构造哈夫曼树要简单的多,将输入缓冲区中的每个编码用对应的ASCII码逐个替换就可以了。只要记住,这里的输入缓冲区是一个包含每个ASCII值的编码的位流。因此,为了用ASCII值替换编码,我们必须用位流搜索哈夫曼树,直到发现一个叶节点,然后将它的ASCII值添加到输出缓冲区中:
int nDesIndex = 0;
DWORD nCode;
while(nDesIndex < nDesLen)
{
nCode = (*(DWORD*)(pSrc+(nSrcIndex>>3)))>>(nSrcIndex&7);
pNode = pRoot;
while(pNode->pLeft)
{
pNode = (nCode&1) ? pNode->pRight : pNode->pLeft;
nCode >>= 1;
nSrcIndex++;
}
pDes[nDesIndex++] = pNode->byAscii;
}

这个比较难,你的数学底子怎么样?复变函数要熟~