HEVC代码学习14:motionCompensation函数

今天来看运动补偿,推荐两篇博文:
http://blog.csdn.net/hevc_cjl/article/details/8457642
http://blog.csdn.net/nb_vol_1/article/details/55253979

1、运动补偿整体介绍

运动补偿的功能是根据之前的局部图像来预测补偿当前的局部图像。而实际代码来看,主要完成的是亚像素插值补偿的工作。在运动估计中,得到的MV是亚像素精度的,而参考图像是整像素的,亚像素位置上是没有值的。如果通过MV找到的参考块是非整像素位置的,那么在参考图像上是没有这个参考块的。这就需要对参考图像进行插值,构造亚像素参考块。得到的这个参考块将用于计算残差。
实际运动补偿中的函数调用关系如下:

这里写图片描述

2、运动补偿入口函数motionCompensation

下面来看代码(没有具体看加权预测部分),运动补偿的入口函数是motionCompensation,在确定了MV之后被调用,用来构造参考块。

//运动补偿
Void TComPrediction::motionCompensation ( TComDataCU* pcCU, TComYuv* pcYuvPred, RefPicList eRefPicList, Int iPartIdx )
{
  Int         iWidth;
  Int         iHeight;
  UInt        uiPartAddr;

  //如果PU的索引是有效值,那么直接处理该PU,然后返回
  if ( iPartIdx >= 0 )
  {
    pcCU->getPartIndexAndSize( iPartIdx, uiPartAddr, iWidth, iHeight );     //获取PU地址和大小
    if ( eRefPicList != REF_PIC_LIST_X )    //指明了具体参考列表
    {
      if( pcCU->getSlice()->getPPS()->getUseWP())   //是否进行加权预测
      {
        xPredInterUni (pcCU, uiPartAddr, iWidth, iHeight, eRefPicList, pcYuvPred, true );   //最后的参数true标识双向
      }
      else    
      {
        xPredInterUni (pcCU, uiPartAddr, iWidth, iHeight, eRefPicList, pcYuvPred );
      }
      if ( pcCU->getSlice()->getPPS()->getUseWP() )
      {
        xWeightedPredictionUni( pcCU, pcYuvPred, uiPartAddr, iWidth, iHeight, eRefPicList, pcYuvPred );
      }
    }
    else    //没有指明明确的参考列表,那么判断PU两个方向上的参考帧是否相同
    {
      if ( xCheckIdenticalMotion( pcCU, uiPartAddr ) )      //检测PU与预测的CU是否匹配,匹配则进行单向补偿,否则进行双向补偿
      {
        xPredInterUni (pcCU, uiPartAddr, iWidth, iHeight, REF_PIC_LIST_0, pcYuvPred );  
      }
      else
      {
        xPredInterBi  (pcCU, uiPartAddr, iWidth, iHeight, pcYuvPred );
      }
    }
    return;
  }

  //PU索引无效则处理所有PU,处理同上。
  for ( iPartIdx = 0; iPartIdx < pcCU->getNumPartitions(); iPartIdx++ )
  {
    pcCU->getPartIndexAndSize( iPartIdx, uiPartAddr, iWidth, iHeight );

    if ( eRefPicList != REF_PIC_LIST_X )
    {
      if( pcCU->getSlice()->getPPS()->getUseWP())
      {
        xPredInterUni (pcCU, uiPartAddr, iWidth, iHeight, eRefPicList, pcYuvPred, true );
      }
      else
      {
        xPredInterUni (pcCU, uiPartAddr, iWidth, iHeight, eRefPicList, pcYuvPred );
      }
      if ( pcCU->getSlice()->getPPS()->getUseWP() )
      {
        xWeightedPredictionUni( pcCU, pcYuvPred, uiPartAddr, iWidth, iHeight, eRefPicList, pcYuvPred );
      }
    }
    else
    {
      if ( xCheckIdenticalMotion( pcCU, uiPartAddr ) )
      {
        xPredInterUni (pcCU, uiPartAddr, iWidth, iHeight, REF_PIC_LIST_0, pcYuvPred );
      }
      else
      {
        xPredInterBi  (pcCU, uiPartAddr, iWidth, iHeight, pcYuvPred );
      }
    }
  }
  return;
}

