H.266/VVC代碼學習56:率失真優化

躲避疫情,在家也學點東西吧,解鎖一波新地圖——VVC的RDO。

所謂率失真優化:在給定編碼比特率的情況下,如何使失真最小:J(λ) = min[ D + λR ]。其中:
率:信息數量的多少(比特率R);
失真:接收短信號與源信號的差異程度(失真D);
λ:拉格朗日參數,只與量化參數有關;
幾何意義:一條斜率爲λ的直線,與率失真曲線的切點即最優解。
目的:得到性價比最高的編碼參數,如劃分方式、預測模式、變換方式。

如需瞭解更多具體的理論方面的知識,可參考:率失真優化理論

1 lambda的確定及走向

之前的學習見H.266/VVC代碼學習6:VTM4.0中lambda與QP的關係

直接看VTM7.0中的代碼:

重要成員變量含義

  double                  m_dLambda;//最初始的lambda
  double                  m_DistScaleUnadjusted;//lambda的倒數,同時有移位(左移了15位)
  double                  m_DistScale;//lambda的倒數,同時有移位(左移了15位),後面可能有調整
  double                  m_dLambdaMotionSAD[2 /* 運動信息的lambda 0=standard, 1=for transquant bypass when mixed-lossless cost evaluation enabled*/];

重要相關函數含義

double RdCost::calcRdCost( 
	uint64_t fracBits,		//比特數R
	Distortion distortion,	//失真D
	bool useUnadjustedLambda//是否調整lambda
)
{
  if( m_costMode == COST_LOSSLESS_CODING && 0 != distortion )//如果無損編碼情況下有失真,就返回最大值
  {
    return MAX_DOUBLE;
  }
  return ( useUnadjustedLambda ? m_DistScaleUnadjusted : m_DistScale ) * double( distortion ) + double( fracBits );//率失真計算

}
template<typename T, size_t N>
uint32_t updateCandList
(
	T uiMode,								//新的要參與對比的模式
	double uiCost,							//新的要參與對比的模式的代價
	static_vector<T, N>& candModeList,		//之前留下的模式
	static_vector<double, N>& candCostList	//之前留下的模式的代價
  , size_t uiFastCandNum = N,				//最後留下幾個模式(長度)
	int* iserttPos = nullptr
)
{
  CHECK( std::min( uiFastCandNum, candModeList.size() ) != std::min( uiFastCandNum, candCostList.size() ), "Sizes do not match!" );
  CHECK( uiFastCandNum > candModeList.capacity(), "The vector is to small to hold all the candidates!" );

  size_t i;
  size_t shift = 0;
  size_t currSize = std::min( uiFastCandNum, candCostList.size() );//確定最後有多少個模式

  while( shift < uiFastCandNum && shift < currSize && uiCost < candCostList[currSize - 1 - shift] )//找到當前模式排在倒數第幾個索引
  {
    shift++;//從大的往小的找,排在倒數第幾個位置
  }

  if( candModeList.size() >= uiFastCandNum && shift != 0 )//更新列表的條件:超出總列表範圍(需要往外剔除),並且,新加入的幹掉了其中的一部分
  {
    for( i = 1; i < shift; i++ )//被幹掉的都往後移一位
    {
      candModeList[currSize - i] = candModeList[currSize - 1 - i];
      candCostList[currSize - i] = candCostList[currSize - 1 - i];
    }
    candModeList[currSize - shift] = uiMode;//把當前這個插進入
    candCostList[currSize - shift] = uiCost;
    if (iserttPos != nullptr)
    {
      *iserttPos = int(currSize - shift);
    }
    return 1;//列表有所更新,就返回1
  }
  else if( currSize < uiFastCandNum )//否則(列表沒填滿):直接插入
  {
    candModeList.insert( candModeList.end() - shift, uiMode );
    candCostList.insert( candCostList.end() - shift, uiCost );
    if (iserttPos != nullptr)
    {
      *iserttPos = int(candModeList.size() - shift - 1);
    }
    return 1;//列表有所更新,就返回1
  }
  if (iserttPos != nullptr)
  {
    *iserttPos = -1;
  }
  return 0;
}

#endif

1.1 updataLambda

