motionCompensation运动补偿,在帧间模式(merge和inter_ME)中,根据之前获取到的Qter精度的mv,就能在参考帧中得到pu的参考块,在pu的参考块位置处,通过亚像素插值来得到亚像素位置的pu的预测像素pred。
总之,MC运动补偿,就是根据运动向量mv,得到当前pu的预测像素。
motionCompensation调用了xPredInterUni函数进行单向预测的运动补偿,调用xPredInterBi函数进行双向预测的运动补偿,
VTM中,调用motionCompensation时,参数eRefPicList只有三种情况:REF_PIC_LIST_0、REF_PIC_LIST_1、REF_PIC_LIST_X,表示前向、后向、不确定。
Void InterPrediction::motionCompensation( PredictionUnit &pu, PelUnitBuf &predBuf, const RefPicList &eRefPicList )
{
CodingStructure &cs = *pu.cs;
const PPS &pps = *cs.pps;
const SliceType sliceType = cs.slice->getSliceType();
if( eRefPicList != REF_PIC_LIST_X ) //指明了pu的参考列表,单向预测
{ //使用加权预测
if( ( ( sliceType == P_SLICE && pps.getUseWP() ) || ( sliceType == B_SLICE && pps.getWPBiPred() ) ) )
{
xPredInterUni ( pu, eRefPicList, predBuf, true ); //单向预测 最后一个参数为true表示之后还要进行加权预测
xWeightedPredictionUni( pu, predBuf, eRefPicList, predBuf, -1, m_maxCompIDToPred ); //单向加权预测
}
else //不使用加权预测
{
xPredInterUni( pu, eRefPicList, predBuf, false ); //单向预测
}
}
else //没有指明参考列表,当作Bi处理
{
if( xCheckIdenticalMotion( pu ) ) //如果前后两个方向的参考poc相同,mv相同,则归为单向处理
{
xPredInterUni( pu, REF_PIC_LIST_0, predBuf, false ); //前向预测
}
else
{
xPredInterBi( pu, predBuf ); //双向预测 其中会进行双向预测的加权预测
}
}
return;
}
单向预测的运动补偿算法
xPredInterUni调用xPredInterBlk函数进行pu的每个通道的预测像素的获取
Void InterPrediction::xPredInterUni(const PredictionUnit& pu, const RefPicList& eRefPicList, PelUnitBuf& pcYuvPred, const Bool& bi )
{
const SPS &sps = *pu.cs->sps;
Int iRefIdx = pu.refIdx[eRefPicList]; //参考帧refIdx
Mv mv[3];
{
mv[0] = pu.mv[eRefPicList];
}
clipMv(mv[0], pu.cu->lumaPos(), sps);
for( UInt comp = COMPONENT_Y; comp < pcYuvPred.bufs.size() && comp <= m_maxCompIDToPred; comp++ )
{
const ComponentID compID = ComponentID( comp ); //Y/Cb/Cr
{ //pu块的每个通道,进行运动补偿,获取pu预测像素
xPredInterBlk( compID, pu, pu.cu->slice->getRefPic( eRefPicList, iRefIdx ), mv[0], pcYuvPred, bi, pu.cu->slice->clpRng( compID )
);
}
}
}
双向预测的运动补偿算法
双向运动补偿,可以看做是分别进行前向运动补偿和后向运动补偿,分别获得前向的预测像素和后向的预测像素
之后分情况进行加权预测
Void InterPrediction::xPredInterBi(PredictionUnit& pu, PelUnitBuf &pcYuvPred)
{
const PPS &pps = *pu.cs->pps;
const Slice &slice = *pu.cs->slice;
for (UInt refList = 0; refList < NUM_REF_PIC_LIST_01; refList++) //前后向两次单向补偿,合起来就是双向预测运动补偿
{
if( pu.refIdx[refList] < 0)
{
continue;
}
RefPicList eRefPicList = (refList ? REF_PIC_LIST_1 : REF_PIC_LIST_0); //参考列表
CHECK( pu.refIdx[refList] >= slice.getNumRefIdx( eRefPicList ), "Invalid reference index" );
m_iRefListIdx = refList;
//单向运动补偿获得的前向和后向预测像素,存储于m_acYuvPred
PelUnitBuf pcMbBuf = ( pu.chromaFormat == CHROMA_400 ?
PelUnitBuf(pu.chromaFormat, PelBuf(m_acYuvPred[refList][0], pcYuvPred.Y())) :
PelUnitBuf(pu.chromaFormat, PelBuf(m_acYuvPred[refList][0], pcYuvPred.Y()), PelBuf(m_acYuvPred[refList][1], pcYuvPred.Cb()), PelBuf(m_acYuvPred[refList][2], pcYuvPred.Cr())) );
if (pu.refIdx[0] >= 0 && pu.refIdx[1] >= 0) //pu的前后向预测皆可用
{
xPredInterUni ( pu, eRefPicList, pcMbBuf, true //单向运动补偿
);
}
else //单向预测
{
if( ( (pps.getUseWP() && slice.getSliceType() == P_SLICE) || (pps.getWPBiPred() && slice.getSliceType() == B_SLICE) ) )
{
xPredInterUni ( pu, eRefPicList, pcMbBuf, true );
}
else
{
xPredInterUni ( pu, eRefPicList, pcMbBuf, false ); //最后一个参数false,表示不会采用加权预测
}
}
}
//m_acYuvPred中存储的,就是前面通过单向运动补偿获得的前向和后向预测像素
//srcPred0为前向运动补偿的预测像素,srcPred1为后向运动补偿的预测像素
CPelUnitBuf srcPred0 = ( pu.chromaFormat == CHROMA_400 ?
CPelUnitBuf(pu.chromaFormat, PelBuf(m_acYuvPred[0][0], pcYuvPred.Y())) :
CPelUnitBuf(pu.chromaFormat, PelBuf(m_acYuvPred[0][0], pcYuvPred.Y()), PelBuf(m_acYuvPred[0][1], pcYuvPred.Cb()), PelBuf(m_acYuvPred[0][2], pcYuvPred.Cr())) );
CPelUnitBuf srcPred1 = ( pu.chromaFormat == CHROMA_400 ?
CPelUnitBuf(pu.chromaFormat, PelBuf(m_acYuvPred[1][0], pcYuvPred.Y())) :
CPelUnitBuf(pu.chromaFormat, PelBuf(m_acYuvPred[1][0], pcYuvPred.Y()), PelBuf(m_acYuvPred[1][1], pcYuvPred.Cb()), PelBuf(m_acYuvPred[1][2], pcYuvPred.Cr())) );
if( pps.getWPBiPred() && slice.getSliceType() == B_SLICE ) //加权预测
{
xWeightedPredictionBi( pu, srcPred0, srcPred1, pcYuvPred, m_maxCompIDToPred ); //B帧且采用加权预测时,双向加权预测
}
else if( pps.getUseWP() && slice.getSliceType() == P_SLICE )
{
xWeightedPredictionUni( pu, srcPred0, REF_PIC_LIST_0, pcYuvPred, -1, m_maxCompIDToPred ); //P帧且采用加权预测时,单向加权预测
}
else //平均加权预测
{
xWeightedAverage( pu, srcPred0, srcPred1, pcYuvPred, slice.getSPS()->getBitDepths(), slice.clpRngs() );
}
}
xPredInterBlk调用了filterHor和filterVer进行pu参考块位置的亚像素插值,得到pu预测像素
Void InterPrediction::xPredInterBlk ( const ComponentID& compID, const PredictionUnit& pu, const Picture* refPic, const Mv& _mv, PelUnitBuf& dstPic, const Bool& bi, const ClpRng& clpRng
)
{
const ChromaFormat chFmt = pu.chromaFormat;
const bool rndRes = !bi; //bi表示xPredInterBlk之后是否还需要进行加权预测
int shiftHor = 2 + ::getComponentScaleX( compID, chFmt );
int shiftVer = 2 + ::getComponentScaleY( compID, chFmt );
int xFrac = _mv.hor & ( ( 1 << shiftHor ) - 1 );
int yFrac = _mv.ver & ( ( 1 << shiftVer ) - 1 ); //mv
PelBuf &dstBuf = dstPic.bufs[compID]; //存入预测像素
unsigned width = dstBuf.width;
unsigned height = dstBuf.height;
CPelBuf refBuf;
{
Position offset = pu.blocks[compID].pos().offset( _mv.getHor() >> shiftHor, _mv.getVer() >> shiftVer ); //参考块的位置
refBuf = refPic->getRecoBuf( CompArea( compID, chFmt, offset, pu.blocks[compID].size() ) ); //参考块像素
} //注意,运动补偿时参考块用的是reco像素信息
if( yFrac == 0 ) //根据mv,分别对参考块进行横向纵向插值,获取当期pu的预测像素
{
m_if.filterHor(compID, (Pel*) refBuf.buf, refBuf.stride, dstBuf.buf, dstBuf.stride, width, height, xFrac, rndRes, chFmt, clpRng);
}
else if( xFrac == 0 )
{
m_if.filterVer(compID, (Pel*) refBuf.buf, refBuf.stride, dstBuf.buf, dstBuf.stride, width, height, yFrac, true, rndRes, chFmt, clpRng);
}
else
{
PelBuf tmpBuf = PelBuf(m_filteredBlockTmp[0][compID], pu.blocks[compID]);
Int vFilterSize = isLuma(compID) ? NTAPS_LUMA : NTAPS_CHROMA;
m_if.filterHor(compID, (Pel*) refBuf.buf - ((vFilterSize >> 1) - 1) * refBuf.stride, refBuf.stride, tmpBuf.buf, tmpBuf.stride, width, height + vFilterSize - 1, xFrac, false, chFmt, clpRng);
m_if.filterVer(compID, (Pel*) tmpBuf.buf + ((vFilterSize >> 1) - 1) * tmpBuf.stride, tmpBuf.stride, dstBuf.buf, dstBuf.stride, width, height, yFrac, false, rndRes, chFmt, clpRng);
}
}