您的位置:首页 > 其它

熵编码

2018-03-14 11:59 393 查看
熵编码部分看的:http://blog.csdn.net/nb_vol_1/article/details/71374859,因博主禁止转载,请点链接去看原博主文章,写的很清晰明了,学习后整理内容如下,方便以后查看。

熵编码:
消息发生的概率越小,携带的信息量就越大。确定的消息没有信息量,因为你已经知道要发生什么了。信息量定义:



由以上性质可知,序列中依赖性越强,不确定性就越弱。二进制编码:变长编码给大概率分配短码字,给小概率分配长码字,目标是使平均长最小。指数哥伦布编码:
编码流程:



解码流程:记录0个数为m,哥伦布编码阶数为k,第一个1之后的值记为value,解码值= (2的m+k次幂) - (2的m次幂) + value零阶指数哥伦布编码:
  假设输入值是N:
1、计算N += 1
  2、把N转换成二进制串bins,计算二进制串的长度,假设是M,那么在bins的前面添加M-1个0
 假设输入值N对应的二进制串的长度是len,那么零阶哥伦布码的长度是2*len-1
哥伦布码golomb = 前缀prefix + 后缀suffix
    前缀prefix : len - 1个0
    后缀suffix : (N+1)对应的二进制串
HEVC中的熵编码:HEVC的熵编码使用了两种算术编码:CABAC和CAVLC。CAVLC主要用于编码SEI、参数集、片头等,剩下的所有数据和语法元素均使用CABAC来编码。CAVLC中的编码方法:1、零阶无符号哥伦布指数编码
 2、零阶有符号指数哥伦布编码
   3、不编码直接写入若干比特
   4、不编码直接写入一个比特//无符号指数哥伦布编码
