HEVC代码学习——帧间预测:预测MV获取(xEstimateMvPredAMVP、fillMVPCand)

HEVC帧间预测在AMVP模式下是依靠xEstimateMvPredAMVP函数获取预测MV(MVP)的。

这部分内容的学习还可以参考这两篇博客:
HEVC代码学习15:AMVP相关函数
HM编码器代码阅读(16)——帧间预测之AMVP模式(四)预测MV的获取

xEstimateMvPredAMVP

xEstimateMvPredAMVP的作用是建立MVP列表并获取最优MVP,最终将最优MVP以及其索引等信息返回给上层函数——preInterSearch

xEstimateMvPredAMVP的基本流程如下:
(1)判断bFilled标识,该标识表示MVP候选列表是否已经建好;
(2)如果bFilled是false,通过fillMvpCand获取MVP候选列表;否则,不需要重新建立MVP候选列表
(3)先把MVP候选列表中的第一个MVP作为最优的MVP
(4)如果候选列表中MVP的数量小于等于1,那么直接把步骤3选出的MVP返回
(5)如果bFilled是true,表示MVP候选列表是原来已经建立好的,那么直接根据PU的相关信息得到最优的MVP,然后返回
(6)遍历MVP候选列表,选出代价最小的MVP作为当前PU的MVP,并设置相关信息,然后返回

xEstimateMvPredAMVP代码如下:

Void TEncSearch::xEstimateMvPredAMVP( TComDataCU* pcCU, TComYuv* pcOrgYuv, UInt uiPartIdx, RefPicList eRefPicList, Int iRefIdx, TComMv& rcMvPred, Bool bFilled, Distortion* puiDistBiP )
{
  AMVPInfo*  pcAMVPInfo = pcCU->getCUMvField(eRefPicList)->getAMVPInfo();

  TComMv     cBestMv;  //最优MV
  Int        iBestIdx   = 0; //最优MV在候选中的索引
  TComMv     cZeroMv;
  TComMv     cMvPred;  
  Distortion uiBestCost = std::numeric_limits<Distortion>::max();
  UInt       uiPartAddr = 0; // PU地址
  Int        iRoiWidth, iRoiHeight; // PU长宽
  Int        i;
  Int        minMVPCand;
  Int        maxMVPCand;

  // 获取当前PU的索引和大小
  pcCU->getPartIndexAndSize( uiPartIdx, uiPartAddr, iRoiWidth, iRoiHeight );
  // Fill the MV Candidates
  if (!bFilled)
  {
    pcCU->fillMvpCand( uiPartIdx, uiPartAddr, eRefPicList, iRefIdx, pcAMVPInfo ); // 建立MVP候选列表
  }
  // initialize Mvp index & Mvp
#if MCTS_ENC_CHECK
  if (m_pcEncCfg->getTMCTSSEITileConstraint() && pcCU->isLastColumnCTUInTile() && (pcAMVPInfo->numSpatialMVPCandidates < pcAMVPInfo->iN))
  {
    iBestIdx    = (pcAMVPInfo->numSpatialMVPCandidates == 0) ? 1 : 0;
    cBestMv     = pcAMVPInfo->m_acMvCand[(pcAMVPInfo->numSpatialMVPCandidates == 0) ? 1 : 0];
    minMVPCand  = (pcAMVPInfo->numSpatialMVPCandidates == 0) ? 1 : 0;
    maxMVPCand  = (pcAMVPInfo->numSpatialMVPCandidates == 0) ? pcAMVPInfo->iN : 1;
  }
  else
  {
    iBestIdx = 0;
    cBestMv  = pcAMVPInfo->m_acMvCand[0];
    minMVPCand  = 0;
    maxMVPCand  = pcAMVPInfo->iN;
  }
#else
  iBestIdx = 0;
  cBestMv  = pcAMVPInfo->m_acMvCand[0];
  minMVPCand  = 0;
  maxMVPCand  = pcAMVPInfo->iN;
#endif
  if (pcAMVPInfo->iN <= 1)
  {
    rcMvPred = cBestMv;

    pcCU->setMVPIdxSubParts( iBestIdx, eRefPicList, uiPartAddr, uiPartIdx, pcCU->getDepth(uiPartAddr));
    pcCU->setMVPNumSubParts( pcAMVPInfo->iN, eRefPicList, uiPartAddr, uiPartIdx, pcCU->getDepth(uiPartAddr));

    if(pcCU->getSlice()->getMvdL1ZeroFlag() && eRefPicList==REF_PIC_LIST_1)
    {
      (*puiDistBiP) = xGetTemplateCost( pcCU, uiPartAddr, pcOrgYuv, &m_cYuvPredTemp, rcMvPred, 0, AMVP_MAX_NUM_CANDS, eRefPicList, iRefIdx, iRoiWidth, iRoiHeight);
    }
    return;
  }

  // 上述步骤(5)
  if (bFilled)
  {
    assert(pcCU->getMVPIdx(eRefPicList,uiPartAddr) >= 0);
    rcMvPred = pcAMVPInfo->m_acMvCand[pcCU->getMVPIdx(eRefPicList,uiPartAddr)];
    return;
  }

  m_cYuvPredTemp.clear();
  //-- Check Minimum Cost.
  // 比较得出最优MV
  for ( i = minMVPCand ; i < maxMVPCand; i++)
  {
    Distortion uiTmpCost;
    uiTmpCost = xGetTemplateCost( pcCU, uiPartAddr, pcOrgYuv, &m_cYuvPredTemp, pcAMVPInfo->m_acMvCand[i], i, AMVP_MAX_NUM_CANDS, eRefPicList, iRefIdx, iRoiWidth, iRoiHeight);
    if ( uiBestCost > uiTmpCost )
    {
      uiBestCost = uiTmpCost;
      cBestMv   = pcAMVPInfo->m_acMvCand[i];
      iBestIdx  = i;
      (*puiDistBiP) = uiTmpCost;
    }
  }

  m_cYuvPredTemp.clear();

  // Setting Best MVP
  // 设置最优MVP的信息,将获取的最优MVP信息返回给preInterSearch
  rcMvPred = cBestMv;
  pcCU->setMVPIdxSubParts( iBestIdx, eRefPicList, uiPartAddr, uiPartIdx, pcCU->getDepth(uiPartAddr));
  pcCU->setMVPNumSubParts( pcAMVPInfo->iN, eRefPicList, uiPartAddr, uiPartIdx, pcCU->getDepth(uiPartAddr));
  return;
}

