CABAC熵编码详解

信息熵概念

信息熵是指信号源(信源)的信息量。设有一个离散信源,{$X_i$},i=1,2..,N,它产生消息的概率是已知的,记为,P({$X_i$})),$\sum_{i=1}^{N} P(X_i)=1 $,则信息量定义为:$I(X_i)=\log_{2}{\frac{1}{P(X_i)} } =-\log_{2}{P(X_i) } bit$

信息量仅反映了一个符号的信息量的大小,而信源都是由若干个符号所组成,如二进制信源由0和1两个符号,因此,用平均信息量,称

为“熵”(entropy)来表示由多个符号所组成信源所携带的信息量,定义为

$H=\sum_{i=1}^{N} P(X_i)I[P(X_i) ]=-\sum_{i=1}^{N} P(X_i)\log_{2}{P(X_i) }$

上式取以2为底的对数时,单位是比特:

$H=\sum_{i=1}^{N} P(X_i)I[P(X_i) ]=-\sum_{i=1}^{N} P(X_i)\log_{2}{P(X_i) }$  (比特/符号)(2)

定长编码

定长编码也称为等长编码,即为每个编码符号分配一个等长比特的码字。常用的二进制码如表1:

在HEVC中,描述子f(n)表示有一个固定n比特的预定义值。具体到某一具体语法元素forbiden_zero_bit,它是一个f(1)码字,即1比特长度,其值为0。这种定长编码主要用于NAL单元头,slice分割头以及SPS/PPS中.

变长编码

变长编码为各个编码符号分配的比特数不一定相等,常见的变长编码有哈夫曼、香浓、指数哥伦布编码等。变长编码的优势是编码的平均长度比定长编码短。例如概率分布为2的负幂次方的序列符号,具体如下:

图 3 概率分布为2的负幂次方的序列符号

3.1哈夫曼编码
对图3采用哈夫曼如图:

 

按照公式$\bar{N}=\sum_{i=1}^{n} p_{i} n_{i}$
1计算其平均码长为:

$\bar{N}=\sum_{i=1}^{n} p_{i} n_{i}$=0.20x2+0.19x2+0.18x3+0.17x3+0.15x3+0.05x4+0.05x4+0.01x4=2.72

若采用定长编码,则需要3位二进制等长码

算术编码

3.2.1传统编码方法
传统编码是通过符号映射实现的。映射包含符号(symbol)与码字(codeword)两个要素,如下面的例子

通过上述的映射表,我们可以把"hello"编码成码流 01 00 10 10 11。
而诸如Haffuman,Shannon这些编码方法也没脱离这种编码模式,他们只是通过符号出现的概率对码字进行调优。 

算术编码

从理论上讲,对信源数据采用哈夫曼熵编码方法可以获得最佳编码效果,但是在实际中,由于在计算机中存储和处理的最小数据单位是1bit,无法表示小数比特。例如两符号信源{x,y},其对应的概率为{2/3,1/3},根据信息熵计算,x的最佳码长=-log2(2/3)bit=0.588bit;y的最佳码长=-log2(1/3)bit=1.588bit;

平均码长=0.588*(2/3)+1.588*(1/3)=0.961bit。而计算机中不可能有非整数位出现,即采用哈夫曼对x,y编码,得到x,y的码字分别为0和1,可见对于出现概率大的符号x并未赋予较短的码字。而算术编码的产生正是为了解决计算机中必须以整数位进行编码的问题。