void EncCu::updateLambda (Slice* slice, const int dQP,
 #if WCG_EXT && ER_CHROMA_QP_WCG_PPS
                          const bool useWCGChromaControl,
 #endif
                          const bool updateRdCostLambda)
{
#if WCG_EXT && ER_CHROMA_QP_WCG_PPS
  if (useWCGChromaControl)//如果使用了WCG,比較容易,計算後返回即可
  {
    const double lambda = m_pcSliceEncoder->initializeLambda (slice, m_pcSliceEncoder->getGopId(), slice->getSliceQp(), (double)dQP);//獲得lambda
    const int clippedQP = Clip3 (-slice->getSPS()->getQpBDOffset (CHANNEL_TYPE_LUMA), MAX_QP, dQP);

    m_pcSliceEncoder->setUpLambda (slice, lambda, clippedQP);//加載lambda
    return;
  }
#endif
  int iQP = dQP;
  const double oldQP     = (double)slice->getSliceQpBase();
#if ENABLE_QPA_SUB_CTU
  const double oldLambda = (m_pcEncCfg->getUsePerceptQPA() && !m_pcEncCfg->getUseRateCtrl() && slice->getPPS()->getUseDQP()) ? slice->getLambdas()[0] :
                           m_pcSliceEncoder->calculateLambda (slice, m_pcSliceEncoder->getGopId(), oldQP, oldQP, iQP);//計算
#else
  const double oldLambda = m_pcSliceEncoder->calculateLambda (slice, m_pcSliceEncoder->getGopId(), oldQP, oldQP, iQP);
#endif
  const double newLambda = oldLambda * pow (2.0, ((double)dQP - oldQP) / 3.0);//新的lambda是根據QP的變化改動的
#if RDOQ_CHROMA_LAMBDA
  const double chromaLambda = newLambda / m_pcRdCost->getChromaWeight();
  const double lambdaArray[MAX_NUM_COMPONENT] = {newLambda, chromaLambda, chromaLambda};//根據新計算的lambda更新三個分量
  m_pcTrQuant->setLambdas (lambdaArray);//設置進去三個分量的lambda
#else
  m_pcTrQuant->setLambda (newLambda);
#endif
  if (updateRdCostLambda)//如果要更新RDO的lambda,則重新設置lambda
  {
    m_pcRdCost->setLambda (newLambda, slice->getSPS()->getBitDepths());
  }
}
#endif // SHARP_LUMA_DELTA_QP || ENABLE_QPA_SUB_CTU

1.2 calculateLambda

double EncSlice::calculateLambda( const Slice*     slice, //								要處理的幀
                                  const int        GOPid, // entry in the GOP table			第幾個GOP
                                  const double     refQP, // initial slice-level QP			幀的QP
                                  const double     dQP,   // initial double-precision QP	浮點型QP
                                        int       &iQP )  // returned integer QP.			整型QP
{
  double dLambda = initializeLambda (slice, GOPid, int (refQP + 0.5), dQP);//得到lambda
  iQP = Clip3 (-slice->getSPS()->getQpBDOffset (CHANNEL_TYPE_LUMA), MAX_QP, int (dQP + 0.5));//得到QP

  if( m_pcCfg->getDepQuantEnabledFlag() )//如果應用DQ,要有少量的改變
  {
    dLambda *= pow( 2.0, 0.25/3.0 ); // slight lambda adjustment for dependent quantization (due to different slope of quantizer)
  }

  // NOTE: the lambda modifiers that are sometimes applied later might be best always applied in here.
  return dLambda;
}

1.3 initializeLambda

