算术编码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=⌈log2​p(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=⌈log2​p(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语言)相关推荐

  1. java byte char io流_一文带你看懂JAVA IO流,史上最全面的IO教学

    原标题:一文带你看懂JAVA IO流,史上最全面的IO教学 一.IO流是什么 惯例引用百科的回答 流是一种抽象概念,它代表了数据的无结构化传递.按照流的方式进行输入输出,数据被当成无结构的字节序或字符 ...

  2. java io流详解_一文带你看懂JAVA IO流,史上最全面的IO教学啦

    一.IO流是什么 惯例引用百科的回答流是一种抽象概念,它代表了数据的无结构化传递.按照流的方式进行输入输出,数据被当成无结构的字节序或字符序列.从流中取得数据的操作称为提取操作,而向流中添加数据的操作 ...

  3. javaio流_一文带你看懂JAVA IO流,史上最全面的IO教学啦

    一.IO流是什么 惯例引用百科的回答 流是一种抽象概念,它代表了数据的无结构化传递.按照流的方式进行输入输出,数据被当成无结构的字节序或字符序列.从流中取得数据的操作称为提取操作,而向流中添加数据的操 ...

  4. 一文带你看懂JAVA IO流(一),史上最全面的IO教学啦(附送JAVA IO脑图)

    一.IO流是什么 惯例引用百科的回答 流是一种抽象概念,它代表了数据的无结构化传递.按照流的方式进行输入输出,数据被当成无结构的字节序或字符序列.从流中取得数据的操作称为提取操作,而向流中添加数据的操 ...

  5. 一文带你看懂JAVA IO流,史上最全面的IO教学啦(附送JAVA IO脑图

    一.IO流是什么 惯例引用百科的回答 流是一种抽象概念,它代表了数据的无结构化传递.按照流的方式进行输入输出,数据被当成无结构的字节序或字符序列.从流中取得数据的操作称为提取操作,而向流中添加数据的操 ...

  6. 机器学习(1)一文带你看懂显卡,显卡驱动,CUDA,CUDNN(内含举例)

    一文带你看懂显卡,显卡驱动,CUDA,CUDNN(内含举例) 显卡 显卡驱动 CUDA CUDNN 举一个生动形象的例子 显卡 显卡是硬件设备.(花钱购买的) (注:显卡不是GPU,显卡的核心组件包含 ...

  7. 一文带你看懂 MySQL 存储引擎

    本文目录: 1.MySQL体系结构 2.存储引擎介绍 3.MySQL 存储引擎特性 4.MySQL 有哪些存储引擎 5.了解 MySQL 数据存储方式 6.MySQL存储引擎介绍 6.1 CSV存储引 ...

  8. 一文带你看懂java 泛型,史上最全面的泛型教学啦。

    认真看这篇文章,保证你们对泛型又有新的理解,如果没有的话,请顺着网线来打我呀. 概述 引用下百度百科的回答 泛型是程序设计语言的一种特性.允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那 ...

  9. 一文带你看懂PaddleHub

    作者丨Charlotte77 来源丨我爱PaddlePaddle Hub 是什么?Hub 本意是中心,docker 有 docker Hub,大家可以把自己创建的镜像打包提交到 docker hub ...

最新文章

  1. Java 导出excel表 POI
  2. python + MySql 基本操作
  3. U3D 扩展方法 Dotween tolua
  4. 【推荐系统】KDD2021推荐系统论文集锦
  5. HDU 2544最短路dijkstra模板题
  6. php使用cookie获取浏览记录,php 使用COOKIE制作浏览记录_PHP教程
  7. 2012年CIO最关心的十大安全问题
  8. python 几何计算_计算几何-凸包算法 Python实现与Matlab动画演示
  9. 柳婼、知乎PAT经验分享汇总
  10. IP雷达4.0+网络检测
  11. 腾讯云短信申请与使用
  12. Samba 实现文件共享
  13. 数显之家快讯:【SHIO世硕心语】古有《陋室铭》,现有《群之铭》!
  14. flappy+bird+c语言程序,C语言版flappy_bird实现
  15. 单选按钮、字体的设置、沿着y轴旋转、面向用户的这一面不可见、三维效果、背景线性渐变、将背景剪切至文本
  16. Java基本语法笔记
  17. 阿里云IoT千里传音之声连网互动营销服务,首次亮相!
  18. 闯荡江湖的必备指南(2)
  19. 创建阿里云ecs实例Linux系统教程-Unirech阿里云代充
  20. 做直播能有多赚钱,Python告诉你

热门文章

  1. 计算机网络技术店面取名,适合电脑店的名字大全 霸气的电脑店铺起名
  2. 汽车电子MCU开发之路
  3. 聚力远谋,创赢未来 | 坤前全国巡展南京站圆满收官
  4. JWord Alpha 发布
  5. 国外拜访客户,实用小贴士
  6. 一图看懂 pytz 模块:现代以及历史版本的世界时区定义数据库,资料整理+笔记(大全)
  7. 请问OCP上面有没有证书编号?
  8. CH573 Peripheral 修改MTU
  9. 数据结构考研笔记(十五)——图的存储结构邻接矩阵、邻接表、十字链表、临界多重表的概念
  10. 前端js实现图片上传