一文带你看懂算术编码(C语言)
算术编码C语言
简介
算术编码是图像压缩的主要算法之一。 是一种无损数据压缩方法,也是一种熵编码的方法。和其它熵编码方法不同的地方在于,其他的熵编码方法通常是把输入的消息分割为符号,然后对每个符号进行编码,而算术编码是直接把整个输入的消息编码为一个数,一个满足(0.0 ≤ n < 1.0)的小数n(百度百科)(你伤害辽我,还一笑而过~)
原理
算术编码的基本原理是:根据信源的不同符号序列的概率,把[0,1]区间划分为互不重叠的子区间,子区间的宽度恰好是各符号序列的概率。这样信源发出的不同符号序列将与各子区间一一对应,每个子区间内的任意一个实数都可以用来表示对应的符号序列,这个二进制小数就是该符号序列所对应的码字,截取其长度与该序列的概率匹配,实现高效编码。
接下来我们看具体的步骤:
编码
1.假设对一段二进制a1,a2符号序列信源进行编码,先统计出a1,a2出现的概率。
2.计算出该序列的概率p=p(x1)…p(x2),然后根据下面的公式计算出截止长度L=⌈log21p(S)⌉L=\left \lceil log_{2}\frac{1}{p(S)} \right \rceilL=⌈log2p(S)1⌉其中方框表示向上取整。
3.从左到右根据信源符号来截取对应的区间,如第一个符号为a1,则截取0到1区间的a1段,第二个符号为a2,则在上一步的基础上截取其中的a2段,即在0到a1区间取a2段,以此类推直到序列的最后一个符号。
4.在3计算出的序列区间内任取一个数作为码字,截取其二进制的小数点后前L位,即得信源序列的算数编码。
解码
根据算术编码的原理我们可以倒推出解码得步骤:
1.将编码转回十进制小数(主要由于编码时截取的操作使得有截止误差的存在,所以这里转回十进制最好加上一个0.5*2^(-L+1)的补偿量)
2.判断该小数落在哪个区间,如其落在第一个大区间内则可判断源信源序列的第一个符号为a1,又落在第一个大区间内的第二个小区间,则信源序列的第二个符号为a2,以此逐渐细化区间得到全部信源序列信源。
这里结合一个例题进行详细步骤说明:
例:设二进制无记忆信源S={0,1},其p(0)=1/4,p(1)=3/4。对二元序列11110做算术编码解码。
解:
p(S)=p(1)6∗p(0)2=(34)4∗14p(S)=p(1)^{6}*p(0)^{2}=(\frac{3}{4})^{4}*\frac{1}{4}p(S)=p(1)6∗p(0)2=(43)4∗41
L=⌈log21p(S)⌉=4L=\left \lceil log_{2}\frac{1}{p(S)} \right \rceil=4L=⌈log2p(S)1⌉=4
因为x1=1,所以首先落在[14\frac{1}{4}41,1]区间,如图所示:
x2=1,所以落在low=14+(1−14)∗14=716low=\frac{1}{4}+(1-\frac{1}{4})*\frac{1}{4}=\frac{7}{16}low=41+(1−41)∗41=167,即[716\frac{7}{16}167,1]区间,如图所示。
x3=1,所以落在low=716+(1−716)∗14=3764low=\frac{7}{16}+(1-\frac{7}{16})*\frac{1}{4}=\frac{37}{64}low=167+(1−167)∗41=6437,即[3764\frac{37}{64}6437,1]区间。
x4=1,所以落在low=3764+(1−3764)∗14=175256low=\frac{37}{64}+(1-\frac{37}{64})*\frac{1}{4}=\frac{175}{256}low=6437+(1−6437)∗41=256175,即[175256\frac{175}{256}256175,1]区间。
x5=0,所以落在high=175256+(1−175256)∗14=7811024high=\frac{175}{256}+(1-\frac{175}{256})*\frac{1}{4}=\frac{781}{1024}high=256175+(1−256175)∗41=1024781,即[175256\frac{175}{256}256175,7811024\frac{781}{1024}1024781]区间。
0.5∗(175256+7811024)0.5*(\frac{175}{256}+\frac{781}{1024})0.5∗(256175+1024781)=0.7231445=0.10111…
取小数点后前4位得算术编码结果为1011。
解码:
p(S)=1∗2−1+0∗2−2+1∗2−3+1∗2−4+0.5∗2−5=0.701325p(S)=1*2^{-1}+0*2^{-2}+1*2^{-3}+1*2^{-4}+0.5*2^{-5}=0.701325p(S)=1∗2−1+0∗2−2+1∗2−3+1∗2−4+0.5∗2−5=0.701325(最后一项为了补偿截止误差)
因为p(S)>1/4=0.25,
即在[0.25,1],所以第一个符号为1,此时low=0.25,high=1.
又p(S)>low+(high-low)*0.25=0.4375,
即在[0.4375,1]区间,所以第二个符号为1,low=0.4375,high=1.
又p(S)>low+(high-low)*0.25=0.578125,
即在[0.578125,1]区间,所以第三个符号为1,low=0.578125,high=1.
又p(S)>low+(high-low)*0.25=0.68359375,
即在[0.68359375,1]区间,所以第四个符号为1,low=0.68359375,high=1.
又p(S)<low+(high-low)*0.25=0.7627…,
即在[0.68359375,0.7627]区间,所以第五个符号为0.
解码完成,得解码为11110,与源码一致。
从上面的过程中我们可以总结出一个公式,即low+(high-low)*p(0),上一轮的low,high中的一个被该值取代而与另外一个构成新的区间。
C语言实现代码
代码编程思路与面的例题思路基本相同,这里不再过多赘述。在同目录下新建source.txt,compress.txt和decode.txt,即可直接运行。
注意:1.由于C语言的变量容量限制,因此当信源长度过长时需要对其进行分段编码
2.由于C语言中double变量精度的限制,所以分段编码的长度不能过长,太长了后面的码就会变成错码,亲测每段<55为宜。
# include <stdio.h>
# include <stdlib.h>
# include <time.h>
# include <math.h>
clock_t start,stop;
double duration;void codes(float p,int L,int n)
{int i,j,len,N,temp; double ps,PS,high,low,m;char source[L/n+1]; //source用来存放伯努利源 char code[100]; //code用来存放编码 start = clock();FILE *sc;FILE *file;file = fopen("compress.txt","w"); sc = fopen("source.txt","r"); if(file==NULL|sc==NULL){printf("打开文件错误\n");exit(0);}for(i=0;i<n;i++){//算出截止长度N fgets(source,100,sc);// printf("source:%s\n",source); ps=1;for(j=0;j<L/n;j++){if(source[j]=='0')ps = ps*p;elseps = ps*(1-p);}PS = log(1.0/ps)/log(2);N = (int)PS;if(PS-N>1e-6)N = N+1;// printf("N的值:%d\n",N);//算出区间的两端端点值 low=0;high=1;for(j=0;j<L/n;j++){if(source[j]=='0')high=(high-low)*p+low;elselow = (high-low)*p+low; }m = (low+high)*0.5;//printf("\nm的值为 %lf",m);//将m转为二进制取前N位 len=0;while(m){temp = (int)(m*2);if(temp)code[len]='1';elsecode[len]='0';fputc(code[len],file);len=len+1;m = 2*m-temp; if(len==N) break; }fputc(10,file);/*printf("\n编码结果为:");for(j=0;j<len[i];j++){printf("%c",code[i][j]);}printf("\n");*/}fclose(sc);fclose(file);stop = clock();duration= (double)(stop-start)/CLK_TCK;printf("\n编码共计耗时:%lf\n",duration);}void decodes(float p,int L,int n)
{int i,j,f,len,sum;double P,low,high;char code[100]; //code用来存放编码 char decode[L/n+1]; //decode用来存放解码 start = clock();FILE *compress;FILE *file;compress = fopen("compress.txt","r"); file = fopen("decode.txt","w"); if(file==NULL|compress==NULL){printf("打开文件错误\n");exit(0);}for(i=0;i<n;i++){fgets(code,100,compress); //获取第一行数据 for(f=0;f<100;f++){if(code[f]==10) //获取第一行长度 10为换行符 asc码break;}len=f; //printf("len:%d\n",len);//将编码转回十进制小数 P=0;for(j=0;j<len;j++){P = P+(code[j]-48)*pow(2,-(j+1)); } P = P+pow(2,-(len+1))*0.5;// printf(" P = %lf\n",P);//判断转出的十进制小数在哪个区间进行解码并写入decode文件 low=0;high=1;for(j=0;j<L/n;j++){if((P>low)&(P<((high-low)*p+low))){decode[j]='0';high = (high-low)*p+low;}else{decode[j]='1';low = (high-low)*p+low;}fputc(decode[j],file); }fputc(10,file);/*printf("解码结果为:");for(j=0;j<L/n;j++){printf("%c",decode[i][j]); }printf("\n");
*/sum = sum+len; } fclose(file);fclose(compress);stop = clock();duration= (double)(stop-start)/CLK_TCK;printf("\n解码共计耗时:%lf\n",duration);printf("\n压缩率为:%f \n",sum/(L*1.0));
}int main()
{float p=0; int i,j,k,L=0,n=1;while(1){printf("输入概率p:");scanf("%f",&p);printf("输入长度L:");scanf("%d",&L); printf("输入分组数n:");scanf("%d",&n);if (L%n>1e-6|p>=1|p<=0){printf("输入错误,要求0<p<1,L为n的整数倍,请重新输入\n");continue; }elsebreak;}char source[L/n+1]; //source用来存放伯努利源 FILE *file;file = fopen("source.txt","w"); if(file==NULL){printf("打开文件错误\n");exit(0);}//生成伯努利源 for(i=0;i<n;i++){for(j=0;j<L/n;j++){k = rand()%100;if(k>100*p-1){source[j]='1';}else{source[j]='0'; }fputc(source[j],file); }fputc(10,file);}fclose(file);//编码 codes(p,L,n); //解码decodes(p,L,n);return 0;
}
参考资料:信息论与编码(曹雪虹著)
第一次认认真真写博客,有很多不足欢迎评论指正,另外还请多多点赞支持啊啊啊!
一文带你看懂算术编码(C语言)相关推荐
- java byte char io流_一文带你看懂JAVA IO流,史上最全面的IO教学
原标题:一文带你看懂JAVA IO流,史上最全面的IO教学 一.IO流是什么 惯例引用百科的回答 流是一种抽象概念,它代表了数据的无结构化传递.按照流的方式进行输入输出,数据被当成无结构的字节序或字符 ...
- java io流详解_一文带你看懂JAVA IO流,史上最全面的IO教学啦
一.IO流是什么 惯例引用百科的回答流是一种抽象概念,它代表了数据的无结构化传递.按照流的方式进行输入输出,数据被当成无结构的字节序或字符序列.从流中取得数据的操作称为提取操作,而向流中添加数据的操作 ...
- javaio流_一文带你看懂JAVA IO流,史上最全面的IO教学啦
一.IO流是什么 惯例引用百科的回答 流是一种抽象概念,它代表了数据的无结构化传递.按照流的方式进行输入输出,数据被当成无结构的字节序或字符序列.从流中取得数据的操作称为提取操作,而向流中添加数据的操 ...
- 一文带你看懂JAVA IO流(一),史上最全面的IO教学啦(附送JAVA IO脑图)
一.IO流是什么 惯例引用百科的回答 流是一种抽象概念,它代表了数据的无结构化传递.按照流的方式进行输入输出,数据被当成无结构的字节序或字符序列.从流中取得数据的操作称为提取操作,而向流中添加数据的操 ...
- 一文带你看懂JAVA IO流,史上最全面的IO教学啦(附送JAVA IO脑图
一.IO流是什么 惯例引用百科的回答 流是一种抽象概念,它代表了数据的无结构化传递.按照流的方式进行输入输出,数据被当成无结构的字节序或字符序列.从流中取得数据的操作称为提取操作,而向流中添加数据的操 ...
- 机器学习(1)一文带你看懂显卡,显卡驱动,CUDA,CUDNN(内含举例)
一文带你看懂显卡,显卡驱动,CUDA,CUDNN(内含举例) 显卡 显卡驱动 CUDA CUDNN 举一个生动形象的例子 显卡 显卡是硬件设备.(花钱购买的) (注:显卡不是GPU,显卡的核心组件包含 ...
- 一文带你看懂 MySQL 存储引擎
本文目录: 1.MySQL体系结构 2.存储引擎介绍 3.MySQL 存储引擎特性 4.MySQL 有哪些存储引擎 5.了解 MySQL 数据存储方式 6.MySQL存储引擎介绍 6.1 CSV存储引 ...
- 一文带你看懂java 泛型,史上最全面的泛型教学啦。
认真看这篇文章,保证你们对泛型又有新的理解,如果没有的话,请顺着网线来打我呀. 概述 引用下百度百科的回答 泛型是程序设计语言的一种特性.允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那 ...
- 一文带你看懂PaddleHub
作者丨Charlotte77 来源丨我爱PaddlePaddle Hub 是什么?Hub 本意是中心,docker 有 docker Hub,大家可以把自己创建的镜像打包提交到 docker hub ...
最新文章
- Java 导出excel表 POI
- python + MySql 基本操作
- U3D 扩展方法 Dotween tolua
- 【推荐系统】KDD2021推荐系统论文集锦
- HDU 2544最短路dijkstra模板题
- php使用cookie获取浏览记录,php 使用COOKIE制作浏览记录_PHP教程
- 2012年CIO最关心的十大安全问题
- python 几何计算_计算几何-凸包算法 Python实现与Matlab动画演示
- 柳婼、知乎PAT经验分享汇总
- IP雷达4.0+网络检测
- 腾讯云短信申请与使用
- Samba 实现文件共享
- 数显之家快讯:【SHIO世硕心语】古有《陋室铭》,现有《群之铭》!
- flappy+bird+c语言程序,C语言版flappy_bird实现
- 单选按钮、字体的设置、沿着y轴旋转、面向用户的这一面不可见、三维效果、背景线性渐变、将背景剪切至文本
- Java基本语法笔记
- 阿里云IoT千里传音之声连网互动营销服务,首次亮相!
- 闯荡江湖的必备指南(2)
- 创建阿里云ecs实例Linux系统教程-Unirech阿里云代充
- 做直播能有多赚钱,Python告诉你