算术编码的思想是用0到1的区间上的一个数字来表示一个字符输入流,本质是为了整个输入流分配一个码字,而不是给输入流中的每个字符分别指定码字,算术编码是用区间递进的方法来为输入流寻找这个码字的,从第一个符号确定的初始区间0,1开始,逐个字符地读入输入流,在每一个新的字符出现后递归地划分当前区间,划分的根据就是各个字符的概率,将当前区间按照各个字符的概率划分为若干子区间,将当前字符对应的子区间取出,作为下一个字符时的当前区间,当处理完最后一个字符后,得到了最终区间,在最终区间中挑选一个数字作为输出。
算术编码分为浮点算术编码与定点算术编码,例如对浮点算术编码而言:用[0,1]的概率区间,对一串字符编码后,得到了最终区间。在最终区间挑选一个数字作为编码输出,而这个数字是一个小数,受计算机精度的影响;为了避免这种影响,在实际使用中采用定点算术编码,且根据计算机的精度采用比例缩放的方法来解决。在H264/H265中将[0,1]区间放到至[0,210210],采用32位的寄存器实现。
算术编码有如下编码步骤:
1.首先我们需要根据概率设定各符号在[0,1)上的初始区间,其中区间的起点为表中前面的符号的累计概率

"hello"的第一个符号为"h",那么映射的区间为[0.1,0.3)。

2. 接下来我们需要根据符号的概率分割[0.1,0.3)上的区间,得到的结果如下

"hello"的第二个符号为"e”,那么映射的区间为[0.1,0.12)。

3按照这种方式继续进行区间映射,最终“hello"映射到的区间是[0.10888,0.1096)

4.从区间[0.10888,0.1096)中任取一个代表性的小数,如"0.109"就是编码"hello"后的输出值*
算术编码的总体的编码流程可以参考下图

算术编码总体上可以按照如下进行描述
·设输入符号串为ss,ss中的符号值取自符号集S={a1,a2,a3,..,am}
·这些符号出现的概率分别为p(ak)={p1,p2,p3,.,pm}
·这些符号的累计概率为$P(ak)\sum_{k=1}^{m} p k$。累计概率可以理解为,如果区间为[0,1)时,该符号的区间起点的位置<端点>.
·区间大小更新为$R_{i+1}=R_i*p(ak)$,初始值为R0=1,即新活动区间=前活动区间דak"的概率
·区间的起点更新为$L_{i+1}=L_i+P(ak)*R_i$,初始值为L0=0;即新端点=前端点+前活动区间*"ak"的端点
当处理符号ak时,区间R宽度根据ak出现概率p(ak)而变窄,符号序列越长,相应的子区间越窄,编码的位数越多。

算术解码就只是需要判断代表性的小数在哪个区间,相应地就知道输入的符号了

二进制算术编码

二进制算术编码的编码方法跟算术编码是一样的,但是输入只有两个符号:“0”,“1”,也就是说输入的是二进制串。
除了是对二进制串进行编码这个特征外,二进制算术编码跟普通的算术编码还有一些区别,总体上可以按照如下进行描述:

 

算术编码的压缩本质,就是找一个二进制最短的小数作为最终编码。在保留字符排列顺序的同时,对于更高频出现的字符,也就是概率更大的字符,赋予更大的小数区间。比如在低精度的 [0.1, 0.2) 和 高精度的 [0.1111111111, 0.1111111112) 之间各找一个最短编码的二进制进行比较,肯定是 [0.1, 0.2) 中找到的的最短二进制编码更短。
所以算术编码的实现途径就是:尽量使最终目标区间的范围更大。
目标区间长度=1*每个字符出现的概率累乘 所以我们希望出现频率高的字符作为更大的因子。

CABAC编码

CABAC采用的是二进制算术编码,在编码过程中需要传入二进制串,输出的也是二进制串。
在h.264标准中,CABAC在语法结构中用ae表示,它只用于编码slice_data中的语法元素(包括slice_data内部的子模块的语法元素)
CABAC实现分为四个部分
·上下文变量的初始化
·待编码语法元素二值化

·上下文建模 (确定上下文索引I)
·算术编码

上下文变量的初始化

初始化执行于slice开始之前,另外如果在编码过程中某个宏块是PCM宏块,那么在PCM宏块之后,编码下一个宏块之前也需要进行初始化。
初始化主要工作就是确定所有上下文的初始MPS以及初始状态pStateldx。求解方法如下

