新浪博客

[转]算术编码的原理与分析(二)

2011-02-19 17:54阅读:
转自:http://kulasuki115.blogcn.com/diary,201492702.shtml
2.2、算术编码与Huffman编码的区别   霍夫曼编码属于码字长度可变的编码类,即从下到上的编码方法。同其他码字长度可变的编码一样,可区别的不同码字的生成是基于不同符号出现的不同概率。生成霍夫曼编码算法基于一种称为“编码树”的技术。算法步骤如下: ① 初始化,根据符号概率的大小按由大到小顺序对符号进行排序。 ② 把概率最小的两个符号组成一个新符号,即新符号的概率等于这两个符号概率之和。 ③ 重复第②步,直到形成一个符号为止,其概率最后等于1。 ④ 从编码树的根开始回溯到原始的符号,并将每一个下分枝赋值为1,上分枝赋值为0。
  采用霍夫曼编码时有两个问题值得注意: ① 霍夫曼编码没有错误保护功能,在译码时,如果码串中没有错误,那么就能一个接一个地正确译出代码。但如果码串中有错误,哪怕仅仅是 1位出现错误,也会引起一连串的错误,这种现象称为错误传播。计算机对这种错误也无能为力,说不出错在哪里,更谈不上去纠正它。 ② 霍夫曼编码是可变长度码,因此很难随意查找或调用压缩文件中间的内容,然后再译码,这就需要在存储代码之前加以考虑。
  而算术编码的基本原理是将编码的消息表示成实数0和1之间的一个间隔,消息越长,编码表示它的间隔就越小,表示这一间隔所需的二进制位就越多。 算术编码用到两个基本的参数:符号的概率和它的编码间隔。信源符号的概率决定压缩编码的效率,也决定编码过程中信源符号的间隔,而这些间隔包含在0到1之间。编码过程中的间隔决定了符号压缩后的输出。 给定事件序列的算术编码步骤如下: ① 编码器在开始时将“当前间隔”[L,H]设置为[0,1]。 ② 对每一事件,编码器按步骤A和B进行处理。 A.编码器将“当前间隔”分为子间隔,每一个事件一个。 B.一个子间隔的大小与下一个将出现的事件的概率成比例,编码器选择子间隔对应于下一个确切发生的事件,并使它成为新的“当前间隔”。③最后输出的“当前
间隔”的下边界就是该给定事件序列的算术编码。
  算术编码是一种到目前为止编码效率最高的统计熵编码方法,它比著名的Huffman编码效率提高10%左右,但由于其编码复杂性和实现技术的限制以及一些专利权的限制,所以并不象Huffman编码那样应用广泛。算术编码有两点优于Huffman码: ①它的符号表示更紧凑; ②它的编码和符号的统计模型是分离的,可以和任何一种概率模型协同工作。后者非常重要, 因为只要提高模型的性能就可以提高编码效率。
  Huffman 码字必定是整数的比特长,这样就会产生问题:如一个符号的概率为1P3 ,则编码该符号的最优比特数大约是1.6,那么Huffman不得不将其码字设为1比特或2比特,并且每种选择都会得到比理论上可能的长度更长的压缩消息。而算术编码可以解决这个问题。算术编码是一种高效清除字串冗余的算法。它避开用一个特定码字代替一输入符号的思想,而用一个单独的浮点数来代替一串输入符号, 避开了Huffman编码中比特数必须取整的问题。但是算术编码的实现有两大缺陷: ① 很难在具有固定精度的计算机完成无限精度的算术操作。 ② 高度复杂的计算量不利于实际应用。
  算术编码是一种无失真的编码方法,能有效地压缩信源冗余度,属于熵编码的一种。算术编码的一个重要特点就是可以按分数比特逼近信源熵,突破了Haffman编码每个符号只不过能按整数个比特逼近信源熵的限制。对信源进行算术编码,往往需要两个过程,第一个过程是建立信源概率表,第二个过程是对信源发出的符号序列进行扫描编码。而自适应算术编码在对符号序列进行扫描的过程中,可一次完成上述两个过程,即根据恰当的概率估计模型和当前符号序列中各符号出现的频率,自适应地调整各符号的概率估计值,同时完成编码。尽管从编码效率上看不如已知概率表的情况,但正是由于自适应算术编码具有实时性好、灵活性高、适应性强等特点,在图像压缩、视频图像编码等领域都得到了广泛的应用。
