基于上下文的二进制算术编码(Context-Based Adaptive Binary Arithmetic Coding,CABAC)将自适应二进制算术编码和上下文模型相结合。是H.265/HEVC的主要熵编码方案。
主要包括三个步骤:
二进制化;
上下文建模;
二进制算术编码;
其流程如下:
二进制化就是将输入的语法元素转化为二进制形式,如果语法元素以及是二进制形式则跳过该步骤。
H.265/HEVC中的二进制化方法有截断莱斯二元化(Truncated Rice binarization,TR),K阶指数哥伦布二元化(k-th order Exp-Golomb binarization,EGK)和定长二元化(Fixed-length binarization,FL)。K阶指数哥伦布编码前面已经介绍过。下面介绍TR和FL。
(1)截断莱斯二元化TR
设阈值为cMax,莱斯参数R,待二元化的语法元素为V。
截断莱斯码由前缀串和后缀串拼接成,前缀值P由下式计算得到:P = V >> R
则前缀串获取过程为:若P值小于(cMax >> R),则前缀串由P个1和一个0组成,长度为P+1;若P值大于等于(cMax >> R),则前缀串由(cMax >> R)个1组成,长度为(cMax >> R)。
当语法元素V小于cMax时,后缀值S为:S = V - (P << R)
后缀串为S的二元化串,长度为R。当语法元素V大于等于cMax时,无后缀串。
(2)定长二元化FL
当语法元素均匀分布时,可采用定长二元化。假设语法元素值为x,且x在[0,Max]间,则直接将x从十进制转为二进制得到二元化串。
一般情况下,不同语法元素间有一定相关性,且相同语法元素自身也具有一定记忆性。因此根据已编码元素进行条件编码可以提高编码性能。这些已编码元素就是当前待编码元素的上下文。
语法元素使用的上下文模型包括两个概率模型变量:最大概率符号MPS和概率状态索引pStateIdx。
MPS表示待编码的Bin最可能出现的符号,取值为1或0;与之对应,LPS为最小概率符号。在CABAC中,依据先验知识为LPS预设了64个概率值,如下表。
在对第一个二元位编码前需要对概率模型参数进行初始化。在H.265/HEVC中,为每个概率模型分配一个初始值V,通过V计算上下文模型初始变量MPS和pStateIdx。
HM中相关代码如下:
/**
- initialize context model with respect to QP and initialization value
.
\param qp input QP value
\param initValue 8 bit initialization value
*/
Void ContextModel::init( Int qp, Int initValue )
{
qp = Clip3(0, 51, qp);
Int slope = (initValue>>4)*5 - 45;
Int offset = ((initValue&15)<<3)-16;
Int initState = min( max( 1, ( ( ( slope * qp ) >> 4 ) + offset ) ), 126 );
UInt mpState = (initState >= 64 );
//!
获得上下文模型初始参数后即可对输入二元符号进行二元算术编码和模型参数更新,实现上下文自适应的编码。概率模型在每个二元位编码后都要更新。
概率状态pStateIdx更新方法为:
若编码的二元符号值等于MPS,则通过查上面的表更新pStateIdx_new=transIdxMps(pStateIdx)
若编码的二元符号值等于LPS,如果pStateIdx为0则互换MPS和LPS,更新pStateIdx;否则不互换MPS和LPS,只更新pStateIdx;pStateIdx更新也是通过查表pStateIdx_new=transIdxLps(pStateIdx)
HM16中相关代码如下:
Void updateLPS ()
{
m_ucState = m_aucNextStateLPS[ m_ucState ];
}
Void updateMPS ()
{
m_ucState = m_aucNextStateMPS[ m_ucState ];
}
从概率模型更新过程可以看出:若当前编码Bin等于MPS,更新后的概率索引值变大,表示下一个符号是MPS的概率变大;若当前编码Bin等于LPS,更新后的概率索引值变小,表示下一个符号是LPS的概率变大;当概率索引值pStateIdx为0时,表示MPS和LPS的概率相同,若再出现LPS符号则MPS和LPS需要交换。
二进制算术编码也是算术编码的一种,只不过算术编码过程中的区间和编码输出等都用二进制表示。当前语法元素完成二进制化后,对每个Bin根据其概率模型进行二进制算术编码。二进制算术编码是基于递归区间划分方式,在递归过程中保存编码区间的长度和区间下限。包括两种编码方式:常规编码和旁路编码。常规编码利用自适应的概率模型进行编码。旁路编码以等概率方式进行,概率状态无需更新。
编码器输入是上下文模型变量和待编码Bin,编码器的状态是当前编码区间长度ivlCurrRange和区间下限ivlLow。
编码流程如下:
算术编码首先需要估计每个符号概率然后进行区间划分。在CABAC中总是估计LPS的概率(P_lps<0.5),且LPS的概率通过64个离散值表示可以通过查表得到概率索引pStateIdx(见上一节)。
区间划分通常需要多位乘法运算(区间长度x概率),CABAC为了降低其复杂度通过查表获得区间长度。
首先计算索引:qRangeIdx=(ivlCurrRange >> 6) & 3
通过查下表LPS对应的子区间长度为:ivlLpsRange = rangeTabLps[pStateIdx] [qRangeIdx]
则MPS的子区间长度为:ivlCurrRange = ivlCurrRange - ivlLpsRange
因为二进制编码只有两个符号0和1,所有区间总是被分为两个子区间,且MPS总是在左侧,LPS总是在右侧。
如果当前Bin等于MPS,则MPS的区间长度R_mps作为下一个符号编码的区间长度R,区间下限L不变;如果当前Bin等于LPS,则LPS的区间长度R_lps作为下一个符号编码的区间长度R,区间下限L变为L+R_mps;编码完一个Bin后根据该Bin值更新上下文模型。
上面区间划分后得到的新区间长度可能不在[256,512]间(HM中区间长度初始化为510),这就需要进行重归一化。
当区间长度小于256时就需要进行重归一化。
设归一化时用区间[0,102)表示[0,1);
1、当R<256&L<256时,区间落在[0,512)间,即[0,0.5)间(0.5二进制表示为0.1)。此时输出0(PutBit(0)),因为此区间内的数都是0.0x(二进制)。
2、当R<256&L>=512时,区间落在[512,1024)间,即[0.5,1)间。此时输出1(PutBit(1)),因为此区间内的数都是0.1x(二进制)。
3、当R<256&L<512时,区间落在[0,512)间或同时跨越[0,512)和[512,1024)。此时既可能是0.01x也可能是0.10x。暂缓输出,保存这个比特进入下一个重归一化循环。
4、当R>=256时,无法判断编码区间,需要通过输入下一个符号对R和L进行更新后继续判断,因此当前符号编码流程结束。由于这个原因,因此在一个符号编码结束后,另一个符号编码开始前,总是256<=R<512。
PutBit(B)用来输出编码符号0或1,从重归一化可以看出:
1、当满足条件1时执行PutBit(0),输出0;
2、当满足条件2时执行PutBit(1),输出1;
3、当满足条件3时,输出可能为“10”或者“01”,因此不能直接输出,走bitsOutstanding++的步骤。在下一次编码符号时,符合情况2,走PutBit(1),此时bitsOutstanding = 1,因此输出“10”。符合情况1,走PutBit(0),此时bitsOutstanding = 1,因此输出“01”;
另外,PutBit(B)不会编码第一个bit。原因是CABAC在初始化的时候,会以[0,1024)表示区间[0,1),而在初始化区间时R=510,L=0,这意味着已经进行了第一次区间选择,区间为[0,0.5),需要输出“0”。PutBit(B)在此阻止这个“0”的输出,这样就能得到正确的算术编码结果了。
WriteBits( B, N ) 表示将值B以N比特写入码流,然后将码流指针往前移动N个位置。
旁路编码不需要对概率模型进行更新,而是采用0和1概率各1/2的方式进行编码。当bypassFlag标志位为1时采用旁路编码。为了使区间划分更加简单,不采用直接对区间长度二等分的方法,而是采用保持编码区间长度不变使区间下限L加倍的方法实现区间划分。随后进行重归一化操作。旁路编码流程如下:
新一代高效视频编码H.265/HEVC:原理、标准与实现
ITU-T H.265 (V5)
https://www.cnblogs.com/TaigaCon/p/5304563.html
感兴趣的请关注微信公众号Video Coding