而在上层函数preInterSearch中,则通过以下语句调用xEstimateMvPredAMVP并获取从其中返回的上述三个值:cBestMv(最优MV)、iBestIdx(最优MV索引)、pcAMVPInfo->iN(AMVP候选列表中有效候选数量)

xEstimateMvPredAMVP( pcCU, pcOrgYuv, iPartIdx, eRefPicList, iRefIdxTemp, cMvPred[iRefList][iRefIdxTemp], false, &biPDistTemp);
aaiMvpIdx[iRefList][iRefIdxTemp] = pcCU->getMVPIdx(eRefPicList, uiPartAddr);
aaiMvpNum[iRefList][iRefIdxTemp] = pcCU->getMVPNum(eRefPicList, uiPartAddr);

通过调用上述语句,实则将上述三个值分别赋给以下三个值,以供后续使用:

 //最优MV存入cMvPred[iRefList][iRefIdxTemp]
 cMvPred[iRefList][iRefIdxTemp] = cBestMv
 // 最优MVP索引存入CU中(执行xEstimateMvPredAMVP的结果),然后赋值给aaiMvpIdx[iRefList][iRefIdxTemp]
 aaiMvpIdx[iRefList][iRefIdxTemp] = pcCU->m_apiMVPIdx[eRefPicList][uiPartAddr]  
 // MVP候选数量存入CU中(执行xEstimateMvPredAMVP的结果),然后赋值给aaiMvpNum[iRefList][iRefIdxTemp] 
 aaiMvpNum[iRefList][iRefIdxTemp] = pcCU->m_apiMVPNum[eRefPicList][uiPartAddr]

AMVPInfo

另需要特别注意的是,在HM中,AMVP候选列表是用一个AMVPInfo类的结构体进行存储和传递的