Void SyntaxElementWriter::xWriteUvlc ( UInt uiCode )
{
UInt uiLength = 1;//哥伦布码的长度
UInt uiTemp = ++uiCode;//执行UITemp= UICode + 1

assert ( uiTemp );

while( 1 != uiTemp )//计算长度,若uiTemp长度为len,则uiLength=2*len-1
{
uiTemp >>= 1;
uiLength += 2;
}
// Take care of cases where uiLength > 32
m_pcBitIf->write( 0, uiLength >> 1);//写入前缀,len-1个零
m_pcBitIf->write( uiCode, (uiLength+1) >> 1);//写入后缀,uiTemp的二进制。
}将哥伦布码写入比特流:写入比特流中的流程:    1、TComOutputBitstream包含两个部分:缓冲区和比特流
    2、比特流就是已经处理完成的数据,存放在一个vector中
    3、缓冲区暂存还没有写入比特流中的数据,TComOutputBitstream使用一个uchar类型的数据(8 bit)作为缓存区,因为很多时候写入的数据长度只有若干比特,不能直接写入比特流中,需要等到缓冲区满(达到8bit),才写入
    3、m_num_held_bits表示缓冲区中已有的比特数
    4、num_total_bits表示写入数据之后,缓冲区中总的比特数(可能会溢出,后面会解决这个问题)
    5、next_num_held_bits有两种意思:
        (1)如果缓冲区没有溢出,那么它表示缓冲区中总的比特数(已有+新增)
        (2)如果缓冲区溢出,那么它表示数据占用完缓冲区后还需要的比特数,只能存放数据的一部分,剩下那部分需要等缓冲区的数据写入比特流之后再存放
    6、next_held_bits是格式化之后的数据
        (1)如果缓冲区不溢出,那它表示将要写入缓冲区中的数据
        (2)如果缓冲区溢出,那它表示将一部分数据写入缓冲区之后,剩下的那部分数据
    7、判断num_total_bits是否大于8,即判断新增数据之后,缓冲区是否会溢出
    8、如果缓冲区不溢出,那么把数据写入缓冲区中,然后返回
    9、如果缓冲区溢出,那么先写一部分数据到缓冲区中,然后把缓冲区写入比特流中,清空缓冲区,继续把剩余的数据写入缓冲区中Void TComOutputBitstream::write ( UInt uiBits, UInt uiNumberOfBits )
{
assert( uiNumberOfBits <= 32 );
assert( uiNumberOfBits == 32 || (uiBits & (~0 << uiNumberOfBits)) == 0 );

/* any modulo 8 remainder of num_total_bits cannot be written this time,
* and will be held until next time. */
//m_num_held_bits缓冲区中已有的比特数
//num_total_bits写入uiBits之后,缓冲区中的总比特数。
UInt num_total_bits = uiNumberOfBits + m_num_held_bits;
//next_num_held_bits表示缓冲区去中使用的比特数
UInt next_num_held_bits = num_total_bits % 8;

/* form a byte aligned word (write_bits), by concatenating any held bits
* with the new bits, discarding the bits that will form the next_held_bits.
* eg: H = held bits, V = n new bits /---- next_held_bits
* len(H)=7, len(V)=1: ... ---- HHHH HHHV . 0000 0000, next_num_held_bits=0
* len(H)=7, len(V)=2: ... ---- HHHH HHHV . V000 0000, next_num_held_bits=1
* if total_bits < 8, the value of v_ is not used */
//将数据执行移位操作,放入next_held_bits中
UChar next_held_bits = uiBits << (8 - next_num_held_bits);

//判断比特数是否大于8,若大于,表示缓冲区满,要先写入比特流中
if (!(num_total_bits >> 3))
{//不大于8,写入缓冲区尾部
/* insufficient bits accumulated to write out, append new_held_bits to
* current held_bits */
/* NB, this requires that v only contains 0 in bit positions {31..n} */
m_held_bits |= next_held_bits;
m_num_held_bits = next_num_held_bits;
return;
}

/* topword serves to justify held_bits to align with the msb of uiBits */
//写入部分数据
UInt topword = (uiNumberOfBits - next_num_held_bits) & ~((1 << 3) -1);
UInt write_bits = (m_held_bits << topword) | (uiBits >> next_num_held_bits);
//判断num_total_bits的长度:32,24,16,8
switch (num_total_bits >> 3)
{
case 4: m_fifo->push_back(write_bits >> 24);
case 3: m_fifo->push_back(write_bits >> 16);
case 2: m_fifo->push_back(write_bits >> 8);
case 1: m_fifo->push_back(write_bits);
}
//更新计数参数
m_held_bits = next_held_bits;
m_num_held_bits = next_num_held_bits;
}CABAC:CABAC(上下文自适应的二进制算术编码)基于算术编码,在HEVC中,除了参数集、SEI和slice头部之外,其余的所有数据都使用CABAC来进行熵编码。     CABAC有三个步骤:   1、初始化,构建上下文概率模型
     2、根据上下文概率模型获取语法元素的概率,对语法元素进行熵编码
   3、根据编码结果更新上下文概率模型1.初始化     初始化上下文模型,就是指初始化和上下文模型有关的两个变量:MPS和δ。MPS是最大概率符号,它表示待编码符号可能出现符号,对于二进制算术编码来说,MPS是0或者1,相反,LPS表示待编码符号不可能出现的符号,对于二进制算术编码来说,LPS也是0或1;δ表示概率的状态索引,它的值与LPS的概率值是相对应的,δ值随着LPS概率的更新而变化。MPS和δ唯一的确定上下文模型的状态,以及后续如何对上下模型进行更新。
    计算MPS和δ需要一个初始值initValue,initValue的值与slice的类型、初始量化参数有关。HEVC为每一个语法元素都定义了不同的initValue,为了方便,可以通过slice的类型和量化参数查表来得到initValue的值。initValue表示起始概率值。    我们以MPS和δ来表示上下文概率模型,或者说,上下文概率模型的主要参数是MPS和δ。 初始化的入口函数    它的主要功能是:获取QP和slice的类型,然后调用ContextModel3DBuffer::initBuffer进行上下文概率模型的初始化。

