算术编码是图像压缩的主要算法之一。 是一种无损数据压缩方法,也是一种熵编码的方法。和其它熵编码方法不同的地方在于,其他的熵编码方法通常是把输入的消息分割为符号,然后对每个符号进行编码,而算术编码是直接把整个输入的消息编码为一个数,一个满足(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)6∗p(0)2=(43)4∗41
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+(1−41)∗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+(1−167)∗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+(1−6437)∗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+(1−256175)∗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)=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中的一个被该值取代而与另外一个构成新的区间。
代码编程思路与面的例题思路基本相同,这里不再过多赘述。在同目录下新建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;
}
参考资料:信息论与编码(曹雪虹著)
第一次认认真真写博客,有很多不足欢迎评论指正,另外还请多多点赞支持啊啊啊!
文章浏览阅读100次。多线程编程基础java自身提供了创建多线程的方案:继承Thread 、实现Runable接口、实现Callable接口线程对象的一些常见方法线程对象的一些常见方法:_public integer call
文章浏览阅读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
文章浏览阅读1.7w次,点赞3次,收藏11次。iOS中左右滑动切换,滑动标签页导航的设计思路iOS开发中经常(几乎每个APP都含有这样的页面吧,几乎!UI设计师也都是这样抄来抄去…..)demo见Github:https://github.com/zhengwenming/SliderTab估计很多人都会说,直接用第三方就可以了,很多人封装过,很好用。而且这样的页面用第三方2分钟搞定,省时省力。 笔者也曾用过第三方,但是屡屡出_ios 多标签左右切换内容
文章浏览阅读947次,点赞28次,收藏15次。基于springboot+uniapp的点餐系统设计与实现_微信点餐小程序数据库设计
文章浏览阅读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
文章浏览阅读213次。原标题:Linux mktemp 命令使用方法Linux mktemp命令用于建立暂存文件。mktemp建立的一个暂存文件,供shell 使用。创建临时文件或者目录,这样的创建方式是安全的。此命令的适用范围:RedHat、RHEL、Ubuntu、CentOS、SUSE、openSUSE、Fedora。语法mktemp [-qu][文件名参数]参数:-q 执行时若发生错误,不会显示任何信息。-u ..._xbfi
文章浏览阅读1.1k次。为解决recyclerView的滑动冲突导致的NestedScrollView嵌套RecyclerView,如果不给RecyclerView固定高度,关闭自动计算高度的话。默认是展示所有item,也就是说recyclerView根本没有起到复用的作用。由于需要绘制所有的item,必定耗时更长。所以会初始化缓慢。有一个方法:recyclerView.getLayoutManager()..._nestedscrollview 嵌套recyclerview 慢
文章浏览阅读1.1k次。1、下载文件,解压dex2jar就不必多说了吧_android jd_gui java代码在哪里
文章浏览阅读1w次,点赞5次,收藏29次。航管二次雷达工作原理与应用1.航管二次雷达简介:航管应答一般称为空中交通管制雷达系统(Air Traffic Control Radar Beacon System)或二次航管雷达,是用来提供地面对空中交通监视和管制的应答系统。该系统由地面扫描询问雷达和空中雷达应答机组成工作的频段为L波段。地面雷达将收到飞机应答信号通过系统处理后,地面显示器以目标光点的形式在平面显示出来,屏幕显示位置对应飞机..._二次雷达
文章浏览阅读5.2k次。每一个行业都有自己的“圣杯”,例如能源方面的核聚变、医药方面的癌症特效药以及空间探索方面的超光速推进力。 任何领域中“圣杯”的定义都是难以实现和代价高昂的,或者完全是科幻产物。也许这就是我们之所以对此心驰神往的原因所在。 计算机图形领域的“圣杯”就是“实时全局光照”。全局光照是一种渲染游戏环境的方法,它通过模拟光线的行为,从而体现各个表面之间的光线反射效果。然而以光子级别进行自然仿真处_立体像素全局光照
文章浏览阅读182次。运用各站点的直播功能进行引流,在直播中可以加入要推的商品,并在直播中发放优惠券,同时可以引导观众关注店铺。每天分时段小批量上新商品,这样新上传的商品会在同类商品的搜索排名中处于靠前的位置,有利于店铺持续曝光。3)加购优惠:对上Shopee限时抢购的商品设置为加购优惠主商品,并将新品设置为加购商品,精准养链接。中国卖家中心的营销工具包含多种店铺营销活动,可以增加商品的曝光度和市场竞争力,刺激买家的消费欲望。根据目标客户群选品:比如,如果60%-70%的用户为年轻女性,则关注性价比高的潮流商品;
文章浏览阅读10w+次,点赞46次,收藏121次。css设置超出显示省略号可分两种情况:但使用的核心代码是一样的:需要先使用 “overflow:hidden;” 来把超出的部分隐藏,然后使用“text-overflow:ellipsis;”当文本超出时显示为省略号。思路:1、使用 overflow:hidden; 语句不显示超过对象尺寸的内容,就是把超出的部分隐藏了;2、使用 -webkit-line-clamp: 行数; 语句限制显示文本的行数;3、使用 text-overflow:ellipsis; 语句用省略号“…”隐藏超出范围的文本说明_css超出显示...