HM编码器代码阅读(31)——帧间预测之AMVP/Merge模式(六)运动补偿

运动补偿


原理

    说实话一直很难理解运动补偿中“补偿”二字的意思,在参考了 http://blog.csdn.net/hevc_cjl/article/details/8457642 和百度百科以及分析的源代码之后,我大致猜测了一下它的功能:
    百科上说“运动补偿是通过先前的局部图像来预测、补偿当前的局部图像,它是减少帧序列冗余信息的有效方法”,通过前面的运动估计我们得到了MV(运动向量),大部分情况下MV是亚像素精度的,MV的作用就是定位参考块在参考帧中的位置,但是亚像素的MV定位出来的位置是没有像素点的(亚像素就是指该位置在两个像素之间),换句话说非整像素精度的MV定位出来的地方没有像素点(即没有像素块),那么我们需要使用现有的像素点去构造亚像素点,这样通过MV找到位置才有参考块。运动补偿实际干的就是这么回事它通过MV和现有的像素块去构造一个亚像素块,这个新被创建出来的像素块就是当前PU的参考块。这样,得到了MV和参考块之后就可以进行后续的工作了。


运动补偿入口函数

    motionCompensation()完成了运动补偿的工作;

    motionCompensation()调用了xPredInterUni()完成了单向预测的运动补偿;而调用xPredInterBi()完成了双向预测的运动补偿,它实际调用xPredInterBi和xWeightedPredictionBi来完成相应的工作。其中xPredInterUni()调用xPredInterBlk()完成一个分量块的运动补偿。而xPredInterBlk()调用了TComInterpolationFilter类的filterHor()和filterVer()完成了亚像素的插值工作。

    motionCompensation的流程:
    1、如果指明了PU,那么只对这个PU进行处理,如果没有指明PU,那么对CU下面的所有PU进行处理。
    2、对于一个PU,如果指定了参考列表,那么表示进行单向运动补偿(双向运动补偿可以通过两次单向操作来完成);如果没有指定参考列表,那么默认进行双向运动补偿,但是在操作之前先确认PU两个方向上的参考帧是否相同,如果相同,表示只有一个参考帧那么它实际还是进行单向运动补偿,否则使用双向运动补偿。
    3、无论是单向运动补偿还是双向运动补偿,都需要在亚像素插值工作完成之后,检测是否需要进行加权预测,相关的加权操作是在xWeightedPredictionUni中完成的,这个函数根据权重参数对目标像素块进行权重转换,对每一个像素通过一个公式去重新计算它的值。单向预测的运动补偿中,xWeightedPredictionUni跟在xPredInterUni函数的后面,在双向预测的运动补偿中,xWeightedPredictionUni在xPredInterBi函数里面。

下面的它的流程图和代码:


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 );
		
		// 有效的参考列表,即明确的标明了使用哪个参考列表,那么就在对应的方向上进行单向预测
		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 );
			}
		}
		// 没有指明明确的参考列表,那么判断PU两个方向上的参考帧是否一样
		else
		{
			// 如果PU的两个参考列表是相同的,即它们的运动是一致的
			// 那么直接使用单向预测
			if ( xCheckIdenticalMotion( pcCU, uiPartAddr ) )
			{
				xPredInterUni (pcCU, uiPartAddr, iWidth, iHeight, REF_PIC_LIST_0, pcYuvPred );
			}
			// 否则使用双向预测
			else
			{
				xPredInterBi  (pcCU, uiPartAddr, iWidth, iHeight, pcYuvPred );
			}
		}
		return;
	}

	// 否则处理CU下的所有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;
}


单向预测的运动补偿

Void TComPrediction::xPredInterUni ( TComDataCU* pcCU, UInt uiPartAddr, Int iWidth, Int iHeight, RefPicList eRefPicList, TComYuv*& rpcYuvPred, Bool bi )
{
	Int         iRefIdx     = pcCU->getCUMvField( eRefPicList )->getRefIdx( uiPartAddr );           assert (iRefIdx >= 0);
	TComMv      cMv         = pcCU->getCUMvField( eRefPicList )->getMv( uiPartAddr );
	pcCU->clipMv(cMv);
	xPredInterLumaBlk  ( pcCU, pcCU->getSlice()->getRefPic( eRefPicList, iRefIdx )->getPicYuvRec(), uiPartAddr, &cMv, iWidth, iHeight, rpcYuvPred, bi );
	xPredInterChromaBlk( pcCU, pcCU->getSlice()->getRefPic( eRefPicList, iRefIdx )->getPicYuvRec(), uiPartAddr, &cMv, iWidth, iHeight, rpcYuvPred, bi );
}

对亮度块进行亚像素插值工作

Void TComPrediction::xPredInterLumaBlk( TComDataCU *cu, TComPicYuv *refPic, UInt partAddr, TComMv *mv, Int width, Int height, TComYuv *&dstPic, Bool 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 )
	{
		m_if.filterHorLuma( ref, refStride, dst, dstStride, width, height, xFrac,       !bi );
	}
	else if ( xFrac == 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);    
	}
}

双向预测运动补偿

