熵編碼

熵編碼部分看的:http://blog.csdn.net/nb_vol_1/article/details/71374859,因博主禁止轉載,請點鏈接去看原博主文章,寫的很清晰明瞭,學習後整理內容如下,方便以後查看。

熵編碼:

消息發生的概率越小,攜帶的信息量就越大。確定的消息沒有信息量,因爲你已經知道要發生什麼了。信息量定義:

由以上性質可知,序列中依賴性越強,不確定性就越弱。

二進制編碼:

變長編碼給大概率分配短碼字,給小概率分配長碼字,目標是使平均長最小。

指數哥倫布編碼:

編碼流程:

解碼流程:記錄0個數爲m,哥倫布編碼階數爲k,第一個1之後的值記爲value,

解碼值= 2m+k次冪) - 2m次冪) + value

零階指數哥倫布編碼:
  假設輸入值是N
1、計算N += 1
  2、把N轉換成二進制串bins,計算二進制串的長度,假設是M,那麼在bins的前面添加M-10
 假設輸入值N對應的二進制串的長度是len,那麼零階哥倫布碼的長度是2*len-1
哥倫布碼golomb = 前綴prefix + 後綴suffix
    前綴prefix : len - 10
    後綴suffix : (N+1)對應的二進制串

HEVC中的熵編碼:

HEVC的熵編碼使用了兩種算術編碼:CABACCAVLCCAVLC主要用於編碼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的二進制。
}

將哥倫布碼寫入比特流:

寫入比特流中的流程:

    1TComOutputBitstream包含兩個部分:緩衝區和比特流
    2、比特流就是已經處理完成的數據,存放在一個vector
    3、緩衝區暫存還沒有寫入比特流中的數據,TComOutputBitstream使用一個uchar類型的數據(8 bit)作爲緩存區,因爲很多時候寫入的數據長度只有若干比特,不能直接寫入比特流中,需要等到緩衝區滿(達到8bit),才寫入
    3m_num_held_bits表示緩衝區中已有的比特數
    4num_total_bits表示寫入數據之後,緩衝區中總的比特數(可能會溢出,後面會解決這個問題)
    5next_num_held_bits有兩種意思:
        1)如果緩衝區沒有溢出,那麼它表示緩衝區中總的比特數(已有+新增)
        2)如果緩衝區溢出,那麼它表示數據佔用完緩衝區後還需要的比特數,只能存放數據的一部分,剩下那部分需要等緩衝區的數據寫入比特流之後再存放
    6next_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中,除了參數集、SEIslice頭部之外,其餘的所有數據都使用CABAC來進行熵編碼。

     CABAC有三個步驟:

   1、初始化,構建上下文概率模型
     2、根據上下文概率模型獲取語法元素的概率,對語法元素進行熵編碼
   3、根據編碼結果更新上下文概率模型

1.初始化

 

    初始化上下文模型,就是指初始化和上下文模型有關的兩個變量:MPSδMPS是最大概率符號,它表示待編碼符號可能出現符號,對於二進制算術編碼來說,MPS0或者1,相反,LPS表示待編碼符號不可能出現的符號,對於二進制算術編碼來說,LPS也是01δ表示概率的狀態索引,它的值與LPS的概率值是相對應的,δ值隨着LPS概率的更新而變化。MPSδ唯一的確定上下文模型的狀態,以及後續如何對上下模型進行更新。
    計算MPSδ需要一個初始值initValueinitValue的值與slice的類型、初始量化參數有關。HEVC爲每一個語法元素都定義了不同的initValue,爲了方便,可以通過slice的類型和量化參數查表來得到initValue的值。initValue表示起始概率值。

    我們以MPSδ來表示上下文概率模型,或者說,上下文概率模型的主要參數是MPSδ

 

初始化的入口函數

    它的主要功能是:獲取QPslice的類型,然後調用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δ

    在下面的函數中,slopeoffsetinitState都是中間變量,mpState表示MPSm_ucState表示δ

獲取概率進行熵編碼

    語法元素對應的上下文模型初始化完成之後,開始進行二進制算術編碼。二進制算術編碼是對語法元素對應的二進制比特串進行算術編碼。二進制算術編碼包含兩種方式:旁路方式和常規方式。在旁路編碼方式中,二進制串的符號的概率是相同的,也不需要更新上下文概率模型;在常規方式中,二進制串中符號的概率可以由上下文模型中得到,對每一個符號編碼完成之後都需要對上下文模型進行更新。使用常規方式還是旁路方式是由語法元素決定的,HEVC文檔指明瞭哪些語法元素使用旁路方式哪些語法元素使用常規方式。

    在對語法元素進行編碼之前需要對它進行二進制化。

 

二進制化

    理論上,HEVC的二進制方法有:
    1、一元碼 
    2、截斷一元碼 
    3、指數哥倫布碼
    4、截斷萊斯碼
    5、定長碼

    由於在實際中,由於很多語法元素的值都是0或者1,因此,很多語法元素不需要二進制化就可以直接進行編碼,只有少部分纔會進行二進制化。例如mvp-indexdelta-qp等語法元素使用截斷一元碼進行二進制化;mvd等語法元素使用指數哥倫布來進行二進制化。

一元碼

    假設語法的元素值是x,那麼它對應的一元碼由前綴x1和後綴一個0構成:11...10。假設x=5,那麼它的一元碼是111110

Void 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,那麼它的一元碼由起始的x1和最後一個0組成。
    2、給定一個最大的可能值cMaxbins的長度不能超過cMax,如果超過,那麼就對bins的尾部進行截斷
    3、例如,給定一個語法元素的值是5,cMax4
        15對應的一元碼是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階指數哥倫布碼

    它由前綴和後綴構成,前綴和一元碼有點相似,由p1和一個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,需要互換MPSLPS
    2、更新編碼區間。主要是移動編碼區間的下界low或者重新計算區間的長度length。首先計算LPS的子區間的長度length_lps=rangeLPS[δ][(length?6)&3],對應的MPS的子區間的長度length_mps=length-length_lps;如果比特符號xMPS,那麼區間下界low不變,length更新爲length_mps;否則,low=low+length_mpslength更新爲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、如果二進制符號不等於MPSlow=low+length_mpslength更新爲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();
}




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章