2.3、算术编码的静态与自适应模型
  举个简单的例子来说明吧。考虑某条信息中可能出现的字符仅有 a b c 三种,我们要压缩保存的信息为 bccb。对信息 bccb 我们统计出其中只有两个字符,概率分布为 Pb = 0.5,Pc = 0.5。我们在压缩过程中不必再更新此概率分布,每次对区间的划分都依照此分布即可,对上例也就是每次都平分区间。这样,我们的压缩过程可以简单表示为:
输出区间的下限 输出区间的上限
--------------------------------------------------
压缩前 0.0 1.0
输入 b 0.0 0.5
输入 c 0.25 0.5
输入 c 0.375 0.5
输入 b 0.375 0.4375
  可以看出,最后的输出区间在 0.375 - 0.4375 之间,该信息的熵值为 4 个二进制位,甚至连一个十进制位都没有确定,也就是说,整个信息根本用不了一个十进制位。如果改用二进制来表示上述过程的话,会发现可以非常接近该信息的熵值。
  那为什么还要采用自适应模型呢?因为静态模型无法适应信息的多样性,例如,以上得出的概率分布没法在所有待压缩信息上使用,为了能正确解压缩,必须再消耗一定的空间保存静态模型统计出的概率分布,保存模型所用的空间将使我们重新远离熵值。其次,静态模型需要在压缩前对信息内字符的分布进行统计,这一统计过程将消耗大量的时间,使得本来就比较慢的算术编码压缩更加缓慢。另外还有最重要的一点,对较长的信息,静态模型统计出的符号概率是该符号在整个信息中的出现概率,而自适应模型可以统计出某个符号在某一局部的出现概率或某个符号相对于某一上下文的出现概率,换句话说,自适应模型得到的概率分布将有利于对信息压缩(可以说结合上下文的自适应模型的信息熵建立在更高的概率层次上,其总熵值更小),好的基于上下文的自适应模型得到的压缩结果将远远超过静态模型。
  通常用“阶”(order)这一术语区分不同的自适应模型。刚刚上面的例子采用的是0阶自适应模型,也就是说,该例子中统计的是符号在已输入信息中的出现概率,没有考虑任何上下文信息。如果我们将模型变成统计符号在某个特定符号后的出现概率,那么,模型就成为了 1 阶上下文自适应模型。举例来说,要对一篇英文文本进行编码,已经编码了 10000 个英文字符,刚刚编码的字符是 t,下一个要编码的字符是 h。如果在前面的编码过程中已经统计出前 10000 个字符中出现了 113 次字母 t,其中有 47 个 t 后面跟着字母 h。得出字符 h 在字符 t 后的出现频率是 47/113,我们使用这一频率对字符 h 进行编码,需要 - =1.266位。
对比 0 阶自适应模型,如果前 10000 个字符中 h 的出现次数为 82 次,则字符 h 的概率是 82/10000,我们用此概率对 h 进行编码,需要 - = 6.930 位。考虑上下文因素的优势显而易见。我们还可以进一步扩大这一优势,例如要编码字符 h 的前两个字符是 gt,而在已经编码的文本中 gt 后面出现 h 的概率是 80%,那么只需要 0.322 位就可以编码输出字符h。此时,这种模型叫做 2 阶上下文自适应模型。
  最理想的情况是采用 3 阶自适应模型。此时,如果结合算术编码,对信息的压缩效果将达到惊人的程度。采用更高阶的模型需要消耗的系统空间和时间至少在目前还无法让人接受,使用算术压缩的应用程序大多数采用 2 阶或 3 阶的自适应模型。