3、单向预测xPredInterUni和双向预测xPredInterBi

motionCompensation通过xPredInterUni(单向)和xPredInterBi(双向)调用xPredInterBlk函数来进行亚像素插值的操作,得到亚像素参考块。

//单向运动补偿
Void TComPrediction::xPredInterUni ( TComDataCU* pcCU, UInt uiPartAddr, Int iWidth, Int iHeight, RefPicList eRefPicList, TComYuv* pcYuvPred, Bool bi )
{
  Int         iRefIdx     = pcCU->getCUMvField( eRefPicList )->getRefIdx( uiPartAddr ); //参考帧索引
  assert (iRefIdx >= 0);
  TComMv      cMv         = pcCU->getCUMvField( eRefPicList )->getMv( uiPartAddr );     //获取MV
  pcCU->clipMv(cMv);

  for (UInt comp=COMPONENT_Y; compgetNumberValidComponents(); comp++)
  {
    const ComponentID compID=ComponentID(comp);
    //亚像素块补偿
    xPredInterBlk  (compID,  pcCU, pcCU->getSlice()->getRefPic( eRefPicList, iRefIdx )->getPicYuvRec(), uiPartAddr, &cMv, iWidth, iHeight, pcYuvPred, bi, pcCU->getSlice()->getSPS()->getBitDepth(toChannelType(compID)) );
  }
}

//双向运动补偿,调用xPredInterUni
Void TComPrediction::xPredInterBi ( TComDataCU* pcCU, UInt uiPartAddr, Int iWidth, Int iHeight, TComYuv* pcYuvPred )
{
  TComYuv* pcMbYuv;
  Int      iRefIdx[NUM_REF_PIC_LIST_01] = {-1, -1};

  for ( UInt refList = 0; refList < NUM_REF_PIC_LIST_01; refList++ )
  {
    RefPicList eRefPicList = (refList ? REF_PIC_LIST_1 : REF_PIC_LIST_0);
    iRefIdx[refList] = pcCU->getCUMvField( eRefPicList )->getRefIdx( uiPartAddr );

    if ( iRefIdx[refList] < 0 )
    {
      continue;
    }

    assert( iRefIdx[refList] < pcCU->getSlice()->getNumRefIdx(eRefPicList) );

    pcMbYuv = &m_acYuvPred[refList];
    if( pcCU->getCUMvField( REF_PIC_LIST_0 )->getRefIdx( uiPartAddr ) >= 0 && pcCU->getCUMvField( REF_PIC_LIST_1 )->getRefIdx( uiPartAddr ) >= 0 )
    {
      xPredInterUni ( pcCU, uiPartAddr, iWidth, iHeight, eRefPicList, pcMbYuv, true );
    }
    else
    {
      if ( ( pcCU->getSlice()->getPPS()->getUseWP()       && pcCU->getSlice()->getSliceType() == P_SLICE ) ||
           ( pcCU->getSlice()->getPPS()->getWPBiPred()    && pcCU->getSlice()->getSliceType() == B_SLICE ) )
      {
        xPredInterUni ( pcCU, uiPartAddr, iWidth, iHeight, eRefPicList, pcMbYuv, true );
      }
      else
      {
        xPredInterUni ( pcCU, uiPartAddr, iWidth, iHeight, eRefPicList, pcMbYuv );
      }
    }
  }

  if ( pcCU->getSlice()->getPPS()->getWPBiPred()    && pcCU->getSlice()->getSliceType() == B_SLICE  )
  {
    xWeightedPredictionBi( pcCU, &m_acYuvPred[REF_PIC_LIST_0], &m_acYuvPred[REF_PIC_LIST_1], iRefIdx[REF_PIC_LIST_0], iRefIdx[REF_PIC_LIST_1], uiPartAddr, iWidth, iHeight, pcYuvPred );
  }
  else if ( pcCU->getSlice()->getPPS()->getUseWP() && pcCU->getSlice()->getSliceType() == P_SLICE )
  {
    xWeightedPredictionUni( pcCU, &m_acYuvPred[REF_PIC_LIST_0], uiPartAddr, iWidth, iHeight, REF_PIC_LIST_0, pcYuvPred );
  }
  else
  {
    xWeightedAverage( &m_acYuvPred[REF_PIC_LIST_0], &m_acYuvPred[REF_PIC_LIST_1], iRefIdx[REF_PIC_LIST_0], iRefIdx[REF_PIC_LIST_1], uiPartAddr, iWidth, iHeight, pcYuvPred, pcCU->getSlice()->getSPS()->getBitDepths() );
  }
}