double EncSlice::initializeLambda(const Slice* slice, const int GOPid, const int refQP, const double dQP)
{
	/*************************** 1.初始化過程 *************************/
  const int   bitDepthLuma  = slice->getSPS()->getBitDepth(CHANNEL_TYPE_LUMA);//亮度的比特深度
  const int   bitDepthShift = 6 * (bitDepthLuma - 8 - DISTORTION_PRECISION_ADJUSTMENT(bitDepthLuma)) - 12;//比特深度偏移,VVC基本都是0
  const int   numberBFrames = m_pcCfg->getGOPSize() - 1;//B幀數量
  const SliceType sliceType = slice->getSliceType();//獲取該幀類型:I or B or P
#if X0038_LAMBDA_FROM_QP_CAPABILITY
  const int      temporalId = m_pcCfg->getGOPEntry(GOPid).m_temporalId;//當前幀的層級
  const std::vector<double> &intraLambdaModifiers = m_pcCfg->getIntraLambdaModifier();//?????
#endif

  double dQPFactor = m_pcCfg->getGOPEntry(GOPid).m_QPFactor;//獲取QP

  double dLambda, lambdaModifier;//dlambda就是要返回的計算出的lambda的值

  /*************** 2.對I幀的操作,設出一個值爲0.57 ***************/
  if (sliceType == I_SLICE)
  {
    if ((m_pcCfg->getIntraQpFactor() >= 0.0) && (m_pcCfg->getGOPEntry(GOPid).m_sliceType != I_SLICE))
    {
      dQPFactor = m_pcCfg->getIntraQpFactor();
    }
    else
    {
#if X0038_LAMBDA_FROM_QP_CAPABILITY
      if (m_pcCfg->getLambdaFromQPEnable())//如果lambda從QP得出,則固定爲0.57
      {
        dQPFactor = 0.57;
      }
      else//否則比較麻煩:0.57減去一個和B幀數量相關的一個值,但範圍在0.28到0.57之間
#endif
      dQPFactor = 0.57 * (1.0 - Clip3(0.0, 0.5, 0.05 * double (slice->getPic()->fieldPic ? numberBFrames >> 1 : numberBFrames)));
    }
  }
#if X0038_LAMBDA_FROM_QP_CAPABILITY
  else if (m_pcCfg->getLambdaFromQPEnable())
  {
    dQPFactor = 0.57;
  }
#endif

  /***************** 3.先通過0.57計算得到一個dlambda = 0.57 * 2 ^ (QP/3) *********************/
  dLambda = dQPFactor * pow(2.0, (dQP + bitDepthShift) / 3.0);//0.57 * 2 ^ (QP/3)
  
  /***************** 4.根據不同的情況調整lambda的數值 *********************/
#if X0038_LAMBDA_FROM_QP_CAPABILITY
  if (slice->getDepth() > 0 && !m_pcCfg->getLambdaFromQPEnable())//1.如果lambda不是從QP得到的,需要再乘上一個2~4之間的值
#else
  if (slice->getDepth() > 0)
#endif
  {
    dLambda *= Clip3(2.0, 4.0, ((refQP + bitDepthShift) / 6.0));
  }
  // if Hadamard is used in motion estimation process
  if (!m_pcCfg->getUseHADME() && (sliceType != I_SLICE))//2.如果不是I幀並且可以使用哈達瑪變換,需要再乘上一個0.95
  {
    dLambda *= 0.95;
  }
#if X0038_LAMBDA_FROM_QP_CAPABILITY
  if ((sliceType != I_SLICE) || intraLambdaModifiers.empty())
  {
    lambdaModifier = m_pcCfg->getLambdaModifier(temporalId);
  }
  else
  {
    lambdaModifier = intraLambdaModifiers[temporalId < intraLambdaModifiers.size() ? temporalId : intraLambdaModifiers.size() - 1];
  }
  dLambda *= lambdaModifier;//3.乘上一個調整值
#endif

  return dLambda;//返回這一個計算出的lambda
}


1.4 setUpLambda

void EncSlice::setUpLambda( Slice* slice, const double dLambda, int iQP)
{
  // store lambda
  m_pcRdCost ->setLambda( dLambda, slice->getSPS()->getBitDepths() );

  /******************************************* 1.對於率失真優化 ************************************************/

  //在RdCost中,只有一個lambda,因爲亮度和色度位沒有分開,而是對色度的失真進行加權。
  double dLambdas[MAX_NUM_COMPONENT] = { dLambda };//設置了一個臨時變量,默認值爲dLambda,這個臨時變量將在處理後應用於RDOQ和SAO

  /* 亮度分量直接確定爲dLambda,色度分量將在下面的for循環中進行處理 */
  for( uint32_t compIdx = 1; compIdx < MAX_NUM_COMPONENT; compIdx++ )
  {
    const ComponentID compID = ComponentID( compIdx );//確定是Cb還是Cr分量
    int chromaQPOffset       = slice->getPPS()->getQpOffset( compID ) + slice->getSliceChromaQpDelta( compID );//獲取色度QP偏移
    int qpc = slice->getSPS()->getMappedChromaQpValue(compID, iQP) + chromaQPOffset;//將偏移值應用到默認QP,獲取色度實際採用的QP
    double tmpWeight         = pow( 2.0, ( iQP - qpc ) / 3.0 );  // 考慮了色度qp映射和色度qp偏移,得到權重,這個權重大於1
    if( m_pcCfg->getDepQuantEnabledFlag() && !( m_pcCfg->getLFNST() ) )//如果使用了DQ並且不使用LFNST
    {//增加色度權重以進行相關量化(以減少從色度到亮度的比特率偏移)
      tmpWeight *= ( m_pcCfg->getGOPSize() >= 8 ? pow( 2.0, 0.1/3.0 ) : pow( 2.0, 0.2/3.0 ) );  
    }
    m_pcRdCost->setDistortionWeight( compID, tmpWeight );
    dLambdas[compIdx] = dLambda / tmpWeight;//確定色度分量的lambda,比亮度的小一點
  }

  /***************************** 2.根據率失真優化得到的lambda,直接作用於RDOQ和SAO ******************************/

  // for RDOQ
  m_pcTrQuant->setLambdas( dLambdas );
  // for SAO
  slice->setLambdas( dLambdas );
}

