今天来看运动补偿,推荐两篇博文:
http://blog.csdn.net/hevc_cjl/article/details/8457642
http://blog.csdn.net/nb_vol_1/article/details/55253979
运动补偿的功能是根据之前的局部图像来预测补偿当前的局部图像。而实际代码来看,主要完成的是亚像素插值补偿的工作。在运动估计中,得到的MV是亚像素精度的,而参考图像是整像素的,亚像素位置上是没有值的。如果通过MV找到的参考块是非整像素位置的,那么在参考图像上是没有这个参考块的。这就需要对参考图像进行插值,构造亚像素参考块。得到的这个参考块将用于计算残差。
实际运动补偿中的函数调用关系如下:
下面来看代码(没有具体看加权预测部分),运动补偿的入口函数是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;
}
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() );
}
}
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);
}
}
加权预测部分分为单向加权预测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); //计算list0和list1的权重列表
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);
}
}