Void TComPrediction::xPredInterBi ( TComDataCU* pcCU, UInt uiPartAddr, Int iWidth, Int iHeight, TComYuv*& rpcYuvPred )
{
	TComYuv* pcMbYuv;
	Int      iRefIdx[2] = {-1, -1};

	// 执行两次单向预测的运动补偿,就可以完成双向预测的运动补偿了
	for ( Int iRefList = 0; iRefList < 2; iRefList++ )
	{
		RefPicList eRefPicList = (iRefList ? REF_PIC_LIST_1 : REF_PIC_LIST_0);
		iRefIdx[iRefList] = pcCU->getCUMvField( eRefPicList )->getRefIdx( uiPartAddr );

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

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

		pcMbYuv = &m_acYuvPred[iRefList];

		// 单向预测的运动补偿
		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[0], &m_acYuvPred[1], iRefIdx[0], iRefIdx[1], uiPartAddr, iWidth, iHeight, rpcYuvPred );
	}  
	else if ( pcCU->getSlice()->getPPS()->getUseWP() && pcCU->getSlice()->getSliceType() == P_SLICE )
	{
		xWeightedPredictionUni( pcCU, &m_acYuvPred[0], uiPartAddr, iWidth, iHeight, REF_PIC_LIST_0, rpcYuvPred ); 
	}
	else
	{
		xWeightedAverage( &m_acYuvPred[0], &m_acYuvPred[1], iRefIdx[0], iRefIdx[1], uiPartAddr, iWidth, iHeight, rpcYuvPred );
	}
}

加权预测


单向加权预测


// getWpScaling的作用就是设置权重table的参数
// addWeightUni根据权重参数对目标像素块进行权重转换,即对每一个像素通过一个公式去重新计算它的值
Void TComWeightPrediction::xWeightedPredictionUni( TComDataCU* pcCU, TComYuv* pcYuvSrc, UInt uiPartAddr, Int iWidth, Int iHeight, RefPicList eRefPicList, TComYuv*& rpcYuvPred, Int iRefIdx)
{ 
	wpScalingParam  *pwp, *pwpTmp;
	if ( iRefIdx < 0 )
	{
		iRefIdx   = pcCU->getCUMvField( eRefPicList )->getRefIdx( uiPartAddr );
	}
	assert (iRefIdx >= 0);

	if ( eRefPicList == REF_PIC_LIST_0 )
	{
		getWpScaling(pcCU, iRefIdx, -1, pwp, pwpTmp);
	}
	else
	{
		getWpScaling(pcCU, -1, iRefIdx, pwpTmp, pwp);
	}
	addWeightUni( pcYuvSrc, uiPartAddr, iWidth, iHeight, pwp, rpcYuvPred );
}

双向加权预测

/*
** 双向加权预测
*/
Void TComWeightPrediction::xWeightedPredictionBi( TComDataCU* pcCU, TComYuv* pcYuvSrc0, TComYuv* pcYuvSrc1, Int iRefIdx0, Int iRefIdx1, UInt uiPartIdx, Int iWidth, Int iHeight, TComYuv* rpcYuvDst )
{
	wpScalingParam  *pwp0, *pwp1;
	TComPPS         *pps = pcCU->getSlice()->getPPS();
	assert( pps->getWPBiPred());

	// getWpScaling的作用就是设置权重table的参数
	getWpScaling(pcCU, iRefIdx0, iRefIdx1, pwp0, pwp1);


	// addWeightUni根据权重参数对目标像素块进行权重转换,即对每一个像素通过一个公式去重新计算它的值
	if( iRefIdx0 >= 0 && iRefIdx1 >= 0 )
	{
		addWeightBi(pcYuvSrc0, pcYuvSrc1, uiPartIdx, iWidth, iHeight, pwp0, pwp1, rpcYuvDst );
	}
	else if ( iRefIdx0 >= 0 && iRefIdx1 <  0 )
	{
		addWeightUni( pcYuvSrc0, uiPartIdx, iWidth, iHeight, pwp0, rpcYuvDst );
	}
	else if ( iRefIdx0 <  0 && iRefIdx1 >= 0 )
	{
		addWeightUni( pcYuvSrc1, uiPartIdx, iWidth, iHeight, pwp1, rpcYuvDst );
	}
	else
	{
		assert (0);
	}
}

平均加权预测

Void TComPrediction::xWeightedAverage( TComYuv* pcYuvSrc0, TComYuv* pcYuvSrc1, Int iRefIdx0, Int iRefIdx1, UInt uiPartIdx, Int iWidth, Int iHeight, TComYuv*& rpcYuvDst )
{
	if( iRefIdx0 >= 0 && iRefIdx1 >= 0 )
	{
		rpcYuvDst->addAvg( pcYuvSrc0, pcYuvSrc1, uiPartIdx, iWidth, iHeight );
	}
	else if ( iRefIdx0 >= 0 && iRefIdx1 <  0 )
	{
		pcYuvSrc0->copyPartToPartYuv( rpcYuvDst, uiPartIdx, iWidth, iHeight );
	}
	else if ( iRefIdx0 <  0 && iRefIdx1 >= 0 )
	{
		pcYuvSrc1->copyPartToPartYuv( rpcYuvDst, uiPartIdx, iWidth, iHeight );
	}
}




  • 7
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值