4、生成预测块xPredInterBlk

xPredInterBlk用来生成运动补偿块,其实就是在对参考块进行插值。首先会判断MV具体指向的亚像素位置,然后插值得到该位置的参考块。

//生成运动补偿块
/**
 * \brief Generate motion-compensated block
 *
 * \param compID     Colour component ID
 * \param cu         Pointer to current CU  当前CU指针
 * \param refPic     Pointer to reference picture   参考图像指针
 * \param partAddr   Address of block within CU     
 * \param mv         Motion vector      运动矢量
 * \param width      Width of block     
 * \param height     Height of block
 * \param dstPic     Pointer to destination picture     目标图像指针
 * \param bi         Flag indicating whether bipred is used     双向预测标识
 * \param  bitDepth  Bit depth  
 */
Void TComPrediction::xPredInterBlk(const ComponentID compID, TComDataCU *cu, TComPicYuv *refPic, UInt partAddr, TComMv *mv, Int width, Int height, TComYuv *dstPic, Bool bi, const Int bitDepth )
{
  Int     refStride  = refPic->getStride(compID);       //参考图像跨度
  Int     dstStride  = dstPic->getStride(compID);       //目标图像跨度
  Int shiftHor=(2+refPic->getComponentScaleX(compID));
  Int shiftVer=(2+refPic->getComponentScaleY(compID));

  Int     refOffset  = (mv->getHor() >> shiftHor) + (mv->getVer() >> shiftVer) * refStride;

  Pel*    ref     = refPic->getAddr(compID, cu->getCtuRsAddr(), cu->getZorderIdxInCtu() + partAddr ) + refOffset;   //参考块地址

  Pel*    dst = dstPic->getAddr( compID, partAddr );    //目标块地址

  Int     xFrac  = mv->getHor() & ((1<<shiftHor)-1);    //水平亚像素精度
  Int     yFrac  = mv->getVer() & ((1<<shiftVer)-1);    //垂直亚像素精度
  UInt    cxWidth  = width  >> refPic->getComponentScaleX(compID);
  UInt    cxHeight = height >> refPic->getComponentScaleY(compID);

  const ChromaFormat chFmt = cu->getPic()->getChromaFormat();

  if ( yFrac == 0 )   //垂直亚像素精度为0,则进行水平亚像素精度插值
  {
    m_if.filterHor(compID, ref, refStride, dst,  dstStride, cxWidth, cxHeight, xFrac, !bi, chFmt, bitDepth);
  }
  else if ( xFrac == 0 )    //水平亚像素精度为0,则进行垂直亚像素精度插值
  {
    m_if.filterVer(compID, ref, refStride, dst, dstStride, cxWidth, cxHeight, yFrac, true, !bi, chFmt, bitDepth);
  }
  else    //否则水平垂直都进行插值
  {
    Int   tmpStride = m_filteredBlockTmp[0].getStride(compID);
    Pel*  tmp       = m_filteredBlockTmp[0].getAddr(compID);    

    const Int vFilterSize = isLuma(compID) ? NTAPS_LUMA : NTAPS_CHROMA;

    //根据亚像素位置进行插值
    m_if.filterHor(compID, ref - ((vFilterSize>>1) -1)*refStride, refStride, tmp, tmpStride, cxWidth, cxHeight+vFilterSize-1, xFrac, false,      chFmt, bitDepth);
    m_if.filterVer(compID, tmp + ((vFilterSize>>1) -1)*tmpStride, tmpStride, dst, dstStride, cxWidth, cxHeight,               yFrac, false, !bi, chFmt, bitDepth);
  }
}