2.4、算术编码的转义码
  使用自适应模型的算术编码算法必须考虑如何为从未出现过的上下文编码。例如,在 1 阶上下文模型中,需要统计出现概率的上下文可能有 256 * 256 = 65536 种,因为 0 - 255 的所有字符都有可能出现在 0 - 255 个字符中任何一个之后。当我们面对一个从未出现过的上下文时(比如刚编码过字符 b,要编码字符 d,而在此之前,d 从未出现在 b 的后面),该怎样确定字符的概率呢?
  比较简单的办法是在压缩开始之前,为所有可能的上下文分配计数为 1 的出现次数,如果在压缩中碰到从未出现的 bd 组合,我们认为 d 出现在 b 之后的次数为 1,并可由此得到概率进行正确的编码。使用这种方法的问题是,在压缩开始之前,在某上下文中的字符已经具有了一个比较小的频率。例如对 1 阶上下文模型,压缩前,任意字符的频率都被人为地设定为 1/65536,按照这个频率,压缩开始时每个字符要用 16 位编码,只有随着压缩的进行,出现较频繁的字符在频率分布图上占据了较大的空间后,压缩效果才会逐渐好起来。对于 2 阶或 3 阶上下文模型,情况就更糟糕,我们要为几乎从不出现的大多数上下文浪费大量的空间。
  我们通过引入“转义码”来解决这一问题。“转义码”是混在压缩数据流中的特殊的记号,用于通知解压缩程序下一个上下文在此之前从未出现过,需要使用低阶的上下文进行编码。举例来讲,在 3 阶上下文模型中,我们刚编码过 ght,下一个要编码的字符是 a,而在此之前,ght 后面从未出现过字符 a,这时,压缩程序输出转义码,然后检查 2 阶的上下文表,看在此之前 ht 后面出现 a 的次数;如果 ht 后面曾经出现过 a,那么就使用 2 阶上下文表中的概率为 a 编码,否则再输出转义码,检查 1 阶上下文表;如果仍未能查到,则输出转义码,转入最低的 0 阶上下文表,看以前是否出现过字符 a;如果以前根本没有出现过 a,那么我们转到一个特殊的“转义”上下文表,该表内包含 0 - 255 所有符号,每个符号的计数都为 1,并且永远不会被更新,任何在高阶上下文中没有出现的符号都可以退到这里按照 1/256 的频率进行编码。“转义码”的引入使我们摆脱了从未出现过的上下文的困扰,可以使模型根据输入数据的变化快速调整到最佳位置,并迅速减少对高概率符号编码所需要的位数。
2.5、算术编码中需要注意的几个问题
  由于实际的计算机的精度不可能无限长,运算中出现溢出是一个明显的问题,但多数机器都有16位、32位或者64位的精度,因此这个问题可使用比例缩放方法解决。算术编码器对整个消息只产生一个码字,这个码字是在间隔[0, 1)中的一个实数,因此译码器在接受到表示这个实数的所有位之前不能进行译码。
  ⑴算术编码也是一种对错误很敏感的编码方法,如果有一位发生错误就会导致整个消息译错。⑵算术编码可以是静态的或者自适应的。在静态算术编码中,信源符号的概率是固定的。在自适应算术编码中,信源符号的概率根据编码时符号出现的频繁程度动态地进行修改,在编码期间估算信源符号概率的过程叫做建模。需要开开发态算术编码的原因是因为事先知道精确的信源概率是很难的,而且是不切实际的。当压缩消息时,我们不能期待一个算术编码器获得最大的效率,所能做的最有效的方法是在编码过程中估算概率。算术编码的静态与自适应模型因此动态建模就成为确定编码器压缩效率的关键。算术编码非常依赖计算机的计算能力和存储能力。过去,这种依赖极大地限制了它的发展,在提出的最初几年,算术编码压缩算法只在极小的范围内有原型实现。随着计算机科学技术的发展,许多算术编码的实现在执行速度上已经能够被人们接受。如前面所说的那样,实现算术编码的核心问题在于如何获得正确的频率,以及如何高效地实现精确的乘法计算。
  在算术编码高阶上下文模型的实现中,对内存的需求量是一个十分棘手的问题。因为我们必须保持对已出现的上下文的计数,而高阶上下文模型中可能出现的上下文种类又是如此之多,数据结构的设计将直接影响到算法实现的成功与否。
  在 1 阶上下文模型中,使用数组来进行出现次数的统计是可行的,但对于 2 阶或 3 阶上下文模型,数组大小将依照指数规律增长,现有计算机的内存满足不了我们的要求。比较聪明的办法是采用树结构存储所有出现过的上下文。利用高阶上下文总是建立在低阶上下文的基础上这一规律,我们将 0 阶上下文表存储在数组中,每个数组元素包含了指向相应的 1 阶上下文表的指针,1 阶上下文表中又包含了指向 2 阶上下文表的指针……由此构成整个上下文树。树中只有出现过的上下文才拥有已分配的节点,没有出现过的上下文不必占用内存空间。在每个上下文表中,也无需保存所有 256 个字符的计数,只有在该上下文后面出现过的字符才拥有计数值。由此,我们可以最大限度地减少空间消耗。
第三章:简单实现
3.1、开发环境
  操作系统:Windows XP,Windows 2000
  应用软件:Visual C++6.0
