天天看點

H.265/HEVC率失真優化(RDO)及其HM代碼注解

一、率失真優化(RDO)的目的

選擇一個最小失真的編碼模式可以帶來最好的視訊品質, 然而這往往需要很高的編碼比特率。如何在有限的編碼比特數下,選擇一個失真最小的模式是編碼中的關鍵問題。對于給定編碼單元,上述求極值問題可将其轉化為:在給定碼率的情況下,盡可能降低失真D。這也即是 RDO 的目的,率失真代價函數表述如下:

{Para}opt=arg min{Para}(D+λ•R)

其中,R 和D 分别表示編碼所消耗的比特數位率以及失真程度,{Para}opt表示最佳的編碼參數集,包括模式選擇、運動估計以及QP等,λ 為拉格朗日乘子。

二、HEVC中率失真優化方法

HM 采用拉格朗日優化方法為每個編碼樹單元CTU确定除編碼參數QP之外的所有編碼參數,主要包括CU劃分模式、CU中PU和TU的劃分、PU預測等等。每個CTU采用分級方式确定不同層的編碼參數,步驟如下:

1. 首先,周遊所有CU,按如下公式對CU(CU從64x64到8x8)進行劃分模式進行編碼;

min J J=D(Mode)+ λ(Mode)•R(Mode)

2. 然後,在CTU中周遊所有PU(PU是預測的基本單元)模式和TU(TU是變換的基本單元,TU周遊的最小尺寸為4x4)的

組合,選擇率失真代價值最小的确定為最優模式;

3. 不論幀内還是幀間,都會存在PU的預測,對PU的預測模式也是周遊所有的預測模式,分别計算每個模式對應的率失真代價值,選取最小的率失真代價值對應的預測模式為最優模式;

三、HEVC參考模型HM中初始QP和拉格朗日乘子初始化

在RDO中拉格朗日乘子作為率失真代價函數計算的關鍵參數,每幀的初始拉格朗日乘子會根據Slice的類型和在GOP中的位置,根據下式中每幀的初始QP确定對應的拉格朗日乘子:

模式選擇過程對應拉格朗日乘子:

λ(Mode)=αW**pow(2,(QP-12)/3.0)

運動估計過程對應拉格朗日乘子:

λ(Motion)=pow(λ(Mode),1/2.0)

W為權重因子,I幀為0.57。根據目前Slice是否最為參考圖像,Nb為GOP中B幀的個數(發現LDP配置中P幀在HM中也算作B幀),如果為非參考圖像α為1,如果為非參考幀時:

α= 1.0 - Clip3(0.0,0.5,0.05*Nb)

HM在編碼GOP之前會對每個Slice的拉格朗日乘子作初始化,并在compressGOP函數中的initEncSlice實作初始化

m_pcSliceEncoder->initEncSlice ( pcPic, iPOCLast, pocCurr, iGOPid, pcSlice, isField );
           

如果開啟多QP優化,會對每個周遊的QP初始lambda,我用的HM版本為HM 16.9,不過不同版本的對應函數應該沒有太大變化

