一文带你看懂算术编码(C语言)_信息论算术编码c语言实现-程序员宅基地

技术标签: 编码学  算法  c语言  

算术编码C语言

简介

算术编码是图像压缩的主要算法之一。 是一种无损数据压缩方法,也是一种熵编码的方法。和其它熵编码方法不同的地方在于,其他的熵编码方法通常是把输入的消息分割为符号,然后对每个符号进行编码,而算术编码是直接把整个输入的消息编码为一个数,一个满足(0.0 ≤ n < 1.0)的小数n(百度百科)(你伤害辽我,还一笑而过~)

原理

算术编码的基本原理是:根据信源的不同符号序列的概率,把[0,1]区间划分为互不重叠的子区间,子区间的宽度恰好是各符号序列的概率。这样信源发出的不同符号序列将与各子区间一一对应,每个子区间内的任意一个实数都可以用来表示对应的符号序列,这个二进制小数就是该符号序列所对应的码字,截取其长度与该序列的概率匹配,实现高效编码。
接下来我们看具体的步骤:
编码
1.假设对一段二进制a1,a2符号序列信源进行编码,先统计出a1,a2出现的概率。
2.计算出该序列的概率p=p(x1)…p(x2),然后根据下面的公式计算出截止长度 L = ⌈ l o g 2 1 p ( S ) ⌉ L=\left \lceil log_{2}\frac{1}{p(S)} \right \rceil L=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 = ( 3 4 ) 4 ∗ 1 4 p(S)=p(1)^{6}*p(0)^{2}=(\frac{3}{4})^{4}*\frac{1}{4} p(S)=p(1)6p(0)2=(43)441
L = ⌈ l o g 2 1 p ( S ) ⌉ = 4 L=\left \lceil log_{2}\frac{1}{p(S)} \right \rceil=4 L=log2p(S)1=4

因为x1=1,所以首先落在[ 1 4 \frac{1}{4} 41,1]区间,如图所示:
在这里插入图片描述
x2=1,所以落在 l o w = 1 4 + ( 1 − 1 4 ) ∗ 1 4 = 7 16 low=\frac{1}{4}+(1-\frac{1}{4})*\frac{1}{4}=\frac{7}{16} low=41+(141)41=167,即[ 7 16 \frac{7}{16} 167,1]区间,如图所示。在这里插入图片描述
x3=1,所以落在 l o w = 7 16 + ( 1 − 7 16 ) ∗ 1 4 = 37 64 low=\frac{7}{16}+(1-\frac{7}{16})*\frac{1}{4}=\frac{37}{64} low=167+(1167)41=6437,即[ 37 64 \frac{37}{64} 6437,1]区间。

x4=1,所以落在 l o w = 37 64 + ( 1 − 37 64 ) ∗ 1 4 = 175 256 low=\frac{37}{64}+(1-\frac{37}{64})*\frac{1}{4}=\frac{175}{256} low=6437+(16437)41=256175,即[ 175 256 \frac{175}{256} 256175,1]区间。

x5=0,所以落在 h i g h = 175 256 + ( 1 − 175 256 ) ∗ 1 4 = 781 1024 high=\frac{175}{256}+(1-\frac{175}{256})*\frac{1}{4}=\frac{781}{1024} high=256175+(1256175)41=1024781,即[ 175 256 \frac{175}{256} 256175, 781 1024 \frac{781}{1024} 1024781]区间。
0.5 ∗ ( 175 256 + 781 1024 ) 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.701325 p(S)=1*2^{-1}+0*2^{-2}+1*2^{-3}+1*2^{-4}+0.5*2^{-5}=0.701325 p(S)=121+022+123+124+0.525=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;
			else
				ps = 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;
			else
			low = (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';
			else
				code[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;				
			}
		else
	 		break;
	}

	
       
	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;
}

参考资料:信息论与编码(曹雪虹著)

第一次认认真真写博客,有很多不足欢迎评论指正,另外还请多多点赞支持啊啊啊!

在这里插入图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_43219402/article/details/117371680

智能推荐

java编程基础总结——29.多线程编程_public integer call-程序员宅基地

文章浏览阅读100次。多线程编程基础java自身提供了创建多线程的方案:继承Thread 、实现Runable接口、实现Callable接口线程对象的一些常见方法线程对象的一些常见方法:_public integer call