5、加权预测xWeightedPredictionUni和xWeightedPredictionBi

加权预测部分分为单向加权预测xWeightedPredictionUni和双向加权预测xWeightedPredictionBi。
这里简单介绍一下加权预测,对参考图像队列list0和list1加权得到预测像素,分为默认加权(w0=w1=0.5)预测和Explicit(w0和w1计算得到)加权预测。

单向加权预测xWeightedPredictionUni中调用了getWpScaling和addWeightUni函数,分别完成了计算权重列表和加权计算预测像素的工作。

//! weighted prediction for uni-pred
Void TComWeightPrediction::xWeightedPredictionUni(       TComDataCU *const pcCU,
                                                   const TComYuv    *const pcYuvSrc,
                                                   const UInt              uiPartAddr,
                                                   const Int               iWidth,
                                                   const Int               iHeight,
                                                   const RefPicList        eRefPicList,
                                                         TComYuv          *pcYuvPred,
                                                   const Int               iRefIdx_input)
{
  WPScalingParam  *pwp, *pwpTmp;    

  Int iRefIdx=iRefIdx_input;        
  if ( iRefIdx < 0 )
  {
    iRefIdx   = pcCU->getCUMvField( eRefPicList )->getRefIdx( uiPartAddr );
  }
  assert (iRefIdx >= 0);

  if ( eRefPicList == REF_PIC_LIST_0 )      //list0
  {
    getWpScaling(pcCU, iRefIdx, -1, pwp, pwpTmp);       //计算list0的权重列表
  }
  else
  {
    getWpScaling(pcCU, -1, iRefIdx, pwpTmp, pwp);       //计算list1的权重列表
  }
  addWeightUni( pcYuvSrc, pcCU->getSlice()->getSPS()->getBitDepths(), uiPartAddr, iWidth, iHeight, pwp, pcYuvPred );        //进行加权计算预测像素
}

双向加权预测xWeightedPredictionBi调用了getWpScaling、addWeightBi、addWeightUni分别完成计算权重列表、双向加权计算预测像素、单向加权计算预测像素的工作。值得注意的是,xWeightedPredictionBi只在双向预测xPredInterBi中被调用。

//! weighted prediction for bi-pred
Void TComWeightPrediction::xWeightedPredictionBi(       TComDataCU *const pcCU,
                                                  const TComYuv    *const pcYuvSrc0,
                                                  const TComYuv    *const pcYuvSrc1,
                                                  const Int               iRefIdx0,
                                                  const Int               iRefIdx1,
                                                  const UInt              uiPartIdx,
                                                  const Int               iWidth,
                                                  const Int               iHeight,
                                                        TComYuv          *rpcYuvDst )
{
  WPScalingParam  *pwp0;
  WPScalingParam  *pwp1;

  assert(pcCU->getSlice()->getPPS()->getWPBiPred());

  getWpScaling(pcCU, iRefIdx0, iRefIdx1, pwp0, pwp1);       //计算list0list1的权重列表

  if( iRefIdx0 >= 0 && iRefIdx1 >= 0 )
  {
      addWeightBi(pcYuvSrc0, pcYuvSrc1, pcCU->getSlice()->getSPS()->getBitDepths(), uiPartIdx, iWidth, iHeight, pwp0, pwp1, rpcYuvDst );        //进行双向加权计算预测像素

  }
  else if ( iRefIdx0 >= 0 && iRefIdx1 <  0 )
  {
      addWeightUni( pcYuvSrc0, pcCU->getSlice()->getSPS()->getBitDepths(), uiPartIdx, iWidth, iHeight, pwp0, rpcYuvDst );       //进行单向加权计算预测像素

  }
  else if ( iRefIdx0 <  0 && iRefIdx1 >= 0 )
  {
      addWeightUni( pcYuvSrc1, pcCU->getSlice()->getSPS()->getBitDepths(), uiPartIdx, iWidth, iHeight, pwp1, rpcYuvDst );       //进行单向加权计算预测像素

  }
  else
  {
    assert (0);
  }
}

你可能感兴趣的:(HEVC,HM,HEVC代码学习,hevc)