熵编码(三)-算术编码(一) 基础篇

1. 前言

通过熵编码系列第一篇文章熵编码(一)-熵编码概述,我们了解了熵编码的概念和分类。本篇文章主要介绍算术编码。

2. 算术编码概述

算术编码是图像压缩的主要算法之一,是一种熵编码算法。和其他熵编码方法不同,其他熵编码采用将输入信号分割成符号再对每个符号进行编码,而算术编码是直接将整个输入信号编码为一个满足(0.0<=n<1)的小数数n。
算术编码将输入的符号依据它的概率映射到[0,1)内的一个小区间上,如此递归地进行区间映射,最后得到一个小区间,从该区间内选取一个代表性的小数作为实际的编码输出

3. 算术编码步骤

算术编码步骤归纳描述:

  1. 设输入符号串s, s中的符号属于S = {a1, a2, ...., an},初始编码区间[L0, L0+R0)
  2. 设置更新符号出现的概率p(an) =
  3. 设置更新符号的累计概率即概率区间下限P(an)=\(\sum_{i=0}^n\)p(an)
  4. 编码区间大小更新:Ri+1= Ri * p(an)
  5. 编码区间下限:Li+1 = Li + Ri * P(an)
  6. 重复步骤2、3、4、5(静态概率模型不需要重复步骤2、3),不断更新编码区间直到符号编码完成,即可得到最终的映射区间
  7. 在最终的编码区间选择最短二进制小数
    映射区间

从上述编码步骤可以看出算术编码需要两个参数:概率模型和编码区间大小。

算术编码的概率模型分为静态概率模型和自适应概率模型,为简单起见本文的编解码示例使用的是静态概率模型。

  • 静态概率模型:输入符号串的各字符的概率在编码过程中始终不变
  • 自适应概率模型:输入符号串的各字符的概率在编码过程中根据上下文自适应调整变化

4. 算术编解码示例(静态概率模型)

4.1. 算术编码示例

举例:输入序列"aabbc", 符号概率如下表:

symbol a b c
probability 0.4 0.4 0.2

1.算术编码会根据概率分割初始编码区间[0,1), 其中区间的下限为表中前面的符号的累计概率

a:[0, 0.4), b:[0.4, 0.8), c:[0.8, 1.0)

字符序列第1个字符a,那么编码区间是[0, 0.4)

2.按照字符概率重新划分[0, 0.4)编码区间

a:[0, 0.16), b:[0.16, 0.32), c:[0.32, 0.4)

字符序列第2个字符a,那么编码区间是[0, 0.16)

3.按照这种方式递归地进行区间映射,最终“aabbc”映射到的区间是[0.111008, 0.1152)

序列 编码区间 区间大小
初始 [0, 1) 1
a [0, 0.4) 0.4
a [0, 0.16) 0.16
b [0.064, 0.128) 0.064
b [0.0896 0.1152) 0.0256
c [0.11008, 0.1152) 0.00512

算术编码的总体的编码流程可以参考下图:
算术编码的总体的编码流程

选择最短二进制小数
完成最后一个字符编码得到最终编码区间[0.111008, 0.1152),可以在该区间选择任意一个小数作为最终的编码小数。由于计算机智能识别二进制码,因此我们还需要将小数转化为二进制码。
因为是最短压缩,所以需要在[0.111008, 0.1152)选择一个二进制最短的小数
选择最短二进制小数
将10进制区间[0.111008, 0.1152)转换为二进制区间[0.0001110000101110, 0.0001110101111101), 取区间下限&上限二进制相同部分,直到不相同为止(0.0001110)并尾部添加1,小数二进制是0.00011101(0.11328125),因为计算机寄存器只能存储整数,因此保存小数部分二进制码00011101,比特长度是8位。

4.1. 算术解码示例

理解了上述编码流程之后,那么解码流程也就可以实现了。算术解码就只是需要判断代表性的小数在哪个区间,相应地就知道输入的符号了。
上文的编码二进制00011101,对应的小数是0.00011101(10进制0.11328125),输入序列有5个字符, 符号概率如下表:

symbol a b c
probability 0.4 0.4 0.2

1.算术编码会在初始编码区间[0,1)按照概率对符号进行编码区间划分,其中区间的下限为表中前面的符号的累计概率

a:[0, 0.4), b:[0.4, 0.8), c:[0.8, 1.0)

浮点数0.11328125选择a的区间[0, 0.4)作为新的编码区间,输入序列第1个字符是a

2.按照字符概率重新划分[0, 0.4)编码区间

a:[0, 0.16), b:[0.16, 0.32), c:[0.32, 0.4)

浮点数0.11328125选择a的区间[0, 0.16)作为新的编码区间,输入序列第2个字符是a

3.按照这种方式递归地进行区间映射,最终浮点数0.11328125映射到的输入序列是"aabbc"

上述解码步骤我们提前知道了输入序列的长度,否则会无限的进行解码操作。实际的解码器中使用的是序列终止符,当遇到终止符时停止解码操作

5. 算术编码的原理

通过上文我们实现了算术编码&解码的步骤。但是为什么算术编码可以压缩数据呢?
算术编码的目的是要在最终的编码区间内找出最短二进制编码作为最终编码。最终的编码区间越大,可容纳小数精度越低,从而找到的最终二进制小数越短。例如[0.4, 0.5)区间和[0.44532689,0.52345678)区间各找一个最短二进制小数,肯定是[0.4, 0.5)区间找到的最短二进制编码

因此算术编码的实现方式就是:尽量使最终的编码区间更大。因为高频字符出现多,低频字符出现少。因此给高频字符分配较大的编码区间,低频字符分配较小的编码区间,从而使得最终的编码区间更大。

总结:算术编码的压缩本质是对高频字符赋予更大的编码区间,从而实现压缩数据的目的

6. 自适应

上文章我们了解了静态概率模型下的算术编码。然而实际应用中,输入流中的符号概率是根据上下文动态变化的,因此需要建立一个动态概率模型去维护更新符号概率。
设输入流为T,当前符号X,当前符号之前的字符流Z, Z\(\subset\)T,则当前符号概率P(X|Z),自适应概率模型的符号概率受到条件因子Z的影响而动态变化。算术编码的核心就是选择建立一个合适的自适应概率模型,不同编码器有不同的概率模型

7. 算术编码精度和区间缩放

上文简单描述了算术编码的流程,输入字符串越长,最终的编码区间可容纳的小数精度越大, 上述编码过程使用的是无限精度的小数并且在编码结束的时候再转换成二进制。然而由于计算机能处理的精度有限,许多编码器使用的都是有限精度,从而导致编码区间随着编码会越来越小直至为0,因此可编码的字符数目受到编码器的精度限制。
解决这个问题的方法就是编码区间缩放,区间缩放使有限精度不再是编码字符串的的限制。当编码区间减小到区间内的所有数值共享特定的数字时,那些数字就送到输出数据中,现存的数据左移并在尾部添加新的数据位用于扩展能用的数据范围。通过区间缩放,编码器的有限精度可以表示无限精度小数,同时编码器的所用位数甚至可以小于计算机能够处理的精度位数。
下篇文章熵编码(四)-算术编码(二)进行详细讲述算术编码的区间缩放

posted @ 2024-07-22 17:33  尹佑壮  阅读(17)  评论(0编辑  收藏  举报