Logstash 对接 Kafka,在写入ES的时候,报错:Will Retry with exponential backoff {:code=>400_encountered a retryable error. will retry with exp-程序员宅基地

文章浏览阅读1.7k次。[2022-05-12T15:09:13,065][ERROR][logstash.outputs.elasticsearch][unreasonable_use_kafka][d2128c0736a801fa462a2aea862c6bbf3923c3cce59e00fc70fa6e234d9dac33] Encountered a retryable error. Will Retry with exponential backoff {:code=>400, :url=>"http://_encountered a retryable error. will retry with exponential backoff {:code=>4

iOS中左右滑动切换,滑动标签页导航的设计思路_ios 多标签左右切换内容-程序员宅基地

文章浏览阅读1.7w次,点赞3次,收藏11次。iOS中左右滑动切换,滑动标签页导航的设计思路iOS开发中经常(几乎每个APP都含有这样的页面吧,几乎!UI设计师也都是这样抄来抄去…..)demo见Github:https://github.com/zhengwenming/SliderTab估计很多人都会说,直接用第三方就可以了,很多人封装过,很好用。而且这样的页面用第三方2分钟搞定,省时省力。 笔者也曾用过第三方,但是屡屡出_ios 多标签左右切换内容

基于微信小程序的点餐系统设计与实现(lw+数据库+讲解等)_微信点餐小程序数据库设计-程序员宅基地

文章浏览阅读947次,点赞28次,收藏15次。基于springboot+uniapp的点餐系统设计与实现_微信点餐小程序数据库设计

python实现yolo目标检测_Yolov5—实现目标检测(win10)-程序员宅基地

文章浏览阅读9.6k次,点赞5次,收藏38次。Yolov5—实现目标检测(win10)该方法可以在win10上实现Yolov5的目标检测,配置前需要安装Anaconda3一、环境配置源码下载地址:https://github.com/ultralytics/yolov5.git推荐使用B站up主修改好的文件配置Yolov5环境。(链接点这里:提取码为“ugpg”)Pytorch:1.5.1Cuda:10.1Python:3.7打开Anacon..._yolov5检测api

mktemp linux,Linux mktemp 命令使用方法-程序员宅基地

文章浏览阅读213次。原标题:Linux mktemp 命令使用方法Linux mktemp命令用于建立暂存文件。mktemp建立的一个暂存文件,供shell 使用。创建临时文件或者目录,这样的创建方式是安全的。此命令的适用范围:RedHat、RHEL、Ubuntu、CentOS、SUSE、openSUSE、Fedora。语法mktemp [-qu][文件名参数]参数:-q  执行时若发生错误,不会显示任何信息。-u ..._xbfi

随便推点

NestedScrollView嵌套RecyclerView导致的初始化缓慢_nestedscrollview 嵌套recyclerview 慢-程序员宅基地

文章浏览阅读1.1k次。为解决recyclerView的滑动冲突导致的NestedScrollView嵌套RecyclerView,如果不给RecyclerView固定高度,关闭自动计算高度的话。默认是展示所有item,也就是说recyclerView根本没有起到复用的作用。由于需要绘制所有的item,必定耗时更长。所以会初始化缓慢。有一个方法:recyclerView.getLayoutManager()..._nestedscrollview 嵌套recyclerview 慢

dex2jar与jd_gui反编译Android中JAVA代码_android jd_gui java代码在哪里-程序员宅基地

文章浏览阅读1.1k次。1、下载文件,解压dex2jar就不必多说了吧_android jd_gui java代码在哪里

浅谈航管二次雷达工作原理-程序员宅基地

文章浏览阅读1w次,点赞5次,收藏29次。航管二次雷达工作原理与应用1.航管二次雷达简介:航管应答一般称为空中交通管制雷达系统(Air Traffic Control Radar Beacon System)或二次航管雷达,是用来提供地面对空中交通监视和管制的应答系统。该系统由地面扫描询问雷达和空中雷达应答机组成工作的频段为L波段。地面雷达将收到飞机应答信号通过系统处理后,地面显示器以目标光点的形式在平面显示出来,屏幕显示位置对应飞机..._二次雷达

NVIDIA的黑科技3:VXGI体素全局光照_立体像素全局光照-程序员宅基地

文章浏览阅读5.2k次。每一个行业都有自己的“圣杯”,例如能源方面的核聚变、医药方面的癌症特效药以及空间探索方面的超光速推进力。 任何领域中“圣杯”的定义都是难以实现和代价高昂的,或者完全是科幻产物。也许这就是我们之所以对此心驰神往的原因所在。   计算机图形领域的“圣杯”就是“实时全局光照”。全局光照是一种渲染游戏环境的方法,它通过模拟光线的行为,从而体现各个表面之间的光线反射效果。然而以光子级别进行自然仿真处_立体像素全局光照

Shopee(虾皮)运营没流量?没销量?只因你没掌握店铺引流方法大全-程序员宅基地

文章浏览阅读182次。运用各站点的直播功能进行引流,在直播中可以加入要推的商品,并在直播中发放优惠券,同时可以引导观众关注店铺。每天分时段小批量上新商品,这样新上传的商品会在同类商品的搜索排名中处于靠前的位置,有利于店铺持续曝光。3)加购优惠:对上Shopee限时抢购的商品设置为加购优惠主商品,并将新品设置为加购商品,精准养链接。中国卖家中心的营销工具包含多种店铺营销活动,可以增加商品的曝光度和市场竞争力,刺激买家的消费欲望。根据目标客户群选品:比如,如果60%-70%的用户为年轻女性,则关注性价比高的潮流商品;

CSS中 设置( 单行、多行 )超出显示省略号_css超出显示...-程序员宅基地

文章浏览阅读10w+次,点赞46次,收藏121次。css设置超出显示省略号可分两种情况:但使用的核心代码是一样的:需要先使用 “overflow:hidden;” 来把超出的部分隐藏,然后使用“text-overflow:ellipsis;”当文本超出时显示为省略号。思路:1、使用 overflow:hidden; 语句不显示超过对象尺寸的内容,就是把超出的部分隐藏了;2、使用 -webkit-line-clamp: 行数; 语句限制显示文本的行数;3、使用 text-overflow:ellipsis; 语句用省略号“…”隐藏超出范围的文本说明_css超出显示...

推荐文章

热门文章

相关标签