HM14.0 filterHorLuma 和 filterVerLuma 插值函数以及 isFirst、isLast 的作用

1. filterHorLuma 函数

1.1 调用层次结构

filterHorLuma 函数中调用了 filterCopy 和 filterHor<NTAPS_LUMA> 函数,当对整像素位置进行插值时调用前者,对亚像素进行插值时调用后者。
  以下重点分析 filterHor<NTAPS_LUMA> 函数。

1.2 filterHor<NTAPS_LUMA>函数

filterHor<NTAPS_LUMA>函数调用了 filter< Int N, Bool isVertical, Bool isFirst, Bool isLast> 函数,分两种情况:filter< N, false, true, true > 和 filter< N, false, true, false > 。isLast 表示插值操作是否进入最后一步, isFirst 表示当前是否是插值操作的第一步。
  对于所有插值流程来说,或者无需进行水平方向亚像素插值,或者首先进行水平方向亚像素插值。所以filterHor<NTAPS_LUMA>函数中调用 filter 时 template 的 isFirst 参数总为 true。
  isLast 参数则取决于接下来是否需要进行垂直方向的亚像素插值。如果需要,说明插值操作还未结束,isLast 为 false。反之说明插值操作已经进入最后一步,isLast 为 true。

2. filterVerLuma 函数

filterVerLuma 同 filterHorLuma 具有相似的调用结构,但其调用的 filterVer<NTAPS_LUMA> 函数略有不同。

2.1 filterVer<NTAPS_LUMA>函数

filterHor<NTAPS_LUMA>函数调用了 filter< Int N, Bool isVertical, Bool isFirst, Bool isLast> 函数,分四种情况:isFirst 和 isLast 分别取 true 和 false 。因为是对垂直方向亚像素插值,所以 isVertical 为true。

3. HM14.0 调用上述两个函数的情况分析

3.1 MotionEstmation部分

在运动估计部分,要计算 PU 的运动矢量,xPatternSearchFracDIF 就是对 PU 进行亚像素插值的函数,这个函数中分别调用了 xExtDIFUpSamplingH 进行1/2 像素插值,调用 xExtDIFUpSamplingQ 进行1/4 像素插值。虽然在运动估计部分将插值分为了以上两个步骤,但编程的中心思想则是先进行水平方向亚像素插值,再进行垂直方向亚像素插值。

水平方向1/2亚像素插值举例:

//来自 xExtDIFUpSamplingH 函数
m_if.filterHorLuma(srcPtr, srcStride, m_filteredBlockTmp[0].getLumaAddr(), intStride, width+1, height+filterSize, 0, false);
m_if.filterHorLuma(srcPtr, srcStride, m_filteredBlockTmp[2].getLumaAddr(), intStride, width+1, height+filterSize, 2, false);
//来自 xExtDIFUpSamplingQ 函数
m_if.filterHorLuma(srcPtr, srcStride, intPtr, intStride, width, extHeight, 1, false);
m_if.filterHorLuma(srcPtr, srcStride, intPtr, intStride, width, extHeight, 3, false); 

垂直方向1/4亚像素插值举例:

  //以下情况,isFirst == false, isLast == true
  //水平整像素插值后的Y做垂直方向整像素插值,结果存储在m_filteredBlock[0][0]中
  intPtr = m_filteredBlockTmp[0].getLumaAddr() + halfFilterSize * intStride + 1;  
  dstPtr = m_filteredBlock[0][0].getLumaAddr();
  m_if.filterVerLuma(intPtr, intStride, dstPtr, dstStride, width+0, height+0, 0, false, true);
  //水平整像素插值后的Y做垂直方向1/2像素插值,结果存储在m_filteredBlock[2][0]中
  intPtr = m_filteredBlockTmp[0].getLumaAddr() + (halfFilterSize-1) * intStride + 1;  
  dstPtr = m_filteredBlock[2][0].getLumaAddr();
  m_if.filterVerLuma(intPtr, intStride, dstPtr, dstStride, width+0, height+1, 2, false, true);
  //水平1/2像素插值后的Y做垂直方向整像素插值,结果存储在m_filteredBlock[0][2]中
  intPtr = m_filteredBlockTmp[2].getLumaAddr() + halfFilterSize * intStride;
  dstPtr = m_filteredBlock[0][2].getLumaAddr();
  m_if.filterVerLuma(intPtr, intStride, dstPtr, dstStride, width+1, height+0, 0, false, true);
  //水平1/2像素插值后的Y做垂直方向1/2像素插值,结果存储在m_filteredBlock[2][2]中
  intPtr = m_filteredBlockTmp[2].getLumaAddr() + (halfFilterSize-1) * intStride;
  dstPtr = m_filteredBlock[2][2].getLumaAddr();
  m_if.filterVerLuma(intPtr, intStride, dstPtr, dstStride, width+1, height+1, 2, false, true);

