H.266/VVC代碼學習筆記15:VTM6.0中的xCheckRDCostMergeTriangle2Nx2N()函數

VTM6.0中xCheckRDCostMergeTriangle2Nx2N()函數是幀間預測技術中Merge模式中TPM技術的主函數的入口,想要了解TPM在代碼中的實現,這個函數是啃定了,我只是大概看了一下該函數的大致上的流程以及其中三角Merge列表的構建、加權預測、60種三角形組合模式的率失真代價選擇的大致過程。今天將所看的代碼分享給大家,如過代碼中有理解錯的地方,還請指正,謝謝大家~,關於三角預測的原理部分可以參考博客:H.266/VVC相關技術學習筆記:幀間預測中的TPM技術(Triangle partition Mode)

還是老套路,用文字過一下該函數的大致流程,再將加了註釋的代碼附在後面:
1、首先進行一些初始化的操作,定義一些變量、存放預測值以及殘差的buffer、RD代價列表之類

2、定義三角Merge單向預測列表,是從常規的Merge列表中派生出來的,然後調用getTriangleMergeCandidates()函數,獲取三角形預測的單向Merge候選列表,關於該函數的細節,請參考博客: H.266/VVC代碼學習筆記16:VTM6.0中的getTriangleMergeCandidates()函數

3、遍歷6個三角Merge候選 ,運動補償出每個候選的單向預測值。

4、遍歷60種組合模式(2×6×5),進行亮度分量的SAD——Cost,進行粗選操作,通過調用weightedTriangleBlk()函數計算亮度分量下的每種三角組合下的加權預測值,關於該函數細節具體參考博客:H.266/VVC代碼學習筆記17:VTM6.0中的xWeightedTriangleBlk()函數
每遍歷一個組合模式,需要去通過調用updateCandList()函數更新三角模式的RD候選模式列表triangleRdModeList以及三角代價列表tianglecandCostList。遍歷更新完之後,triangleRdModeList列表中的候選的Cost是從小到大排序的,RD列表的最大長度爲3,也就是從60組合中RDCost出3個較優的候選

5、使用SATD-cost限制候選列表的數量,在上一步SAD粗選完之後對RD候選列表的容量左進一步的限制,減少RD候選列表的範圍,在3個裏再縮減,可以縮減爲0,1,2,3中任意一個可能

6、對那些經過SAD以及進一步數量限制篩選出的較優的候選進行遍歷,逐個計算出相應色度分量的加權預測值。(注意:在之前用亮度分量進行篩選,篩選完之後再去對相應的色度進行加權。個人認爲考慮到兩方面的原因:一是亮度分量的可靠性更強,用亮度篩選出的候選比較真實可靠。二是爲了節省複雜度,只需要用亮度分量去進行篩選就足夠了,不需要再去求色度分量)

7、開始正式的HAD_Cost的細選過程。遍歷需要進行SATD細選的列表中的每一個候選,進行SATD_Cost的比較,得到cost最小的那個候選作爲最優的三角模式。然後再去與其餘的Merge模式去進行率失真代價的比較,選出最優的一種三角組合模式,再在外層與其他Merge模式一起去競爭。這其中調用xEncodeInterResidual()函數進行幀間殘差編碼,這與之前的xCheckRDCostMerge2Nx2N()函數的學習中所調用的一樣:H.266/VVC代碼學習筆記14:xCheckRDCostMerge2Nx2N()函數

以上就是xCheckRDCostMergeTriangle2Nx2N()函數的大致流程,關於這部分代碼的詳細註釋附在下面,供大家參考使用:

void EncCu::xCheckRDCostMergeTriangle2Nx2N( CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &partitioner, const EncTestMode& encTestMode )
{
  const Slice &slice = *tempCS->slice;
  const SPS &sps = *tempCS->sps;

  if (slice.getMaxNumTriangleCand() < 2)
    return;

  CHECK( slice.getSliceType() != B_SLICE, "Triangle mode is only applied to B-slices" );

  tempCS->initStructData( encTestMode.qp, encTestMode.lossless );//初始化數據結構

  bool trianglecandHasNoResidual[TRIANGLE_MAX_NUM_CANDS];//60個三角形預測候選是否有殘差
  for( int mergeCand = 0; mergeCand < TRIANGLE_MAX_NUM_CANDS; mergeCand++ )
  {
    trianglecandHasNoResidual[mergeCand] = false;//都初始化爲false
  }

  bool bestIsSkip = false;

  uint8_t                                         numTriangleCandidate   = TRIANGLE_MAX_NUM_CANDS;//三角候選數量爲60,P1預測塊有6種MV的選擇*P2預測塊有5種MV的選擇*兩種分區劃分模式(對角和反對角線)=60
  uint8_t                                         triangleNumMrgSATDCand = TRIANGLE_MAX_NUM_SATD_CANDS;//三角預測用於SATD比較的候選數量最大爲3
  PelUnitBuf                                      triangleBuffer[TRIANGLE_MAX_NUM_UNI_CANDS];//三角形的緩存單元數最大爲6,三角預測的單向Merge列表長度爲6
  PelUnitBuf                                      triangleWeightedBuffer[TRIANGLE_MAX_NUM_CANDS];//三角加權預測值的緩存爲60個
  static_vector<uint8_t, TRIANGLE_MAX_NUM_CANDS> triangleRdModeList;//三角形預測的RD的模式列表,首先粗選選出的組合候選放入到最終的RDCost候選列表中
  static_vector<double,  TRIANGLE_MAX_NUM_CANDS> tianglecandCostList;//三角形預測的候選代價列表,首先粗選選出的組合候選的代價放入到最終的代價列表中
  uint8_t                                         numTriangleCandComb = slice.getMaxNumTriangleCand() * (slice.getMaxNumTriangleCand() - 1) * 2;//三角預測模式的組合數量:6*5*2=60


  DistParam distParam;
  const bool useHadamard = !encTestMode.lossless && !tempCS->slice->getDisableSATDForRD();
  m_pcRdCost->setDistParam( distParam, tempCS->getOrgBuf().Y(), m_acMergeBuffer[0].Y(), sps.getBitDepth( CHANNEL_TYPE_LUMA ), COMPONENT_Y, useHadamard );

  const UnitArea localUnitArea( tempCS->area.chromaFormat, Area( 0, 0, tempCS->area.Y().width, tempCS->area.Y().height) );//開闢編碼CU的空間

  const double sqrtLambdaForFirstPass = m_pcRdCost->getMotionLambda(encTestMode.lossless);

  MergeCtx triangleMrgCtx;//定義三角Merge單向預測列表,是從常規的Merge列表中派生出來的
  {
    CodingUnit cu( tempCS->area );
    cu.cs       = tempCS;
    cu.predMode = MODE_INTER;//預測模式爲幀間模式
    cu.slice    = tempCS->slice;//指向當前的Slice
    cu.tileIdx          = tempCS->picture->brickMap->getBrickIdxRsMap( tempCS->area.lumaPos() );
    cu.triangle = true;//是三角預測模式
    cu.mmvdSkip = false;//不是MMVDSkip模式
    cu.GBiIdx   = GBI_DEFAULT;//雙向預測權重索引


    PredictionUnit pu( tempCS->area );//爲當前的CU開闢臨時緩存空間
    pu.cu = &cu;
    pu.cs = tempCS;
    pu.regularMergeFlag = false;//不是regular模式

    //獲取三角形預測的單向Merge候選列表
    PU::getTriangleMergeCandidates( pu, triangleMrgCtx );

    const uint8_t maxNumTriangleCand = pu.cs->slice->getMaxNumTriangleCand();//三角預測的最大候選數

    //遍歷6個三角Merge候選
    for (uint8_t mergeCand = 0; mergeCand < maxNumTriangleCand; mergeCand++)
    {
      triangleBuffer[mergeCand] = m_acMergeBuffer[mergeCand].getBuf(localUnitArea);
      triangleMrgCtx.setMergeInfo( pu, mergeCand );//設置三角Merge候選的初始信息
      PU::spanMotionInfo( pu, triangleMrgCtx );

      if( m_pcEncCfg->getMCTSEncConstraint() && ( !( MCTSHelper::checkMvBufferForMCTSConstraint( pu ) ) ) )
      {
        // Do not use this mode
        tempCS->initStructData( encTestMode.qp, encTestMode.lossless );//初始化數據結構
        return;
      }
      m_pcInterSearch->motionCompensation( pu, triangleBuffer[mergeCand] );//運動補償獲得每個候選的單向預測值
    }
  }

  triangleNumMrgSATDCand = min(triangleNumMrgSATDCand, numTriangleCandComb);//需要進行SATDCost比較的三角Merge候選的數量

  {
    CodingUnit &cu      = tempCS->addCU( tempCS->area, partitioner.chType );//定義CU

    partitioner.setCUData( cu );//初始化CU
    cu.slice            = tempCS->slice;//定義片的臨時緩存
    cu.tileIdx          = tempCS->picture->brickMap->getBrickIdxRsMap( tempCS->area.lumaPos() );
    cu.skip             = false;
    cu.predMode         = MODE_INTER;//幀間模式
    cu.transQuantBypass = encTestMode.lossless;
    cu.chromaQpAdj      = cu.transQuantBypass ? 0 : m_cuChromaQpOffsetIdxPlus1;
    cu.qp               = encTestMode.qp;
    cu.triangle         = true;//三角模式爲真
    cu.mmvdSkip         = false;//不使用Skip模式
    cu.GBiIdx           = GBI_DEFAULT;//雙向預測權重索引爲默認值

    PredictionUnit &pu  = tempCS->addPU( cu, partitioner.chType );//添加PU

    if( abs(g_aucLog2[cu.lwidth()] - g_aucLog2[cu.lheight()]) >= 2 )//如果當前CU的寬高比大於等於2,則只有一種劃分方向,共30種模式
    {
      numTriangleCandidate = 30;
    }
    else
    {
      numTriangleCandidate = TRIANGLE_MAX_NUM_CANDS;//最大三角候選的數量爲60
    }

    numTriangleCandidate = min(numTriangleCandidate, numTriangleCandComb);//從三角候選數量和三角的結合候選數量中挑一個最小的


    //遍歷60種候選,進行SAD——Cost,進行粗選操作
    for( uint8_t mergeCand = 0; mergeCand < numTriangleCandidate; mergeCand++ )
    {
      bool    splitDir = m_triangleModeTest[mergeCand].m_splitDir;//獲取三角的劃分方向
      uint8_t candIdx0 = m_triangleModeTest[mergeCand].m_candIdx0;//獲取第一個三角分區的候選索引,6箇中選一個
      uint8_t candIdx1 = m_triangleModeTest[mergeCand].m_candIdx1;//獲取第二個三角分區的候選索引,剩下的5箇中選一個


      //將上面三個變量傳給當前PU
      pu.triangleSplitDir = splitDir;
      pu.triangleMergeIdx0 = candIdx0;
      pu.triangleMergeIdx1 = candIdx1;

      pu.mergeFlag = true;//Merge模式可用
      pu.regularMergeFlag = false;//regularMerge模式不可用


      triangleWeightedBuffer[mergeCand] = m_acTriangleWeightedBuffer[mergeCand].getBuf( localUnitArea );//三角權重的臨時緩存
      triangleBuffer[candIdx0] = m_acMergeBuffer[candIdx0].getBuf( localUnitArea );//第一個分區的預測值的臨時緩存
      triangleBuffer[candIdx1] = m_acMergeBuffer[candIdx1].getBuf( localUnitArea );//第二個分區的預測值的臨時緩存

      //亮度分量加權過程
      m_pcInterSearch->weightedTriangleBlk( pu, splitDir, CHANNEL_TYPE_LUMA, triangleWeightedBuffer[mergeCand], triangleBuffer[candIdx0], triangleBuffer[candIdx1] );
      
      
      distParam.cur = triangleWeightedBuffer[mergeCand].Y();//當前組合模式的三角亮度權重

      Distortion uiSad = distParam.distFunc( distParam );//計算當前組合模式失真的SAD

      uint32_t uiBitsCand = m_triangleIdxBins[splitDir][candIdx0][candIdx1];//當前組合模式的總比特數(殘差+頭信息+語法元素)

      double cost = (double)uiSad + (double)uiBitsCand * sqrtLambdaForFirstPass;//計算SAD代價

      updateCandList( mergeCand, cost, triangleRdModeList, tianglecandCostList
        , triangleNumMrgSATDCand );//更新RD候選列表,將cost較小的放入列表中
    }
    //遍歷更新完之後,triangleRdModeList列表中的候選的Cost是從小到大排序的,RD列表的最大長度爲3,也就是從60組合中RDCost出3個較優的候選


    // limit number of candidates using SATD-costs
    //使用SATD-cost限制候選列表的數量,在上一步SAD粗選完之後對RD候選列表的容量左進一步的限制,減少RD候選列表的範圍,在3個裏再縮減,可以縮減爲0,1,2,3中任意一個可能
    for( uint8_t i = 0; i < triangleNumMrgSATDCand; i++ )
    {
      //如果當前RD候選的Cost大於一定的閾值,則就跳出,將前面那些在Cost閾值範圍之內的候選保留在列表中,後面那些超出Cost閾值的候選被排除在外
      if( tianglecandCostList[i] > MRG_FAST_RATIO * tianglecandCostList[0] || tianglecandCostList[i] > getMergeBestSATDCost() )
      {
        triangleNumMrgSATDCand = i;//STAD候選的數量保留爲那些在閾值之內的候選數量
        break;
      }
    }

    // perform chroma weighting process
    //執行色度加權過程

    //對那些經過SAD以及進一步數量限制篩選出的較優的候選進行遍歷(注意:用亮度分量進行篩選,篩選完之後再去對相應的色度進行加權。個人認爲考慮到兩方面的原因:一是亮度分量的可靠性更強,用亮度篩選出的候選比較真實可靠。二是爲了節省複雜度,只需要用亮度分量去進行篩選就足夠了,不需要再去求色度分量)
    for( uint8_t i = 0; i < triangleNumMrgSATDCand; i++ )
    {
      uint8_t  mergeCand = triangleRdModeList[i];//RdModeList列表中的第幾個候選
      bool     splitDir  = m_triangleModeTest[mergeCand].m_splitDir;//劃分方向
      uint8_t  candIdx0  = m_triangleModeTest[mergeCand].m_candIdx0;//分區一的候選
      uint8_t  candIdx1  = m_triangleModeTest[mergeCand].m_candIdx1;//分區二的候選

      pu.triangleSplitDir = splitDir;
      pu.triangleMergeIdx0 = candIdx0;
      pu.triangleMergeIdx1 = candIdx1;
      pu.mergeFlag = true;//標誌Merge模式可用
      pu.regularMergeFlag = false;//標誌regular_Merge模式不可用

      //色度分量加權過程
      m_pcInterSearch->weightedTriangleBlk( pu, splitDir, CHANNEL_TYPE_CHROMA, triangleWeightedBuffer[mergeCand], triangleBuffer[candIdx0], triangleBuffer[candIdx1] );
    }

    //再初始化數據結構
    tempCS->initStructData( encTestMode.qp, encTestMode.lossless );
  }
  //重新獲取三角預測模式的數量,縮減之後的triangleNumMrgSATDCand和RD列表的尺寸中取最小
  triangleNumMrgSATDCand = min(triangleNumMrgSATDCand, (uint8_t)triangleRdModeList.size());

  m_bestModeUpdated = tempCS->useDbCost = bestCS->useDbCost = false;//是否是最優的Cost
  
  
  //這裏開始正式的HAD_Cost的細選過程
  {
    uint8_t iteration;
    uint8_t iterationBegin = 0;//迭代從0開始
    if (encTestMode.lossless)//如果測試的候選模式無損,則迭代一次
    {
      iteration = 1;
    }
    else//否則迭代兩次
    {
      iteration = 2;
    }
    //外層的迭代次數
    for (uint8_t noResidualPass = iterationBegin; noResidualPass < iteration; ++noResidualPass)
    {
      //遍歷需要進行SATD細選的列表中的每一個候選,進行SATD_Cost的比較,得到cost最小的那個候選作爲最優的三角模式。然後再去與其餘的Merge模式去進行率失真代價的比較
      for( uint8_t mrgHADIdx = 0; mrgHADIdx < triangleNumMrgSATDCand; mrgHADIdx++ )
      {
        uint8_t mergeCand = triangleRdModeList[mrgHADIdx];

        if ( ( (noResidualPass != 0) && trianglecandHasNoResidual[mergeCand] )
          || ( (noResidualPass == 0) && bestIsSkip ) )
        {
          continue;
        }

        bool    splitDir = m_triangleModeTest[mergeCand].m_splitDir;
        uint8_t candIdx0 = m_triangleModeTest[mergeCand].m_candIdx0;
        uint8_t candIdx1 = m_triangleModeTest[mergeCand].m_candIdx1;

        CodingUnit &cu = tempCS->addCU(tempCS->area, partitioner.chType);

        partitioner.setCUData(cu);//設置CU的數據
        cu.slice = tempCS->slice;
        cu.tileIdx          = tempCS->picture->brickMap->getBrickIdxRsMap( tempCS->area.lumaPos() );
        cu.skip = false;
        cu.predMode = MODE_INTER;
        cu.transQuantBypass = encTestMode.lossless;
        cu.chromaQpAdj = cu.transQuantBypass ? 0 : m_cuChromaQpOffsetIdxPlus1;
        cu.qp = encTestMode.qp;
        cu.triangle = true;
        cu.mmvdSkip = false;
        cu.GBiIdx   = GBI_DEFAULT;
        PredictionUnit &pu = tempCS->addPU(cu, partitioner.chType);

        pu.triangleSplitDir = splitDir;
        pu.triangleMergeIdx0 = candIdx0;
        pu.triangleMergeIdx1 = candIdx1;
        pu.mergeFlag = true;
        pu.regularMergeFlag = false;

        //重新設置三角預測運動信息
        PU::spanTriangleMotionInfo(pu, triangleMrgCtx, splitDir, candIdx0, candIdx1 );

        if( m_pcEncCfg->getMCTSEncConstraint() && ( !( MCTSHelper::checkMvBufferForMCTSConstraint( *cu.firstPU ) ) ) )
        {
          // Do not use this mode
          tempCS->initStructData( encTestMode.qp, encTestMode.lossless );
          return;
        }

        //將之前得到的加權後的預測值直接拷貝過來。用於下面的殘差編碼(其中就進行了HAD的cost的計算以及比較)
        tempCS->getPredBuf().copyFrom( triangleWeightedBuffer[mergeCand] );//得到三角預測的最終預測值

        //編碼幀間殘差
        xEncodeInterResidual( tempCS, bestCS, partitioner, encTestMode, noResidualPass, ( noResidualPass == 0 ? &trianglecandHasNoResidual[mergeCand] : NULL ) );

        if (m_pcEncCfg->getUseFastDecisionForMerge() && !bestIsSkip)
        {
          bestIsSkip = bestCS->getCU(partitioner.chType)->rootCbf == 0;
        }
        tempCS->initStructData(encTestMode.qp, encTestMode.lossless);
      }// end loop mrgHADIdx
    }
  }
  if ( m_bestModeUpdated && bestCS->cost != MAX_DOUBLE )//選出最優的一種三角組合模式,再在外層與其他Merge模式一起去競爭
  {
    xCalDebCost( *bestCS, partitioner );
  }
}

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