Void TEncSbac::resetEntropy           ()
{
Int  iQp              = m_pcSlice->getSliceQp();
SliceType eSliceType  = m_pcSlice->getSliceType();

Int  encCABACTableIdx = m_pcSlice->getPPS()->getEncCABACTableIdx();
//帧内,Bslice,
if (!m_pcSlice->isIntra() && (encCABACTableIdx==B_SLICE || encCABACTableIdx==P_SLICE) && m_pcSlice->getPPS()->getCabacInitPresentFlag())
{
eSliceType = (SliceType) encCABACTableIdx;
}
//初始化各个模型的缓存
m_cCUSplitFlagSCModel.initBuffer       ( eSliceType, iQp, (UChar*)INIT_SPLIT_FLAG );
//split标志的上下文
m_cCUSkipFlagSCModel.initBuffer        ( eSliceType, iQp, (UChar*)INIT_SKIP_FLAG );
//skip标志的上下文
//merge标志、索引上下文
m_cCUMergeFlagExtSCModel.initBuffer    ( eSliceType, iQp, (UChar*)INIT_MERGE_FLAG_EXT);
m_cCUMergeIdxExtSCModel.initBuffer     ( eSliceType, iQp, (UChar*)INIT_MERGE_IDX_EXT);
//PartSize上下文
m_cCUPartSizeSCModel.initBuffer        ( eSliceType, iQp, (UChar*)INIT_PART_SIZE );
//预测上下文
m_cCUPredModeSCModel.initBuffer        ( eSliceType, iQp, (UChar*)INIT_PRED_MODE );
//帧内预测
m_cCUIntraPredSCModel.initBuffer       ( eSliceType, iQp, (UChar*)INIT_INTRA_PRED_MODE );
//色度预测
m_cCUChromaPredSCModel.initBuffer      ( eSliceType, iQp, (UChar*)INIT_CHROMA_PRED_MODE );
//帧间角度
m_cCUInterDirSCModel.initBuffer        ( eSliceType, iQp, (UChar*)INIT_INTER_DIR );
//mv残差
m_cCUMvdSCModel.initBuffer             ( eSliceType, iQp, (UChar*)INIT_MVD );
m_cCURefPicSCModel.initBuffer          ( eSliceType, iQp, (UChar*)INIT_REF_PIC );
m_cCUDeltaQpSCModel.initBuffer         ( eSliceType, iQp, (UChar*)INIT_DQP );
m_cCUQtCbfSCModel.initBuffer           ( eSliceType, iQp, (UChar*)INIT_QT_CBF );
m_cCUQtRootCbfSCModel.initBuffer       ( eSliceType, iQp, (UChar*)INIT_QT_ROOT_CBF );
//符号上下文
m_cCUSigCoeffGroupSCModel.initBuffer   ( eSliceType, iQp, (UChar*)INIT_SIG_CG_FLAG );
m_cCUSigSCModel.initBuffer             ( eSliceType, iQp, (UChar*)INIT_SIG_FLAG );
//最后一个X
m_cCuCtxLastX.initBuffer               ( eSliceType, iQp, (UChar*)INIT_LAST );
//最后一个Y
m_cCuCtxLastY.initBuffer               ( eSliceType, iQp, (UChar*)INIT_LAST );
m_cCUOneSCModel.initBuffer             ( eSliceType, iQp, (UChar*)INIT_ONE_FLAG );
m_cCUAbsSCModel.initBuffer             ( eSliceType, iQp, (UChar*)INIT_ABS_FLAG );
m_cMVPIdxSCModel.initBuffer            ( eSliceType, iQp, (UChar*)INIT_MVP_IDX );
m_cCUTransSubdivFlagSCModel.initBuffer ( eSliceType, iQp, (UChar*)INIT_TRANS_SUBDIV_FLAG );
m_cSaoMergeSCModel.initBuffer      ( eSliceType, iQp, (UChar*)INIT_SAO_MERGE_FLAG );
m_cSaoTypeIdxSCModel.initBuffer        ( eSliceType, iQp, (UChar*)INIT_SAO_TYPE_IDX );
m_cTransformSkipSCModel.initBuffer     ( eSliceType, iQp, (UChar*)INIT_TRANSFORMSKIP_FLAG );
m_CUTransquantBypassFlagSCModel.initBuffer( eSliceType, iQp, (UChar*)INIT_CU_TRANSQUANT_BYPASS_FLAG );
// new structure
m_uiLastQp = iQp;
//二值化的操作
m_pcBinIf->start();

return;
}
根据initValue和量化参数计算MPS和δ    在下面的函数中,slope、offset、initState都是中间变量,mpState表示MPS,m_ucState表示δ获取概率进行熵编码    语法元素对应的上下文模型初始化完成之后,开始进行二进制算术编码。二进制算术编码是对语法元素对应的二进制比特串进行算术编码。二进制算术编码包含两种方式:旁路方式和常规方式。在旁路编码方式中,二进制串的符号的概率是相同的,也不需要更新上下文概率模型;在常规方式中,二进制串中符号的概率可以由上下文模型中得到,对每一个符号编码完成之后都需要对上下文模型进行更新。使用常规方式还是旁路方式是由语法元素决定的,HEVC文档指明了哪些语法元素使用旁路方式哪些语法元素使用常规方式。    在对语法元素进行编码之前需要对它进行二进制化。 二进制化    理论上,HEVC的二进制方法有:
    1、一元码 
    2、截断一元码 
    3、指数哥伦布码
    4、截断莱斯码
    5、定长码    由于在实际中,由于很多语法元素的值都是0或者1,因此,很多语法元素不需要二进制化就可以直接进行编码,只有少部分才会进行二进制化。例如mvp-index、delta-qp等语法元素使用截断一元码进行二进制化;mvd等语法元素使用指数哥伦布来进行二进制化。一元码    假设语法的元素值是x,那么它对应的一元码由前缀x个1和后缀一个0构成:11...10。假设x=5,那么它的一元码是111110Void TEncSbac::xWriteUnarySymbol( UInt uiSymbol, ContextModel* pcSCModel, Int iOffset )
{
m_pcBinIf->encodeBin( uiSymbol ? 1 : 0, pcSCModel[0] );

if( 0 == uiSymbol)
{
return;
}

while( uiSymbol-- )
{
m_pcBinIf->encodeBin( uiSymbol ? 1 : 0, pcSCModel[ iOffset ] );
}

return;
}截断一元码:
    1、把语法元素之转换成一元码,假如语法元素值是x,那么它的一元码由起始的x个1和最后一个0组成。
    2、给定一个最大的可能值cMax,bins的长度不能超过cMax,如果超过,那么就对bins的尾部进行截断
    3、例如,给定一个语法元素的值是5,cMax是4
        (1)5对应的一元码是111110
        (2)由于一元码的长度大于cMax,因此需要对它进行截断
        (3)截断之后为1111,因此5对应的截断一元码是1111(当cMax等于4时)Void TEncSbac::xWriteUnaryMaxSymbol( UInt uiSymbol, ContextModel* pcSCModel, Int iOffset, UInt uiMaxSymbol )
{
if (uiMaxSymbol == 0)
{
return;
}
// 先编码第一个编码一元码的第一个比特  
m_pcBinIf->encodeBin( uiSymbol ? 1 : 0, pcSCModel[ 0 ] );

if ( uiSymbol == 0 )
{
return;
}
// 判断是否需要截断
Bool bCodeLast = ( uiMaxSymbol > uiSymbol );
// 编码x个1(一元码)
while( --uiSymbol )
{
m_pcBinIf->encodeBin( 1, pcSCModel[ iOffset ] );
}
// 不需要截断,直接在后面添加0
if( bCodeLast )
{
m_pcBinIf->encodeBin( 0, pcSCModel[ iOffset ] );
}

return;
}k阶指数哥伦布码    它由前缀和后缀构成,前缀和一元码有点相似,由p个1和一个0构成,其中p=log2[x/(2^k)+1];后缀是q对应的二进制数,其中q=x+2^k*(1-2^p);HEVC中最常用的是1阶指数哥伦布码。//指数哥伦布编码
Void TEncSbac::xWriteEpExGolomb( UInt uiSymbol, UInt uiCount )
{//uiSymbol是元素值,uiCount 是阶数
UInt bins = 0;
Int numBins = 0;

while( uiSymbol >= (UInt)(1<<uiCount) )
{//去掉后uiCount位
bins = 2 * bins + 1;
numBins++;//比特位数
uiSymbol -= 1 << uiCount;
uiCount ++;
}
bins = 2 * bins + 0;
numBins++;

bins = (bins << uiCount) | uiSymbol;
numBins += uiCount;

assert( numBins <= 32 );
m_pcBinIf->encodeBinsEP( bins, numBins );
} 1、更新上下文模型。上下文模型的更新主要是对上下文模型的δ和MPS变量进行更新。如果bin等于MPS,那么δ_new=transMPS[δ];否则δ_new=transLPS[δ],而且如果δ等于0,需要互换MPS和LPS
    2、更新编码区间。主要是移动编码区间的下界low或者重新计算区间的长度length。首先计算LPS的子区间的长度length_lps=rangeLPS[δ][(length?6)&3],对应的MPS的子区间的长度length_mps=length-length_lps;如果比特符号x等MPS,那么区间下界low不变,length更新为length_mps;否则,low=low+length_mps,length更新为length_lps。encodeBin中的编码流程: 编码流程:
    1、首先计算LPS对应的子区间的长度,通过查表得到:length_lps=rangeLPS[δ][(length?6)&3],对应的代码是:UInt  uiLPS   = TComCABACTables::sm_aucLPSTable[ rcCtxModel.getState() ][ ( m_uiRange >> 6 ) & 3 ];  其中rcCtxModel.getState()返回δ
    2、计算MPS对应的子区间的长度:m_uiRange    -= uiLPS;
    3、如果二进制符号不等于MPS,low=low+length_mps,length更新为length_lps    4、如果二进制符号等于MPS,下界low不变,length更新为length_mps//1.该比特对应的上下文模型设置为已编码