由以上代码可知,在运动估计部分,水平方向插值永远是插值的第一步,而垂直方向插值则是插值的最后一步。

3.2 motionCompensation部分

motionCompensation函数里,首先判断当前 CU 是否需要进行双向补偿。如果是双向补偿,则将 isLast 置为 false。类似地,需要进行加权操作的也要将 isLast 置为 false。意思是,插值之后还不能确定当前像素的值,还要进行加权操作。之前有一个疑问是,双向补偿调用了两次 xPredInterUni 单向补偿函数,但每次都将 isLast 置为 false,那什么时候插值操作结束呢?答案是,双向补偿也需要进行加权预测,所以像素值是在加权预测时最终确定的。
下面的代码是 xPredInterUni 单向补偿函数中调用的对亮度分量进行补偿的函数。运动估计部分将亮度分量的预测结果SAD作为预测好坏的评价标准,但在运动补偿部分还需要补偿色度分量。

Void TComPrediction::xPredInterLumaBlk( TComDataCU *cu, TComPicYuv *refPic, UInt partAddr, TComMv *mv, Int width, Int height, TComYuv *&dstPic, Bool bi ) // bi 是双向预测的标志
{
  Int refStride = refPic->getStride();  
  Int refOffset = ( mv->getHor() >> 2 ) + ( mv->getVer() >> 2 ) * refStride;
  Pel *ref      = refPic->getLumaAddr( cu->getAddr(), cu->getZorderIdxInCU() + partAddr ) + refOffset;
  
  Int dstStride = dstPic->getStride();
  Pel *dst      = dstPic->getLumaAddr( partAddr );
  
  Int xFrac = mv->getHor() & 0x3;
  Int yFrac = mv->getVer() & 0x3;  
  
  if ( yFrac == 0 ) // 如果垂直方向的亚像素运动矢量为0,只需要进行水平方向的亚像素插值
  {
    m_if.filterHorLuma( ref, refStride, dst, dstStride, width, height, xFrac,       !bi );
  }
  else if ( xFrac == 0 ) // 如果水平方向的亚像素运动矢量为0,只需要进行垂直方向的亚像素插值
  {
    m_if.filterVerLuma( ref, refStride, dst, dstStride, width, height, yFrac, true, !bi );
  }
  else // 否则,先进行水平方向插值,后进行垂直方向插值
  {
    Int tmpStride = m_filteredBlockTmp[0].getStride();
    Short *tmp    = m_filteredBlockTmp[0].getLumaAddr();    
    Int filterSize = NTAPS_LUMA;
    Int halfFilterSize = ( filterSize >> 1 );   
    m_if.filterHorLuma(ref - (halfFilterSize-1)*refStride, refStride, tmp, tmpStride, width, height+filterSize-1, xFrac, false     );
    m_if.filterVerLuma(tmp + (halfFilterSize-1)*tmpStride, tmpStride, dst, dstStride, width, height,              yFrac, false, !bi);    
  }
}

4. 有关亚像素插值的其他大佬的博客

https://blog.csdn.net/cabbage2008/article/details/50612444 x265插值过程像素值的计算方法
https://blog.csdn.net/lin453701006/article/details/73188458#commentBox m_filteredBlockTmp[]和m_filteredBlock[][]
https://blog.csdn.net/lin453701006/article/details/72677630 运动补偿函数分析

5. 下一步更新计划

嗯,想写的很多,但是还没有全部看懂。

  • 插值过程像素值的计算方法,offset,shift变量的作用?
  • 插值过程分析,为什么m_filteredBlockTmp[]的宽高和预测块大小不一致?
  • emmm,我就是说说,不一定写
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值