typedef struct _AMVPInfo
{
  // MVP候选列表
  TComMv m_acMvCand[ AMVP_MAX_NUM_CANDS ];  ///< array of motion vector predictor candidates
  // 候选列表中有效候选数量
  Int    iN;                                ///< number of motion vector predictor candidates
#if MCTS_ENC_CHECK
  // 候选列表中空域候选数量
  UInt   numSpatialMVPCandidates;
#endif
} AMVPInfo;

这个存储MVP候选列表信息的结构体,在xEstimateMvPredAMVP中名为pcAMVPInfo,在fillMVPCand中名为pInfo

fillMVPCand

下面学习fillMVPCand函数,该函数用于建立MVP候选列表。
该函数学习可参考博客:
HEVC代码学习30:fillMvpCand函数

在这里插入图片描述
由于在视频中经常出现一大片区域(比如同一物体)运动方向一样的情况,因此运动向量MV不论在空域上还是时域上都有着很强的相关性,因此可以用当前PU在空域、时域上相邻的块的MV近似代替当前PU的MV以实现运动矢量预测(MVP)。

AMVP技术及将理论上与当前PU的MV最可能相近的空域相邻块、时域相邻块的若干MV收录进MVP候选列表中,以供之后进行选择。而这个建立MVP候选列表并收录MVP的过程就是在fillMVPCand函数中实现的。

建立顺序如下:
一、建立空域候选列表:
1、按顺序搜索左侧块A0-A1-scaled A0-scaled A1,只要有一个MV存在,写入候选列表,跳出进行下一步。
2、按顺序搜索上方块B0-B1-B2(-scaled B0-scaled B1-scaled B2),只要有一个MV存在,写入候选列表,跳出进行下一步。而其中scaled B0-scaled B1-scaled B2只有当A0和A1的MV都不存在时,才进行搜索。
3、空域候选列表长度为2时,即左侧和上方各选出了一个候选,则对两者进行比较,相同时进行合并。
4、空域候选列表建立完成。
二、当空域候选数量少于2个,且启用时域候选时,建立时域候选列表。
三、如果空域+时域候选个数少于2,则使用(0,0)补足。

fillMVPCand代码如下:

Void TComDataCU::fillMvpCand ( const UInt partIdx, const UInt partAddr, const RefPicList eRefPicList, const Int refIdx, AMVPInfo* pInfo ) const
{
  pInfo->iN = 0; // 候选数量先置零
  if (refIdx < 0)
  {
#if MCTS_ENC_CHECK
    pInfo->numSpatialMVPCandidates = 0; // 空域候选数量先置零
#endif
    return;
  }

  //-- Get Spatial MV
  // 获取相邻块地址
  UInt partIdxLT, partIdxRT, partIdxLB;
  deriveLeftRightTopIdx( partIdx, partIdxLT, partIdxRT );
  deriveLeftBottomIdx( partIdx, partIdxLB );

  Bool isScaledFlagLX = false; /// variable name from specification; true when the PUs below left or left are available (availableA0 || availableA1).
  {
    UInt idx;
    const TComDataCU* tmpCU = getPUBelowLeft(idx, partIdxLB); // 获取左下角CU
    isScaledFlagLX = (tmpCU != NULL) && (tmpCU->isInter(idx));
    if (!isScaledFlagLX)
    {
      tmpCU = getPULeft(idx, partIdxLB);
      isScaledFlagLX = (tmpCU != NULL) && (tmpCU->isInter(idx));
    }
  }

  // Left predictor search
  // 获取左侧块MV
  if (isScaledFlagLX)
  {
    Bool bAdded = xAddMVPCandUnscaled( *pInfo, eRefPicList, refIdx, partIdxLB, MD_BELOW_LEFT); // 获取A0的MV
    if (!bAdded)
    {
      bAdded = xAddMVPCandUnscaled( *pInfo, eRefPicList, refIdx, partIdxLB, MD_LEFT ); // 获取A1的MV
      if(!bAdded)
      {
        bAdded = xAddMVPCandWithScaling( *pInfo, eRefPicList, refIdx, partIdxLB, MD_BELOW_LEFT); // 获取scaled A0的MV
        if (!bAdded)
        {
          xAddMVPCandWithScaling( *pInfo, eRefPicList, refIdx, partIdxLB, MD_LEFT ); // 获取scaled A1的MV
        }
      }
    }
  }

  // Above predictor search
  {
    Bool bAdded = xAddMVPCandUnscaled( *pInfo, eRefPicList, refIdx, partIdxRT, MD_ABOVE_RIGHT);
    if (!bAdded)
    {
      bAdded = xAddMVPCandUnscaled( *pInfo, eRefPicList, refIdx, partIdxRT, MD_ABOVE);
      if(!bAdded)
      {
        xAddMVPCandUnscaled( *pInfo, eRefPicList, refIdx, partIdxLT, MD_ABOVE_LEFT);
      }
    }
  }

  if(!isScaledFlagLX)
  {
    Bool bAdded = xAddMVPCandWithScaling( *pInfo, eRefPicList, refIdx, partIdxRT, MD_ABOVE_RIGHT);
    if (!bAdded)
    {
      bAdded = xAddMVPCandWithScaling( *pInfo, eRefPicList, refIdx, partIdxRT, MD_ABOVE);
      if(!bAdded)
      {
        xAddMVPCandWithScaling( *pInfo, eRefPicList, refIdx, partIdxLT, MD_ABOVE_LEFT);
      }
    }
  }

  if ( pInfo->iN == 2 )
  {
    if ( pInfo->m_acMvCand[ 0 ] == pInfo->m_acMvCand[ 1 ] )
    {
      pInfo->iN = 1;
    }
  }
#if MCTS_ENC_CHECK
  pInfo->numSpatialMVPCandidates = pInfo->iN;
#endif
  // 获取时域MVP
  if (pInfo->iN < AMVP_MAX_NUM_CANDS && getSlice()->getEnableTMVPFlag() )
  {
    // Get Temporal Motion Predictor
    const UInt numPartInCtuWidth  = m_pcPic->getNumPartInCtuWidth();
    const UInt numPartInCtuHeight = m_pcPic->getNumPartInCtuHeight();
    const Int refIdx_Col = refIdx;
    TComMv cColMv;
    UInt partIdxRB;
    UInt absPartIdx;

    deriveRightBottomIdx( partIdx, partIdxRB );
    UInt absPartAddr = m_absZIdxInCtu + partAddr;

    //----  co-located RightBottom Temporal Predictor (H) ---//
    absPartIdx = g_auiZscanToRaster[partIdxRB];
    Int ctuRsAddr = -1;
    if (  ( ( m_pcPic->getCtu(m_ctuRsAddr)->getCUPelX() + g_auiRasterToPelX[absPartIdx] + m_pcPic->getMinCUWidth () ) < m_pcSlice->getSPS()->getPicWidthInLumaSamples () )  // image boundary check
       && ( ( m_pcPic->getCtu(m_ctuRsAddr)->getCUPelY() + g_auiRasterToPelY[absPartIdx] + m_pcPic->getMinCUHeight() ) < m_pcSlice->getSPS()->getPicHeightInLumaSamples() ) )
    {
      if ( ( absPartIdx % numPartInCtuWidth < numPartInCtuWidth - 1 ) &&  // is not at the last column of CTU
           ( absPartIdx / numPartInCtuWidth < numPartInCtuHeight - 1 ) )  // is not at the last row    of CTU
      {
        absPartAddr = g_auiRasterToZscan[ absPartIdx + numPartInCtuWidth + 1 ];
        ctuRsAddr = getCtuRsAddr();
      }
      else if ( absPartIdx % numPartInCtuWidth < numPartInCtuWidth - 1 )  // is not at the last column of CTU But is last row of CTU
      {
        absPartAddr = g_auiRasterToZscan[ (absPartIdx + numPartInCtuWidth + 1) % m_pcPic->getNumPartitionsInCtu() ];
      }
      else if ( absPartIdx / numPartInCtuWidth < numPartInCtuHeight - 1 ) // is not at the last row of CTU But is last column of CTU
      {
        absPartAddr = g_auiRasterToZscan[ absPartIdx + 1 ];
        ctuRsAddr = getCtuRsAddr() + 1;
      }
      else //is the right bottom corner of CTU
      {
        absPartAddr = 0;
      }
    }
    if ( ctuRsAddr >= 0 && xGetColMVP( eRefPicList, ctuRsAddr, absPartAddr, cColMv, refIdx_Col ) )
    {
      pInfo->m_acMvCand[pInfo->iN++] = cColMv;
    }
    else
    {
      UInt uiPartIdxCenter;
      xDeriveCenterIdx( partIdx, uiPartIdxCenter );
      if (xGetColMVP( eRefPicList, getCtuRsAddr(), uiPartIdxCenter,  cColMv, refIdx_Col ))
      {
        pInfo->m_acMvCand[pInfo->iN++] = cColMv;
      }
    }
    //----  co-located RightBottom Temporal Predictor  ---//
  }
  // 若候选列表不满,补零
  while (pInfo->iN < AMVP_MAX_NUM_CANDS)
  {
    pInfo->m_acMvCand[pInfo->iN].set(0,0);
    pInfo->iN++;
  }
  return ;
}