preCtxState = Clip3( 1, 126, ( ( m ∗ Clip3( 0, 51, SliceQPY ) ) >> 4 ) + n )
if( preCtxState <= 63 ) {
    pStateIdx = 63 − preCtxState
    valMPS = 0                                                 ( 9-5)
} else {
    pStateIdx = preCtxState − 64
    valMPS = 1
}

上面的计算依赖于SliceQPY,m,n三个变量,其中不同的上下文索引丨(contexIndex)对应不同的m、n,具体的m、n的取值请参考标准9.3.1中的各个表格。上下文索引是基于语法元素以及二值化后的二进制串的索引lbinldx,我们将在下一小节进行阐述。
在CABAC的初始化过程的结果会得到所有上下文索引I对应的MPS与pStateldx的初始值。如果确定了MPS为"O",那么LPS为"1”,反之如果MPS为"1",那么LPS为"O"。状态pStateldx是什么呢? 

LPS的概率

状态pStateldx是LPS出现的概率pLPSpLPS的索引l。在CABAC中规定了LPS的概率取值范围是pLPS∈[0.01875,0.5],由于LPS是小概率符号,因此它的概率肯定是小于0.5的,如果某个小概率符号在状态转换的过程中超出了0.5,此时我们就需要把MPS与LPS进行交换。
CABAC的状态机中共有64个状态,pStateldx=0,1,2,...,63,分别代表64个不同的概率,除了pStateldx=63外,其他的63个状态都满
足上述状态转换规则,其中[pOLPS=0.5p62LPS=0.01875

 

CABAC状态机的状态转换如下图(黑色实线代表输入的是MPS,红色虚线代表输入的是LPS),具体的pStateIdx变换请参考标准的表9-45

2. 待编码语法元素二值化

CABAC编码的是slice data中的语法元素,在进行算术编码前,需要把这些语法元素按照一定的方法转换成适合进行二进制算术编码的二进制串,这个转换的过程被称为二值化(binarization)。

二值化的方案共有7种

1. 一元码(Unary)

对于一个非二进制的无符号整数值符号,在CABAC中的一元码码字由个“1”位外加一个结尾的“0”组成,见下表。例如,输入的语法元素值为3,其二值化结果为1110

value of syntax elementUnary
0 0          
1 1 0        
2 1 1 0      
3 1 1 1 0    
4 1 1 1 1 0  
5 1 1 1 1 1 0
           
binIdx 0 1 2 3 4 5
2. 截断一元码(TU,Truncated Unary)

一元码的变体,用在已知语法元素的最大值cMax的情况。对于的范围内的取值,使用一元码进行二值化。对于,其二值化的二进制串全部由“1”组成,长度为cMax。例如,当cMax=5时,语法元素值为4的二进制串为11110,语法元素值为5的二进制串为11111。

 

3. k阶指数哥伦布编码(kth order Exp-Golomb,EGk)

指数哥伦布编码由前缀和后缀组成。其中前缀部分由的值所对应的一元码组成;后缀部分可通过使用长度为位的的二进制值来计算。详细的计算过程请参考指数哥伦布编码。

4. 定长编码(FL,Fixed-Length)

用定长编码二进制的无符号语法元素, 语法元素的最大值cMax已知,那么定长编码的长度为,其中值就是语法元素的值的二进制。定长编码用于近似均匀分布的语法元素的二值化。

给定一个参数cMax,对于编码语法元素值x,必须满足0≤x<cMax0≤x<cMax,输出二进制串为十进制值x对应的二进制数,其最小长度固定为IFZ=floor(log2(cMax)),最高位比特(MSB)标注在最低位比特之前(LSB)。一个3比特长的定长码的码表见下表:

表 4.2.5 定长码一例(cMax=8cMax=8)

 

5. mb_type与sub_mb_type特有的查表方式

详情请查看h.264标准中9.3.2.5小节

 

6. 4位FL与截断值为2的TU联合二值化方案

这种方案只用于对语法元素CBP的二值化。4位的FL(cMax=15)的前缀用于编码亮度CBP,2位的TU用于编码色度CBP(当色彩格式为4:2:0或4:2:2时才会存在这个后缀)

 

7. TU与EGk的联合二值化方案(UEGk,Unary/kth order Exp-Golomb)

这种方案的前缀使用一元截断码,后缀使用k阶哥伦布编码。但是在取值较小的范围内,只用一元码表示(即只有前缀部分)。对于不同的语法元素,有不同的截断值与阶数。如下表为abs_level_minus1的二值化表(cMax=14的TU、0阶哥伦布编码)

abs_level_minus1Prefix (TU)Suffix (EG0)
0 0                                    
1 1 0                                  
2 1 1 0                                
3 1 1 1 0                              
4 1 1 1 1 0                            
                         
             
12 1 1 1 1 1 1 1 1 1 1 1 1 0            
13 1 1 1 1 1 1 1 1 1 1 1 1 1 0          
14 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0        
15 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0    
16 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1    
17 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0
18 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 1
                                     
binIdx 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

 

具体哪个语法元素选择哪种二值化方案,请查看标准9.3.2中d第一个表格。

 

3. 上下文建模(确定上下文索引)

在前面初始化的时候就出现了上下文这个概念,那么上下文所指的是什么?

以JM中的上下文结构体为例

1
2
3
4
5
6
7
//! struct for context management
struct  bi_context_type  
{
    unsigned  long   count; 
    byte state;  //uint16 state;         // index into state-table CP
    unsigned  char   MPS;            // Least Probable Symbol 0/1 CP 
};

上下文包含两个变量:MPS、pStateIdx(count只是用于计数)。在CABAC编码的过程中会碰到需要修改这两个值的情况(如上面的状态变换),这些修改都是以上下文为单位的。

语法元素在经过二值化后形成二进制串,二进制串中不同binIdx位置上的MPS(出现频率高的符号)可能会有所不同,并且概率也可能会不同,因此需要用一个概念来表示特定语法元素的二进制串中特定binIdx的MPS与pStateIdx,上下文就是这样的概念。

 

在h.264标准中,用一个上下文索引ctxIdx来代表上下文,ctxIdx的取值为0~1023,就是说h.264的上下文一共有1024个。

 

ctxIdx的计算方式分为两种:

如果语法元素为coded_block_flag、significant_coeff_flag、last_significant_coeff_flag、coeff_abs_level_minus1,即残差系数部分的语法元素,则

ctxIdx = ctxIdxOffset + BlockCatOffset(ctxBlockCat) + ctxIdxInc(ctxBlockCat)

否则

ctxIdx = ctxIdxOffset + ctxIdxInc

 

其中的变量有

  • ctxIdxOffset  每个语法元素都有一个ctxIdxOffset,甚至一些语法元素在二值化后分为前后缀会,这种语法元素可能会有两个ctxIdxOffset,如下表格部分摘自h.264标准9.3.2的第一个表格
Syntax Element ctxIdxOffset
mb_type 
(SI slices only)
prefix: 0
suffix: 3
mb_type (I slices only) 3
mb_skip_flag 
(P,SP slices only)
11
mb_type 
(P, SP slices only)
prefix: 14
suffix: 17
sub_mb_type[] 
(P, SP slices only)
21
  • ctxIdxInc   在特定的语法元素二值化后,会形成以binIdx为索引的二进制串,尽管是同一个二进制串,但是不同的binIdx上的上下文(MPS,pStateIdx)可能会有所不同,ctxIdxInc就是在这种情况下产生的一个值,它用于划分二进制串上不同的上下文。如下面一项表格摘自h.264标准9.3.3.1的第一个表格
    ctxIdxOffset b0 b1 b2 b3 b4 b5 >=b6
    40 0,1,2 3 4 5 6 6 6
    ctxIdxOffset=40代表mvd的横向分量,上述表格描述了这个语法元素各个binIdx所对应的ctxIdxInc。也就是说mvd横向分量按照binIdx得到的ctxIdx如下表
    binIdx b0 b1 b2 b3 b4 b5 >=b6
    ctxIdx 40,41,42 43 44 45 46 46 46
    像上面表格的这种binIdx=0中出现三个ctxIdx的情况,意思就是会根据编码的具体情况选择0、1或者2作为ctxIdxInc,需要另外分析。ctxIdxInc的确定方法具体请参考标准9.3.3.1小节
  • BlockCatOffset(ctxBlockCat)  其中ctxBlockCat的范围为0~13,分别代表不同残差块类型,如下表
    Block description maxNumCoeff ctxBlockCat
    block of luma DC transform coefficient levels 16 0
    block of luma AC transform coefficient levels 15 1
    block of 16 luma transform coefficient levels 16 2
    block of chroma DC transform coefficient levels when ChromaArrayType is equal to 1 or 2 4*2/4*4 3
    block of chroma AC transform coefficient levels when ChromaArrayType is equal to 1 or 2 15 4
    block of 64 luma transform coefficient levels 64 5
    block of Cb DC transform coefficient levels when ChromaArrayType is equal to 3 16 6
    block of Cb AC transform coefficient levels when ChromaArrayType is equal to 3 15 7
    block of 16 Cb transform coefficient levels when ChromaArrayType is equal to 3 16 8
    block of 64 Cb transform coefficient levels when ChromaArrayType is equal to 3 64 9
    block of Cr DC transform coefficient levels when ChromaArrayType is equal to 3 16 10
    block of Cr AC transform coefficient levels when ChromaArrayType is equal to 3 15 11
    block of 16 Cr transform coefficient levels when ChromaArrayType is equal to 3 16 12
    block of 64 Cr transform coefficient levels when ChromaArrayType is equal to 3 64 13

    在残差系数部分,上下文是会根据不同的残差块类型做出不同选择的,BlockCatOffset就代表了不同的残差块类型的索引偏移,具体偏移值可以查看标准中的相关表格。

4. 算术编码

算术编码是基于区间划分的,普通的概率划分需要使用到多位乘法。CABAC的算术编码为了降低计算复杂度,并便于硬件实现,采取了如下一些方法:

  1. 总是估计小概率符号LPS()的概率,并将其概率离散化成64个不同概率状态。概率估计转换成基于表格的概率状态的转换(见初始化部分的描述)。
  2. 使用9bit的变量与10bit的表示当前区间,其中为区间的起点,为区间长度
  3. 每当输入新符号时,会对区间的起点以及区间的长度进行更新,在前面的二进制算术编码时,我们已经得知两者的更新方法,其中与的更新包含了浮点数乘法,为了降低运算复杂度,CABAC把乘法换算成了查表的形式。换算方法如下:
    • 离散化的状态pStateIdx代表了符号的概率
    • 9个bit的区间长度通过被量化成2个bit,即(因为总是大于等于小于,在后面的归一化可以看出来)
    • 有了上述两个离散的变量,区间更新所需要的乘法就能转换成查表操作,表格请查看标准9.3.3.2中的第一个表格。
  4. 在算术编码的过程中,尽管是同一上下文,但是概率并不是固定的,每次输入一个新符号都会改变相应上下文的概率,也就是会进行状态转换(见初始化部分的描述)
  5. 对近似均匀分布的语法元素,在编码和解码时选择盘路(bypass)模式,可以免除上下文建模,提高编解码的速度。
  6. 由于编码区间是有限位表示的,因此在输入一个符号进行区间更新后,需要进行重归一化以保证编码精度。

算术编码过程

该过程可分为5个步骤

  1. 通过当前编码器区间范围得到其量化值作为查表索引,然后利用状态索引与进行查表得出的概率区间大小。
  2. 根据要编码的符号是否是MPS来更新算术编码中的概率区间起点以及区间范围
  3. 表明当前LPS在上下文状态更新之前已经是0.5的概率,那么此时还输入LPS,表明它已经不是LPS了,因此需要进行LPS、MPS的转换
  4. 更新上下文模型概率状态
  5. 重归一化,输出编码比特。
重归一化分析

在区间划分结束后,如果新的区间不在之内,即,256是最大编码区间的,那么就需要进行重归一化操作(incremental output and interval expansion),输出一位或多位编码比特。重归一化过程如下图所示。

在CABAC编码过程中,在输入符号后,进行区间更新,接下来就是重归一化过程。下面就以表示区间为例,分析重归一化过程

    (请注意,在该过程中,只起到辅助作用,实际的区间为)

  1. 的情况,如果,则可知,那么可以确定编码区间在区间上,用二进制表示这个区间即为,因此输出(只记录小数点后面的二进制)。最后用来表示区间,也就是将原本的线性扩增到
  2. 的情况,如果,那么就可以确定编码区间在区间上,用二进制表示这个区间即为,因此输出。最后用来表示区间,也就是将原本的线性扩增到
  3. 的情况,如果,则,编码区间可能在区间内,也有可能跨越与两个区间,即可能是,也可能是。此时可以先暂缓输出,用来表示区间,也就是将原本的线性扩增到,然后进入重归一化的下一个循环继续判断。
  4. 的情况,无法通过来确定编码区间,需要通过输入下一个符号来对与进行更新后再继续进行判断,因此当前符号的编码流程结束。由于这个原因,因此在一个符号编码结束后,另一个符号编码开始前,总是。

输出

在编码输出“0”或者“1”的阶段,用PutBit(B)表示

 

关于PutBit(B)的分析,参考上面重归一化的区间图,可以看到有三种情况

  1. 情况1,走PutBit(0),直接输出“0”
  2. 情况2,走PutBit(1),直接输出“1”
  3. 情况3,输出可能为“10”或者“01”,因此不能直接输出,走bitsOutstanding++的步骤。在下一次编码符号时,符合情况2,走PutBit(1),此时bitsOutstanding = 1,因此输出“10”

另外,PutBit(B)不会编码第一个bit。原因是CABAC在初始化的时候,会以表示区间,而在初始化区间时,这意味着已经进行了第一次区间选择,区间为,需要输出“0”。PutBit(B)在此阻止这个“0”的输出,这样就能得到正确的算术编码结果了。

旁路(bypass)编码

有些语法元素在二值化后选择的可能不是上述的算术编码,而是旁路编码,具体情况请查看h.264标准9.3.2的第一个表格。旁路编码中,假设待编码的符号符合近似的均匀分布。下图给出了旁路模式下的编码过程。

旁路模式有几个特点:符号均匀分布,无需对进行量化与查表;每编码完一个符号后,总是会小于,因此每次都需要对概率区间进行放大;把对的移位操作提到了前面,因此旁路编码的重归一化的区间可以看作由变成了。

下面是旁路编码的一个例子

编码结束 EncodeTerminate

在编码语法元素end_of_slice_flag(ctxIdx = 276)以及I_PCM mb_type时会调用EncodeTerminate这个编码过程。

在EncodeTerminate中,采用的是pStateIdx = 63的状态,这个状态表示的是当前宏块是否为该slice的最后一个宏块的概率。在该状态下,对概率区间的划分跟概率区间量化值无关。在编码end_of_slice_flag以及I_PCM的mb_type时,概率区间固定为。如果当前宏块为slice的最后一个宏块(end_of_slice_flag = 1)或者当前编码为PCM宏块并且编码它的mb_type I_PCM时,结束CABAC编码,调用EncodeTerminate中的EncodeFlush。具体情况请参考标准中的9.3.4.5小节。

 

在编码完成slice的最后一个宏块后,将会调用字节填充过程。该过程会往NAL单元写入0个或者更多个字节(cabac_zero_word),目的是完成对NAL单元的封装(标准9.3.4.6)。这里有计算如下

 

如果,则需要将3字节长的0x000003添加到NAL单元次。这里的前两字节0x0000代表了cabac_zero_word,第三个字节0x03代表一个emulation_prevention_three_byte,这两个语法元素请参考h.264语法结构分析的相关部分。

如果,则无需添加字节到NAL单元。

式子中的各个变量所代表的意思请查看标准

posted @ 2024-09-06 14:22  luckylan  阅读(414)  评论(0)    收藏  举报