算术编码Arithmetic Coding-高质量代码实现详解
关于算术编码的具体讲解我不多细说,本文按照下述三个部分构成。
- 两个例子分别说明怎么用算数编码进行编码以及解码(来源:ARITHMETIC CODING FOR DATA COIUPRESSION);
- 接下来我会给出算术编码的压缩效果接近熵编码的证明方法(这一部分参考惠普公司的论文:Introduction to Arithmetic Coding - Theory and Practice);
- 最后我会详细说明一下算数编码的实现代码(代码来源:ACM87 ARITHMETIC CODING FOR DATA COIUPRESSION);
一, 直观上去认识算术编码
编码过程:将字符映射到 [0,1) 的区间的一个数
稍微说明一下,一开始将区间分为好几段,每一段表示一个字符。编码字符e的时候,就把原先区间表示e的那一段放大,对这个区间进行划分获得子区间,每个子区间也是代表一个字符。依次进行下去。编码结束的时候获得的那个区间就是我们要的,我们可以在这中间取个数就好了。
伪代码是这样的:
解码过程:将编码得到的数还原成字符串。
大概思路是这样的,就是每次看那个数处落在哪个子区间段,然后输出这个区间段所表示的字符。之后,调整区间以及这个数,递归知道输出所有编码字符为止。
二,证明算术编码的压缩效率
首先我们得确切知道我们到底编码出来的是什么,然后我们才能去进一步去证明。
经过上一步的直观认识,我们应该知道编码结束的时候我们获得一个最终的区间,然后取这个区间中的一个值来表示最终的编码。在实践中,我们是输出子区间上下界中的共同位。比如我们最终得到的区间是[0.1010011,0.1010000)那么共同位就是0.10100,当然喽,方便起见,我们就只保存10100就好了,而把小数点什么的去掉。
接下来就是证明了。
三,实现代码详解
着重讲一下编码过程中字符编码的实现,先看一下代码。功能在于完成一个字符的编码工作
1: static void bit_plus_follow(int); /* Routine that follows */
2: static code_value low, high; /* Ends of the current code region */
3: static long bits_to_follow; /* Number of opposite bits to output after */
4:
5:
6: void encode_symbol(int symbol,int cum_freq[])
7: {
8: long range; /* Size of the current code region */
9: range = (long)(high-low)+1;
10:
11: high = low + (range*cum_freq[symbol-1])/cum_freq[0]-1; /* Narrow the code region to that allotted to this */
12: low = low + (range*cum_freq[symbol])/cum_freq[0]; /* symbol. */
13:
14: for (;;)
15: { /* Loop to output bits. */
16: if (high<Half) {
17: bit_plus_follow(0); /* Output 0 if in low half. */
18: }
19: else if (low>=Half) { /* Output 1 if in high half.*/
20: bit_plus_follow(1);
21: low -= Half;
22: high -= Half; /* Subtract offset to top. */
23: }
24: else if (low>=First_qtr && high<Third_qtr) { /* Output an opposite bit later if in middle half. */
25: bits_to_follow += 1;
26: low -= First_qtr; /* Subtract offset to middle*/
27: high -= First_qtr;
28: }
29: else break; /* Otherwise exit loop. */
30: low = 2*low;
31: high = 2*high+1; /* Scale up code range. */
32: }
33: }
34:
35: static void bit_plus_follow(int bit)
36: {
37: output_bit(bit); /* Output the bit. */
38: while (bits_to_follow>0) {
39: output_bit(!bit); /* Output bits_to_follow */
40: bits_to_follow -= 1; /* opposite bits. Set */
41: } /* bits_to_follow to zero. */
42: }
详细说明:
6-12行就是简单地计算,根据当前编码字符找到我们需要的子区间。前面讲到伪代码的时候编码到这一步的时候就已经完成对该字符的编码,即将对下一字符编码了。可是,实际操作的时候,我们看到这样一次次运行,区间会越来越小,也就意味着要存的那个数位数越来越多,那么我们的计算机能不能存下呢?这是个很严重的问题。
解决的方法是这样的,我们注意到,要是区间的上下界中前面几个字符是一样的,那么以后编码的时候它们还是一样不变的.举个例子,要是编码区间为[0.1101,0.1111),那么后来再怎么编码,得到的区间还是[0.11~,0.11~)前面几个字符是一样的。那么我们是不是可以进行输出了呢,这样就可以避免溢出啦!16-23行代码就是执行这个的。
细心的同学就发现了还有24-28行代码的存在,他们是干嘛的呢?
我们举个,就是说区间卡在0.5这个地方,区间为[0.10~,0.01~)那么这种情况怎么处理?因为显然要是始终这样下去的话,16-23行代码是无能为力的。对此我们也是可以处理的。
此时的区间上下界应该是类似这样,前面相同的部分我们就不看了,默认已经由16-23行代码处理完毕。
我们先看这个例子,假设区间是[0.011,0.101),那么画图来看的话区间就是处于[3/8,6/8)之间,我们将原先区间的[2/8,6/8)放大一倍,那么此时原先的子区间就变成了[2/8,1),可以参见下图。
我们注意到放大后,如果编码下一个字符的时候,子区间存在于上半部分,也就是上图右边[4/8,1)之间,那么也就是上图左边[4/8,6/8)的位置,这个部分的编码为10,所以输出10。
通过这个例子我们就知道怎么处理了。
首先记录一下从[2/8,6/8)放大到区间[0,1)的次数bits_to_follow ,直到区间长度大于0.5为止。
然后开始编码下一个字符,如果区间存在于上半部,则输出10000,其中0的个数为bits_to_follow 个。
如果区间存在于下半部,则输出01111,其中1的个数为bits_to_follow 个。如果区间位于[2/8,6/8)则继续放大,bits_to_follow 也随之增加。
建议大家自己画图好好体会一下这段代码的妙处!
现在给出全部代码:很多小细节有待自己去研究,很微妙的。
#include<cstdio>
#include<stdlib.h>
using namespace::std; #define Code_value_bits 16 /* Number of bits in a code value */
typedef long code_value; /* Type of an arithmetic code value */ #define Top_value (((long)1<<Code_value_bits)-1) /* Largest code value */ #define First_qtr (Top_value/4+1) /* Point after first quarter */
#define Half (2*First_qtr) /* Point after first half */
#define Third_qtr (3*First_qtr) /* Point after third quarter */ #define No_of_chars 256 /* Number of character symbols */
#define EOF_symbol (No_of_chars+1) /* Index of EOF symbol */ #define No_of_symbols (No_of_chars+1) /* Total number of symbols */ /* TRANSLATION TABLES BETWEEN CHARACTERS AND SYMBOL INDEXES. */ int char_to_index[No_of_chars]; /* To index from character */
unsigned char index_to_char[No_of_symbols+]; /* To character from index */ /* CUMULATIVE FREQUENCY TABLE. */ #define Max_frequency 16383 /* Maximum allowed frequency count */
/* 2^14 - 1 */
int cum_freq[No_of_symbols+]; /* Cumulative symbol frequencies */ //固定频率表,为了方便起见
int freq[No_of_symbols+] = {
,
, , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , , /* ! " # $ % & ' ( ) * + , - . / */
, , , , , , , , , , , , , , , , /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
, , , , , , , , , , , , , , , , /* @ A B C D E F G H I J K L M N O */
, , , , , , , , , , , , , , , , /* P Q R S T U V W X Y Z [ / ] ^ _ */
, , , , , , , , , , , , , , , , /* ' a b c d e f g h i j k l m n o */
, , , , , , , , , , , , , , , , /* p q r s t u v w x y z { | } ~ */
, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , , }; //用来存储编码值,是编码解码过程的桥梁。大小暂定100,实际中可以修改
char code[];
static int code_index=;
static int decode_index=; //buffer为八位缓冲区,暂时存放编码制
static int buffer;
//buffer中还有几个比特没有用到,初始值为8
static int bits_to_go;
//超过了EOF的字符,也是垃圾
static int garbage_bits; //启用字符频率统计模型,也就是计算各个字符的频率分布区间
void start_model(){
int i;
for (i = ; i<No_of_chars; i++) {
//为了便于查找
char_to_index[i] = i+;
index_to_char[i+] = i;
} //累计频率cum_freq[i-1]=freq[i]+...+freq[257], cum_freq[257]=0;
cum_freq[No_of_symbols] = ;
for (i = No_of_symbols; i>; i--) {
cum_freq[i-] = cum_freq[i] + freq[i];
}
//这条语句是为了确保频率和的上线,这是后话,这里就注释掉
//if (cum_freq[0] > Max_frequency); /* Check counts within limit*/
} //初始化缓冲区,便于开始接受编码值
void start_outputing_bits()
{
buffer = ; //缓冲区一开始为空
bits_to_go = ;
} void output_bit(int bit)
{
//为了写代码方便,编码数据是从右到左进入缓冲区的。记住这一点!
buffer >>= ;
if (bit) buffer |= 0x80;
bits_to_go -= ;
//当缓冲区满了的时候,就输出存起来
if (bits_to_go==) {
code[code_index]=buffer;
code_index++; bits_to_go = ; //重新恢复为8
}
} void done_outputing_bits()
{
//编码最后的时候,当缓冲区没有满,则直接补充0
code[code_index]=buffer>>bits_to_go;
code_index++;
} static void bit_plus_follow(int); /* Routine that follows */
static code_value low, high; /* Ends of the current code region */
static long bits_to_follow; /* Number of opposite bits to output after */ void start_encoding()
{
for(int i=;i<;i++)code[i]='\0'; low = ; /* Full code range. */
high = Top_value;
bits_to_follow = ; /* No bits to follow */
} void encode_symbol(int symbol,int cum_freq[])
{
long range; /* Size of the current code region */
range = (long)(high-low)+; high = low + (range*cum_freq[symbol-])/cum_freq[]-; /* Narrow the code region to that allotted to this */
low = low + (range*cum_freq[symbol])/cum_freq[]; /* symbol. */ for (;;)
{ /* Loop to output bits. */
if (high<Half) {
bit_plus_follow(); /* Output 0 if in low half. */
}
else if (low>=Half) { /* Output 1 if in high half.*/
bit_plus_follow();
low -= Half;
high -= Half; /* Subtract offset to top. */
}
else if (low>=First_qtr && high<Third_qtr) { /* Output an opposite bit later if in middle half. */
bits_to_follow += ;
low -= First_qtr; /* Subtract offset to middle*/
high -= First_qtr;
}
else break; /* Otherwise exit loop. */
low = *low;
high = *high+; /* Scale up code range. */
}
} /* FINISH ENCODING THE STREAM. */ void done_encoding()
{
bits_to_follow += ; /* Output two bits that */
if (low<First_qtr) bit_plus_follow(); /* select the quarter that */
else bit_plus_follow(); /* the current code range */
} /* contains. */ static void bit_plus_follow(int bit)
{
output_bit(bit); /* Output the bit. */
while (bits_to_follow>) {
output_bit(!bit); /* Output bits_to_follow */
bits_to_follow -= ; /* opposite bits. Set */
} /* bits_to_follow to zero. */
} void encode(){
start_model(); /* Set up other modules. */
start_outputing_bits();
start_encoding();
for (;;) { /* Loop through characters. */
int ch;
int symbol;
ch = getchar(); /* Read the next character. */
//if (ch==EOF) break; /* Exit loop on end-of-file. */
//为了简单起见,这里就不用EOF为结尾了,直接使用回车符作为结尾。这不影响说明算法的原理
if(ch==)break;
symbol = char_to_index[ch]; /* Translate to an index. */
encode_symbol(symbol,cum_freq); /* Encode that symbol. */ }
//将EOF编码进去,作为终止符
encode_symbol(EOF_symbol,cum_freq);
done_encoding(); /* Send the last few bits. */
done_outputing_bits(); } //解码 static code_value value; /* Currently-seen code value */ void start_inputing_bits()
{
bits_to_go = ; /* Buffer starts out with */
garbage_bits = ; /* no bits in it. */
} int input_bit()
{
int t; if (bits_to_go==) {
buffer = code[decode_index];
decode_index++; // if (buffer==EOF) {
if(decode_index > code_index ){
garbage_bits += ; /* Return arbitrary bits*/
if (garbage_bits>Code_value_bits-) { /* after eof, but check */
fprintf(stderr,"Bad input file/n"); /* for too many such. */
// exit(-1);
}
}
bits_to_go = ;
}
//从左到右取出二进制位,因为存的时候是从右到左
t = buffer&; /* Return the next bit from */
buffer >>= ; /* the bottom of the byte. */
bits_to_go -= ;
return t;
} void start_decoding()
{
int i;
value = ; /* Input bits to fill the */
for (i = ; i<=Code_value_bits; i++) { /* code value. */
value = *value+input_bit();
} low = ; /* Full code range. */
high = Top_value;
} int decode_symbol(int cum_freq[])
{
long range; /* Size of current code region */
int cum; /* Cumulative frequency calculated */
int symbol; /* Symbol decoded */
range = (long)(high-low)+;
cum = (((long)(value-low)+)*cum_freq[]-)/range; /* Find cum freq for value. */ for (symbol = ; cum_freq[symbol]>cum; symbol++) ; /* Then find symbol. */
high = low + (range*cum_freq[symbol-])/cum_freq[]-; /* Narrow the code region *//* to that allotted to this */
low = low + (range*cum_freq[symbol])/cum_freq[]; for (;;) { /* Loop to get rid of bits. */
if (high<Half) {
/* nothing */ /* Expand low half. */
}
else if (low>=Half) { /* Expand high half. */
value -= Half;
low -= Half; /* Subtract offset to top. */
high -= Half;
}
else if (low>=First_qtr && high <Third_qtr) {
value -= First_qtr;
low -= First_qtr; /* Subtract offset to middle*/
high -= First_qtr;
}
else break; /* Otherwise exit loop. */
low = *low;
high = *high+; /* Scale up code range. */
value = *value+input_bit(); /* Move in next input blt. */
}
return symbol;
} void decode(){
start_model(); /* Set up other modules. */
start_inputing_bits();
start_decoding();
for (;;) { /* Loop through characters. */
int ch; int symbol;
symbol = decode_symbol(cum_freq); /* Decode next symbol. */
if (symbol==EOF_symbol) break; /* Exit loop if EOF symbol. */
ch = index_to_char[symbol]; /* Translate to a character.*/
putc(ch,stdout); /* Write that character. */
}
} int main()
{
encode();
decode();
system("pause");
return ;
}
算术编码Arithmetic Coding-高质量代码实现详解的更多相关文章
- KMP高质量代码实现详解
KMP算法 对于KMP算法我分为两个部分说明,第一部分是算法部分,介绍KMP算法的算法思想:第二部分是实现部分,介绍一种厉害的实现代码以及代码注释.当然了由于本文主要介绍怎么实现故而先分析实现,对KM ...
- 编写高质量代码--改善python程序的建议(八)
原文发表在我的博客主页,转载请注明出处! 建议四十一:一般情况下使用ElementTree解析XML python中解析XML文件最广为人知的两个模块是xml.dom.minidom和xml.sax, ...
- 编写高质量代码--改善python程序的建议(三)
原文发表在我的博客主页,转载请注明出处! 建议十三:警惕eval()的安全漏洞 相信经常处理文本数据的同学对eval()一定是欲罢不能,他的使用非常简单: eval("1+1==2" ...
- 编写高质量代码改善C#程序的157个建议——导航开篇
前言 由于最近工作重心的转移,原来和几个同事一起开发的项目也已经上线了,而新项目就是在现有的项目基础上进行优化延伸扩展.打个比方,现在已经上线的项目行政案件的Web管理网站(代码还没那么多相比较即将要 ...
- 编写高质量代码:改善Java程序的151个建议(第一章:JAVA开发中通用的方法和准则)
编写高质量代码:改善Java程序的151个建议(第一章:JAVA开发中通用的方法和准则) 目录 建议1: 不要在常量和变量中出现易混淆的字母 建议2: 莫让常量蜕变成变量 建议3: 三元操作符的类型务 ...
- 编写高质量代码改善C#程序的157个建议:第17个建议之多数情况下使用foreach进行循环遍历
今天是我看<编写高质量代码:改善C#程序的157个建议>第二遍的时候了,看完这本书的确是受益匪浅,学到了很多东西,也明白了很多道理. 里面的代码我每个都调试了一遍,有时候是有些出入的,可能 ...
- C# 《编写高质量代码改善建议》整理&笔记 --(一)基本语言篇
题记:这是自己的观后感,工作两年了,本来打算好好学习设计模式,或者作为客户端深入了解GPU编程的,但是突然发现还有这么一本书. <编写高质量代码改善建议>,感觉这正是自己需要的. 我是做 ...
- 编写高质量代码改善java程序的151个建议——[1-3]基础?亦是基础
原创地址: http://www.cnblogs.com/Alandre/ (泥沙砖瓦浆木匠),需要转载的,保留下! Thanks The reasonable man adapts himse ...
- 编写高质量代码:改善Java程序的151个建议 --[117~128]
编写高质量代码:改善Java程序的151个建议 --[117~128] Thread 不推荐覆写start方法 先看下Thread源码: public synchronized void start( ...
随机推荐
- 网站网页生成.shtml访问无法显示
网站换了服务器后发现shtml网页无法访问,原因是没有注册.shtml扩展名,解决方法如下 IIS6.0解析shtm,shtml文件由于IIS6.0的安全性较以前有特别大的改进,所以在很多功能默认情况 ...
- Android 解决图片大量下载:软引用必须懂4点
1.对象的强.软.弱和虚引用为了能更加灵活控制对象的生命周期,需要知道对象引用的4中级别,由高到低依次为 :强引用.软引用.弱引用和虚引用 备注: 这四种的区别: ⑴强引用(StrongReferen ...
- [转]Squid中的日志出现TCP_CLIENT_REFRESH_MISS的问题排除
转自:http://www.php-oa.com/2008/07/15/tcp_client_refresh_miss.html 今天检查Squid发现大量的日志出现TCP_CLIENT_REFRES ...
- (转)前端构建工具gulp入门教程
前端构建工具gulp入门教程 老婆婆 1.8k 2013年12月30日 发布 推荐 10 推荐 收藏 83 收藏,20k 浏览 本文假设你之前没有用过任何任务脚本(task runner)和命令行工具 ...
- shell 编程基础
1 创建shell脚本文件 要创建一个shell脚本文件,必须在第一行指定要使用的shell,其格式为: #! /bin/bash 接着加上该shell文件的注释,说明该脚本文件用来干什么,有谁创建, ...
- GNU make 总结 (五)
一.使用make更新静态库 静态库文件是一些.o文件的集合,在Linux中使用ar工具对它进行维护管理.一个静态库通常由多个.o文件组成,这些.o文件可独立的被作为一个规则的目标,库成员作为目标时需要 ...
- Linux系统木马后门查杀方法详解
木马和后门的查杀是系统管理员一项长期需要坚持的工作,切不可掉以轻心.以下从几个方面在说明Linux系统环境安排配置防范和木马后门查杀的方法: 一.Web Server(以Nginx为例) 1.为防止跨 ...
- hmmer 使用(转载)
hmmer 使用 » 转载文章请注明,转载自:博耘生物 » <hmmer的安装与使用> » 原文链接:http://boyun.sh.cn/bio/?p=1753 从功能基因研究的角度 ...
- Modelsim的demo入门教程
写在前面的话学过MCU设计的朋友都知道,系统调试是多么的重要.而对于FPGA设计来说,仿真确实最重要的.一个完整的项目,必须有完整的仿真平台.有朋友说,按键仿真模型没法搞. 我只能说,你并不了解硬件及 ...
- Hibernate从入门到精通(十一)多对多双向关联映射
上次我们在中Hibernate从入门到精通(十)多对多单向关联映射讲解了一下多对多单向关联映射,这次我们讲解一下七种映射中的最后一种多对多双向关联映射. 多对多双向关联映射 按照我们之前的惯例,先看一 ...