3.2、Visual C++简介
  本次毕业设计主要研究的是算术编码,其具体的算法是通过Visual C++编程来实现的。
  在向对象的程序设计技术是当今全球程序员普遍采用的一种程序设计方法,是软件开发的最新潮流。在众多的开发工具中,Microsoft公司的Visual C++6.0独树一帜(1998年底,微软推出了其开发工具企业版套件Visual Studio6.0,Visual C++是其中之一),将面向对象的程序设计方法和可视化的软件开发环境完美地结合起来,合得开发Windows平台的应用程序更加方便,深入。Visual C++自诞生以来,一直是Windows环境下最主要的应用开发系统之一。Visual C++功能十分强大,支持面对对象编程技术,支持组件共享,不仅可以提高软件系统开发的速度,而且可以大大提高软件的质量。Visual C++不仅是C++语言的集成开发环境,而且与Win32紧密相连,所以,利用Visual C++开发系统可以完成各种各样的应用程序的开发,从底层软件直到上层直接面向用户的软件,而且, Visual C++强大的调试功能也为大型复杂软件的开发提空了有效的排错手段。Visual C++程序的执行速度以及对操作系统访问的权限之高,是其他许多语言无法比拟的,加上Windows操作系统的支持,就使得Visual C++的高级程序员对整个计算机的硬件系统和软件系统在各方面的访问和控制更加游刃有余。
  进入20世纪90年代以来,随着多媒体技术和图形图像技术的不断发展,可视化(Visual)技术得到广泛的重视,越来越多的计算机专业人员和非专业人员都开始研究并应用可视化技术。所谓可视化技术,一般是指软件开发阶段的可视化和对计算机图形技术和方法的应用,它是当前发展迅速并引人注目的技术之一。它的特点是把原来抽象的数字,表格,功能逻辑等用直观的图形,图像表现出来。可视化编程是它的重要应用之一。所谓可视化编程,就是指在软件开发过程中,用直观的具有一定含义的图标按钮,图形化的对象取代原来手工的抽象的编辑,运行,浏览操作,软件开发过程中表现为鼠标操作和拖放图形化的对象以及指定对象的属性,行为的过程。这种可视化编程方法易学易用,提高了工作效率。
  Visual C++是一个很好的可视化编程工具,使用Visual C++环境来开发基于Windows的应用程序大大缩短了开发时间,而且它的界面更友好,给程序员提供了一个完整方便的开发界面和许多的辅助开发工具,便于程序员操作。在没有可视化开发工具之前,程序员要花几个月的时间来完成Windows程序的界面开发,而现在只需较少的时间就可以完成。
  开发环境是程序员同Visual C++的交互界面,通过它程序员可以访问C++源代码编辑器,资源编辑器,使用内部调试器,还可以创建项目文件。
  Visual C++不但具有程序框架自动生成、灵活方便的类管理、代码编写和界面设计集成交互操作、可开发多种程序等优点,而且通过简单的设置就可使其生成的程序框架支持数据库接口、OLE2、Winsock网络、3D控制界面。由于Visual C++本身就是一个图形的开发界面,它提供了丰富的关于位图操作的函,对开发图像处理系统提供了极大的方便。
3.3、算术编码的实现
  因为算术编码的动态编码即自适应编码比较复杂,所以在此我做的只是实现一个简单的静态模型。部分代码:
  ⑴以下为编码过程:
  编码流程图如下:
  以下为部分代码:
void compress()
{
int i;
char c;
SYMBOL s;
FILE *compressed_file;
FILE*source_file;    //定义指针文件
source_file=fopen( 'source.txt', 'rb' ); //是以读的方式打开源文件
if ( source_file == NULL )
AfxMessageBox('Could not open source file', MB_OK);
compressed_file=fopen( 'test.cmp', 'wb' );
if ( compressed_file == NULL )
AfxMessageBox( 'Could not open output file' );
initialize_output_bitstream(); //初始化输入文件
initialize_arithmetic_encoder();
while(!feof(source_file))
{
fread(&c, 1, 1, source_file);
convert_int_to_symbol( c, &s );//查出此字符对应的概率区间
encode_symbol( compressed_file, &s );编码
if ( c == '\0' )
break;
}
flush_arithmetic_encoder( compressed_file );//处理编码结束时的有效概率
flush_output_bitstream( compressed_file );//输出码流到文件中
fclose( compressed_file);
fclose(source_file);
}
  下面为每次编码后,概率区间改变的处理过程:
range = (long) ( high-low ) + 1;
high = low + (unsigned short int )
(( range * s->high_count ) / s->scale - 1 );
low = low + (unsigned short int )
(( range * s->low_count ) / s->scale );
//重新为新的待编字符调节设定high,low
……
if ( ( high & 0x8000 ) == ( low & 0x8000 ) )//检查是否有可以输出的bit
{
output_bit( stream, high & 0x8000 );//输出最高位
while ( underflow_bits > 0 )//输出一些借位溢出的bit
{
output_bit( stream, ~high & 0x8000 );
underflow_bits--;
}
}
//把高位与1000(二进制码)比较,低位与1000比较,相同则输出,不同则返回
else if ( ( low & 0x4000 ) && !( high & 0x4000 ))//检查是否有可能溢出
{
underflow_bits += 1;
low &= 0x3fff;//消除可能溢出的第二位
high |= 0x4000;
}
else
return ;
low <<= 1;//低位补0
high <<= 1;
high |= 1;//高位补1
}
  上边说明的是:把低位与0100,高位与0100比较,相同则输出,向前移位,后补一位;不同则返回。
void flush_arithmetic_encoder( FILE *stream )
{
output_bit( stream, low & 0x4000 );
underflow_bits++;
while ( underflow_bits-- > 0 )
output_bit( stream, ~low & 0x4000 );
}//连续地进行编码,当输入bit流为0时,
  ⑵以下为译码过程:
  解码流程图如下:
  以下为部分代码:
short int get_current_count( SYMBOL *s )
{
long range;
short int count;
range = (long) ( high - low ) + 1;
count = (short int)
((((long) ( code - low ) + 1 ) * s->scale-1 ) / range );
return( count );
}
//找到当前位置count,根据码表,找到当前对应的字符
void initialize_arithmetic_decoder( FILE *stream )
{
int i;
code = 0;
for ( i = 0 ; i < 16 ; i++ )
{
code <<= 1;
code += input_bit( stream );
}
low = 0;
high = 0xffff;
}
range = (long)( high - low ) + 1;
high = low + (unsigned short int)
(( range * s->high_count ) / s->scale - 1 );
low = low + (unsigned short int)
(( range * s->low_count ) / s->scale );
//重新定位high low
if ( ( high & 0x8000 ) == ( low & 0x8000 ) )
//将高位与1000比较,如果相同则溢出
else if ((low & 0x4000) == 0x4000 && (high & 0x4000) == 0 )
{
code ^= 0x4000;
low &= 0x3fff;
high |= 0x4000;
}//比较第二位,将编码与0100相比较,相同则溢出
else
return;
low <<= 1;
high <<= 1;
high |= 1;
code <<= 1;
code += input_bit( stream );
}//
第四章:结果分析
运行界面如图:
  最下面为输入字符,中间为编译过程,最上面为译码结果,整个程序的运行过程是:输入一个文本文件,转为二进制码储存,然后利用算术编码的方法进行编码,最后再译码成字符格式。
第五章:总结
  各种媒体信息(特别是图像和动态视频)数据量非常之大,算术编码作为一种高效的数据编码方法在文本,图像,音频等压缩中有广泛的应用。所以,研究算术编码有非常好的前景与实用价值。本次毕业设计课题名称为“算术编码原理分析与实现”,围绕这个中心,本文主要介绍了算术编码的基本原理,应用,算法的实现等等。用Visual C++程序实现算术编码的整个过程。
  通过分析和事例可见,应用Visual C++程序对图像处理中的算术编码问题处理时,具有操作方便,处理速度快等特点。Visual C++程序的图像处理中经常用到的技术和方法在工具箱里都可以实现,所以,在对数字图像进行处理时,可以充分利用Visual C++程序的图像处理工具箱,使图像处理工作者可以从烦琐的编程工作中解脱出来。
  算术编码对整条信息(无论有多么长),其输出仅仅是一个小数,而且是一个介于0和1之间的二进制小数。例如算术编码对某条信息的输出为1010001111,那么它表示小数0.1010001111,也即是十进制小数0.64。
在以上程序中,码表不是按实际情况制作,而是等间隔定义的,熵最大,则压缩率比较小,没能表现出算术编码压缩率比较大的优点。
参考文献
[1]张远夏,浅谈算术编码的编码译码过程,玉林师范学院学报,2003,4。
[2]林福宗,多媒体技术基础[M],清华大学出版社,2000。
[3]陈明,多媒体技术基础[M],中央广播电视大学出版社,2000。
[4]杨义先,林须端. 编码密码学. 北京:人民邮电出版社,1992。
[5]何兵,陈健,MPEG22AAC缩放因子的算术编码方法,上海交通大学学报,2002,5。
[6]方世强,李远青,胡刚,文本压缩技术综述,工业工程,2002年第2期。
[7]Davidlove,向极限挑战:算术编码,http://blog.csdn.net/davidlove/,2003年7月11日
致 谢 辞

我的更多文章

下载客户端阅读体验更佳

APP专享