可见,最终构建的MVP候选列表存入了上述的名为pInfoAMVPInfo结构体中,并返回给xEstimateMvPredAMVPpcAMVPInfo

另外,从相邻块获取MV的函数分别为xAddMVPCandUnscaled以及xAddMVPCandWithScaling。前者为获取A0~B2 MV的函数,后者为获取scaled A0~B2的函数。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的MATLAB代码示例,用于实现HEVC预测: ```matlab function [predBlock] = intra_predict(image, blockSize, row, col, mode) % image: 输入图像 % blockSize: 块大小 % row, col: 当前块的起始行和列 % mode: 预测模式 % 获取参考像素 switch(mode) case 0 % 模式0:DC预测 refBlock = image(row:blockSize + row - 1, col:blockSize + col - 1); p = mean2(refBlock); predBlock = ones(blockSize, blockSize) * p; case 1 % 模式1:水平预测 refBlock = image(row:blockSize + row - 1, col - 1); predBlock = repmat(refBlock, [1, blockSize]); case 2 % 模式2:垂直预测 refBlock = image(row - 1, col:blockSize + col - 1); predBlock = repmat(refBlock, [blockSize, 1]); case 3 % 模式3:左上角预测 refBlock = image(row - 1, col - 1); predBlock = ones(blockSize, blockSize) * refBlock; case 4 % 模式4:右上角预测 refBlock = image(row - 1, col + blockSize); predBlock = ones(blockSize, blockSize) * refBlock; case 5 % 模式5:左下角预测 refBlock = image(row + blockSize, col - 1); predBlock = ones(blockSize, blockSize) * refBlock; case 6 % 模式6:垂直右下预测 refBlock1 = image(row - 1, col + blockSize); refBlock2 = image(row - 2, col + blockSize); refBlock3 = image(row - 3, col + blockSize); refBlock4 = image(row - 4, col + blockSize); refBlock5 = image(row - 5, col + blockSize); refBlock6 = image(row - 6, col + blockSize); refBlock7 = image(row - 7, col + blockSize); refBlock = (refBlock1 + 2 * refBlock2 + 3 * refBlock3 + 4 * refBlock4 + 5 * refBlock5 + 6 * refBlock6 + 7 * refBlock7 + 8) / 16; predBlock = ones(blockSize, blockSize) * refBlock; case 7 % 模式7:水平右下预测 refBlock1 = image(row + blockSize, col - 1); refBlock2 = image(row + blockSize, col - 2); refBlock3 = image(row + blockSize, col - 3); refBlock4 = image(row + blockSize, col - 4); refBlock5 = image(row + blockSize, col - 5); refBlock6 = image(row + blockSize, col - 6); refBlock7 = image(row + blockSize, col - 7); refBlock = (refBlock1 + 2 * refBlock2 + 3 * refBlock3 + 4 * refBlock4 + 5 * refBlock5 + 6 * refBlock6 + 7 * refBlock7 + 8) / 16; predBlock = ones(blockSize, blockSize) * refBlock; end end ``` 请注意,此代码仅实现了HEVC预测的一些基本模式,可能需要根据您的特定需求进行更改和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值