// pre-compute lambda and QP values for all possible QP candidates
  for ( Int iDQpIdx = ; iDQpIdx <  * m_pcCfg->getDeltaQpRD() + ; iDQpIdx++ )
  {
    // compute QP value
    dQP = dOrigQP + ((iDQpIdx+)>>)*(iDQpIdx% ? - : );

    // compute lambda value
    Int    NumberBFrames = ( m_pcCfg->getGOPSize() -  );       //計算GOP中B幀個數
    Int    SHIFT_QP = ;
#if FULL_NBIT
    Int    bitdepth_luma_qp_scale =  * (rpcSlice->getSPS()->getBitDepth(CHANNEL_TYPE_LUMA) - );
#else
    Int    bitdepth_luma_qp_scale = ;
#endif
    Double qp_temp = (Double) dQP + bitdepth_luma_qp_scale - SHIFT_QP;
#if FULL_NBIT
    Double qp_temp_orig = (Double) dQP - SHIFT_QP;
#endif
    // Case #1: I or P-slices (key-frame)
    Double dQPFactor = m_pcCfg->getGOPEntry(iGOPid).m_QPFactor;
    if ( eSliceType==I_SLICE )
    {
      if (m_pcCfg->getIntraQpFactor()>= && m_pcCfg->getGOPEntry(iGOPid).m_sliceType != I_SLICE)
      {
        dQPFactor=m_pcCfg->getIntraQpFactor();      //如果不是I幀,根據 cfg 配置,對GOP中不同Slice配置設定不同的拉格朗日乘子權重
      }
      else
      {
          //I幀根據GOP中非參考圖像的個數配置設定拉格朗日乘子權重
        Double dLambda_scale =  - Clip3( , , *(Double)(isField ? NumberBFrames/ : NumberBFrames) );

        dQPFactor=*dLambda_scale;
      }
    }

    dLambda = dQPFactor*pow( , qp_temp/ );

    if ( depth> )      // I幀的 depth 為0
    {
#if FULL_NBIT
        dLambda *= Clip3( , , (qp_temp_orig / ) ); // (j == B_SLICE && p_cur_frm->layer != 0 )
#else
        dLambda *= Clip3( , , (qp_temp / ) ); // (j == B_SLICE && p_cur_frm->layer != 0 )
#endif
    }

    // if hadamard is used in ME process
    if ( !m_pcCfg->getUseHADME() && rpcSlice->getSliceType( ) != I_SLICE )
    {
      dLambda *= ;
    }

#if W0062_RECALCULATE_QP_TO_ALIGN_WITH_LAMBDA
    Double lambdaRef = *pow(, qp_temp/);
    // QP correction due to modified lambda
    Double qpOffset = floor((*log(dLambda/lambdaRef)/log()) +);
    dQP += qpOffset;
#endif

    iQP = max( -rpcSlice->getSPS()->getQpBDOffset(CHANNEL_TYPE_LUMA), min( MAX_QP, (Int) floor( dQP +  ) ) );
//iDQpIdx為周遊QP的索引
    m_vdRdPicLambda[iDQpIdx] = dLambda;
    m_vdRdPicQp    [iDQpIdx] = dQP;
    m_viRdPicQp    [iDQpIdx] = iQP;
  }
  // 如果沒有多QP優化時,初始的QP和拉格朗日乘子選擇索引為0的PQ和lambda
  // obtain dQP = 0 case
  dLambda = m_vdRdPicLambda[];
  dQP     = m_vdRdPicQp    [];
  iQP     = m_viRdPicQp    [];

           

順帶說一下,在編碼CU過程中會遞歸調用xCompressCU函數,并在完成遞歸編碼完子CU後都會比較率失真代價值

#if AMP_ENC_SPEEDUP
          DEBUG_STRING_NEW(sChild)
          if ( !(rpcBestCU->getTotalCost()!=MAX_DOUBLE && rpcBestCU->isInter()) )
          {
            xCompressCU( pcSubBestPartCU, pcSubTempPartCU, uhNextDepth DEBUG_STRING_PASS_INTO(sChild), NUMBER_OF_PART_SIZES );
          }
          else
          {

            xCompressCU( pcSubBestPartCU, pcSubTempPartCU, uhNextDepth DEBUG_STRING_PASS_INTO(sChild), rpcBestCU->getPartitionSize() );
          }
          DEBUG_STRING_APPEND(sTempDebug, sChild)
#else
          xCompressCU( pcSubBestPartCU, pcSubTempPartCU, uhNextDepth );
#endif

          rpcTempCU->copyPartFrom( pcSubBestPartCU, uiPartUnitIdx, uhNextDepth );         // Keep best part data to current temporary data.
          xCopyYuv2Tmp( pcSubBestPartCU->getTotalNumPart()*uiPartUnitIdx, uhNextDepth );
        }
        else
        {
          pcSubBestPartCU->copyToPic( uhNextDepth );
          rpcTempCU->copyPartFrom( pcSubBestPartCU, uiPartUnitIdx, uhNextDepth );
        }
      }

      m_pcRDGoOnSbacCoder->load(m_pppcRDSbacCoder[uhNextDepth][CI_NEXT_BEST]);
      if( !bBoundary )
      {
        m_pcEntropyCoder->resetBits();
        m_pcEntropyCoder->encodeSplitFlag( rpcTempCU, , uiDepth, true );

        rpcTempCU->getTotalBits() += m_pcEntropyCoder->getNumberOfWrittenBits(); // split bits
        rpcTempCU->getTotalBins() += ((TEncBinCABAC *)((TEncSbac*)m_pcEntropyCoder->m_pcEntropyCoderIf)->getEncBinIf())->getBinsCoded();
      }
      rpcTempCU->getTotalCost()  = m_pcRdCost->calcRdCost( rpcTempCU->getTotalBits(), rpcTempCU->getTotalDistortion() );
           

比較率失真代價函數,選擇最優編碼參數