//2.计算LPS(最低概率值)
//3.计算新的范围
//4.根据比特值是否和MPS相等,更新上下文模式(最大概率值)。
Void TEncBinCABAC::encodeBin( UInt binValue, ContextModel &rcCtxModel )
{
//调试的打印信息
{
DTRACE_CABAC_VL( g_nSymbolCounter++ )
DTRACE_CABAC_T( "\tstate=" )
DTRACE_CABAC_V( ( rcCtxModel.getState() << 1 ) + rcCtxModel.getMps() )
DTRACE_CABAC_T( "\tsymbol=" )
DTRACE_CABAC_V( binValue )
DTRACE_CABAC_T( "\n" )
}

m_uiBinsCoded += m_binCountIncrement;//比特计数
//设置已编码标志
rcCtxModel.setBinsCoded( 1 );
//查表获取LPS对应的子区间长度
UInt uiLPS = TComCABACTables::sm_aucLPSTable[ rcCtxModel.getState() ][ ( m_uiRange >> 6 ) & 3 ];
//m_uiRange表示MPS对应的子区间长度
m_uiRange -= uiLPS;
//如果二进制符号不等于MPS
if( binValue != rcCtxModel.getMps() )
{//binVal != valMPS 索引概率值将减小,即LPS的概率增大
//numBit用于归一化
Int numBits = TComCABACTables::sm_aucRenormTable[ uiLPS >> 3 ];
//更新low=low+length_mps,使用<<numBits的目的是重归一化
m_uiLow = ( m_uiLow + m_uiRange ) << numBits;
m_uiRange = uiLPS << numBits;
rcCtxModel.updateLPS();//用LPS对δ进行更新

m_bitsLeft -= numBits;
}
else//binVal == valMPS,概率索引值将增大,即LPS的概率减小
{//下界low不变,length更新为length_mps(m_uiRange已经等于length_mps)
rcCtxModel.updateMPS();//更新δ
if ( m_uiRange >= 256 )//大于256不用归一化
{
return;
}

m_uiLow <<= 1;
m_uiRange <<= 1;
m_bitsLeft--;
}
// 尝试写到比特流中,先判断当前缓冲区中的空闲空间是否足够,不足的话就写到比特流中,腾出空间  
testAndWriteOut();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  HEVC CABAC 熵编码