1.5 setLambda

void RdCost::setLambda( double dLambda, const BitDepths &bitDepths )
{
  m_dLambda             = dLambda;//把參數中的lambda值賦值給RdCost類的成員變量m_dLambda

  m_DistScale           = double(1<<SCALE_BITS) / m_dLambda;//m_DistScale這個是lambda的倒數,還有移位,J(λ) = min[ D + λR ] 轉化爲 1/J(λ) = min[ (1/λ) D + R ]

  m_dLambdaMotionSAD[0] = sqrt(m_dLambda);//常規幀間運動信息的SAD的lambda是lambda的開方
  dLambda = 0.57
            * pow(2.0, ((LOSSLESS_AND_MIXED_LOSSLESS_RD_COST_TEST_QP_PRIME - 12
                         + 6
                             * ((bitDepths.recon[CHANNEL_TYPE_LUMA] - 8)
                                - DISTORTION_PRECISION_ADJUSTMENT(bitDepths.recon[CHANNEL_TYPE_LUMA])))
                        / 3.0));
  m_dLambdaMotionSAD[1] = sqrt(dLambda);//TS模式下幀間運動信息的SAD的lambda是上面新算得lambda的開方
}

2 率失真優化所在位置

2.1 TPM

用於TPM模式的粗選:從40種劃分確定索引方式中,根據SAD找到3種最優,後續這3種模式會進入SATD篩選。

      double cost = (double)uiSad + (double)uiBitsCand * sqrtLambdaForFirstPass;
      updateCandList( mergeCand, cost, triangleRdModeList, tianglecandCostList, triangleNumMrgSATDCand );

2.2 Affine merge

用於Affine merge模式的粗選,根據SAD找到2種最優,後續這2種模式會進入SATD篩選。

     double cost = (double)uiSad + (double)uiBitsCand * sqrtLambdaForFirstPass;
     updateCandList( uiMergeCand, cost, RdModeList, candCostList, uiNumMrgSATDCand );

2.3 SAO

1)確定最佳邊帶補償階段:對比28種邊帶,找到最佳邊帶BO。
2)CTU級:確定最佳SAO模式階段:對比以下6種:4個邊界補償方式EO,1個前面找到的最佳BO,1個不採用SAO,找到最佳SAO方式
3)CTU級:確定是否使用參數融合:對比以下3種:剛找到的最佳SAO方式,上塊SAO merge,左塊SAO merge,得到SAO全過程的最佳

2.4 ALF

1)CTU級:確定是否使用ALF(根據前面計算出的協方差D)
2)確定亮度是否合併濾波器分類,對比固定濾波器和計算得出的濾波器係數,得到最佳亮度濾波器係數,保存進APS
3)確定色度用哪個濾波器,得到最佳色度濾波器係數,保存進APS
4)遍歷APS序列(即對比當前係數和歷史系數哪個更好),得到最佳ALF係數

2.5 Intra

1、亮度預測對35種模式第一輪粗選出2或3種模式:D應用SAD和HAD中較小的
2、亮度預測對2或3種旁邊的模式第二輪粗選出2或3種模式:D應用SAD和HAD中較小的
3、亮度預測對MPM列表中的進行多參考行的篩選找到最佳參考行:D應用SAD和HAD中較小的
4、亮度MIP預測的篩選得出一種最佳MIP模式,放在前面2到3種模式後面成爲3或4種模式:D應用SAD和HAD中較小的
5、亮度預測選出最佳模式:D應用HAD
6、色度預測用到lambda計算的有6種,6種中選出最佳:D應用HAD

2.6 Merge

merge、mmvd、CIIP的失真計算共同粗選出5種(CIIP選出1種,merge和mmvd共同選出4種):D均應用SAD

未完待續。。

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