HM编码器代码阅读(16)——帧间预测之AMVP模式(四)预测MV的获取

时间:2022-10-15 11:34:34

帧间预测的原理


AMVP的原理

    帧间预测的实质就是为当前的PU在参考帧中寻找一块最相似块(相似度的判断准则有SAD等方法)。但是参考图像通常都比较大,我们直接去搜索的话就太费时了,应该使用某种方法在参考图像中确定一个搜索起始点,然后再该搜索起始点的周围进行搜索,那么就能降低搜索的时间了。
    AMVP模式就提供了这样一种方法,在开始搜索之前,先为当前PU预测出一个MV,这个预测的MV就被称为MVP,预测的MV可以直接从空域或者时域上的相邻块直接得到,因为相邻块有多个,因此MVP也会有多个,这些MVP组成了MVP候选列表,我们需要从中选择最优的一个来作为实际的MVP,得到MVP之后,我们根MVP(MVP实际也是一个MV)来确定搜索的起始点,然后在搜索起始点的附近按照某种方法做搜索,最后得到一个最优的MV,这个MV就是实际MV,MV确定了参考块的位置,参考块与当前PU相减得到残差,达到了数据压缩的目的;
同时MV与MVP相减得到MV残差也就是MVD,也能够达到数据压缩的目的。
    它的大致工作流程是:
    1、根据某种方法获取MVP候选列表
    2、从候选列表中选出最优的一个MVP
    3、根据MVP确定运动估计的起始点
    4、在起始点附近,按照某种方法进行搜索
    5、搜索完毕之后得到最优的MV
    6、由MV确定参考块在参考图像中的位置
    7、参考块减去当前块(PU)得到残差块
    8、MV减去MVP得到MVD
    9、通过7和8两个步骤就能达到数据压缩的目的

merge模式的原理

    帧间预测的目的就是要得到一个MV(运动向量),然后根据该MV确定参考块在参考图像中的位置,
但是由于临近块的相似性(比如当前块和临近块都属于同一个物体,在镜头移动的时候,它们移动的距离和方向当然是相同的),因此很多时候我们并不需要去计算MV,我们把相邻块的MV直接当作当前块的MV。和AMVP相似,我们通过相邻块得到一个MVP候选列表,从中选出最优的一个MVP作为当前块的MV,然后根据该MV直接确定参考块的位置,确定了参考块之后就能计算残差了。因为MVP和MV相同,因此不存在MVD,因此编码的时候只需要编码MV(MVP)在候选列表中索引即可,不再需要编码MVD,解码可以按照类似的方法构造MVP候选列表,然后依据传送过来的索引就能得到MV了。
    工作流程是:
    1、根据某种方法获取MVP候选列表
    2、从候选列表中选出最优的一个MVP,同时得到该MVP在候选列表中的索引
    3、把该MVP作为当前块的MV
    4、根据MV确定参考块在参考图像中的位置
    5、参考块减去当前块得到残差块
    6、因为MVP和MV相同,因此没有MVD,只需把残差系数和MVP的索引传给解码器就行了



skip模式的原理

    skip模式是merge模式的一种特例。按照merge模式得到MV之后,如果编码器根据某种方法判断了当前块和参考块基本一样,那么不需要传输残差数据,只需要传送MV的索引和一个标志,来表明当前块可以直接从参考块得到。

xEstimateMvPredAMVP的工作流程

AMVP中MVP的获取就是通过xEstimateMvPredAMVP这个函数得到的
1、判断bFilled标识,该标识表示MVP候选列表是否已经建立好
2、如果bFilled是false,通过fillMvpCand获取MVP候选列表;否则,不需要重新建立MVP候选列表
3、先把MVP候选列表中的第一个MVP作为最优的MVP
4、如果候选列表中MVP的数量小于等于1,那么直接把步骤3选出的MVP返回
5、如果bFilled是true,表示MVP候选列表是原来已经建立好的,那么直接根据PU的相关信息得到最优的MVP,然后返回
6、遍历MVP候选列表,选出代价最小的MVP作为当前PU的MVP,并设置相关信息,然后返回

#if ZERO_MVD_EST
Void TEncSearch::xEstimateMvPredAMVP( TComDataCU* pcCU, TComYuv* pcOrgYuv, UInt uiPartIdx, RefPicList eRefPicList, Int iRefIdx, TComMv& rcMvPred, Bool bFilled, UInt* puiDistBiP, UInt* puiDist )
#else
Void TEncSearch::xEstimateMvPredAMVP( TComDataCU* pcCU, TComYuv* pcOrgYuv, UInt uiPartIdx, RefPicList eRefPicList, Int iRefIdx, TComMv& rcMvPred, Bool bFilled, UInt* puiDistBiP )
#endif
{
AMVPInfo* pcAMVPInfo = pcCU->getCUMvField(eRefPicList)->getAMVPInfo();

TComMv cBestMv;
Int iBestIdx = 0;
TComMv cZeroMv;
TComMv cMvPred;
UInt uiBestCost = MAX_INT;
UInt uiPartAddr = 0;
Int iRoiWidth, iRoiHeight;
Int i;

// 得到此次分割的索引和大小
pcCU->getPartIndexAndSize( uiPartIdx, uiPartAddr, iRoiWidth, iRoiHeight );

// Fill the MV Candidates
// 获取MVP(即预测的MV),MVP存放在pcAMVPInfo中
if (!bFilled)
{
pcCU->fillMvpCand( uiPartIdx, uiPartAddr, eRefPicList, iRefIdx, pcAMVPInfo );
}

// initialize Mvp index & Mvp
// 最优MVP的默认索引是0
iBestIdx = 0;

// 最优MVP默认是列表的第一个
cBestMv = pcAMVPInfo->m_acMvCand[0];
#if !ZERO_MVD_EST

// 如果MVP候选列表中MVP的数量是0或者1
if (pcAMVPInfo->iN <= 1)
{
// 得到了最优的MVP
rcMvPred = cBestMv;

pcCU->setMVPIdxSubParts( iBestIdx, eRefPicList, uiPartAddr, uiPartIdx, pcCU->getDepth(uiPartAddr));
pcCU->setMVPNumSubParts( pcAMVPInfo->iN, eRefPicList, uiPartAddr, uiPartIdx, pcCU->getDepth(uiPartAddr));

// 如果参考列表是list1(表示这是B slice)
if(pcCU->getSlice()->getMvdL1ZeroFlag() && eRefPicList==REF_PIC_LIST_1)
{
#if ZERO_MVD_EST
(*puiDistBiP) = xGetTemplateCost( pcCU, uiPartIdx, uiPartAddr, pcOrgYuv, &m_cYuvPredTemp, rcMvPred, 0, AMVP_MAX_NUM_CANDS, eRefPicList, iRefIdx, iRoiWidth, iRoiHeight, uiDist );
#else
(*puiDistBiP) = xGetTemplateCost( pcCU, uiPartIdx, uiPartAddr, pcOrgYuv, &m_cYuvPredTemp, rcMvPred, 0, AMVP_MAX_NUM_CANDS, eRefPicList, iRefIdx, iRoiWidth, iRoiHeight);
#endif
}
// 返回
return;
}
#endif
// 如果MVP候选列表已经填充完毕,那么直接返回MVP就行了
// 注意:fillMvpCand并不会修改bFilled的值,bFilled的值是函数传进来的
if (bFilled)
{
assert(pcCU->getMVPIdx(eRefPicList,uiPartAddr) >= 0);
// 选择相应的MVP
rcMvPred = pcAMVPInfo->m_acMvCand[pcCU->getMVPIdx(eRefPicList,uiPartAddr)];
return;
}

m_cYuvPredTemp.clear();
#if ZERO_MVD_EST
UInt uiDist;
#endif
//-- Check Minimum Cost.
// 遍历每一个MVP,选出代价最小的那个
for ( i = 0 ; i < pcAMVPInfo->iN; i++)
{
UInt uiTmpCost;
#if ZERO_MVD_EST
uiTmpCost = xGetTemplateCost( pcCU, uiPartIdx, uiPartAddr, pcOrgYuv, &m_cYuvPredTemp, pcAMVPInfo->m_acMvCand[i], i, AMVP_MAX_NUM_CANDS, eRefPicList, iRefIdx, iRoiWidth, iRoiHeight, uiDist );
#else
uiTmpCost = xGetTemplateCost( pcCU, uiPartIdx, uiPartAddr, pcOrgYuv, &m_cYuvPredTemp, pcAMVPInfo->m_acMvCand[i], i, AMVP_MAX_NUM_CANDS, eRefPicList, iRefIdx, iRoiWidth, iRoiHeight);
#endif
if ( uiBestCost > uiTmpCost )
{
uiBestCost = uiTmpCost;
cBestMv = pcAMVPInfo->m_acMvCand[i];
iBestIdx = i;
(*puiDistBiP) = uiTmpCost;
#if ZERO_MVD_EST
(*puiDist) = uiDist;
#endif
}
}

m_cYuvPredTemp.clear();

// Setting Best MVP
rcMvPred = cBestMv;
pcCU->setMVPIdxSubParts( iBestIdx, eRefPicList, uiPartAddr, uiPartIdx, pcCU->getDepth(uiPartAddr));
pcCU->setMVPNumSubParts( pcAMVPInfo->iN, eRefPicList, uiPartAddr, uiPartIdx, pcCU->getDepth(uiPartAddr));
return;
}


MVP候选列表的建立

AMVP模式的候选列表,列表的长度是2,建立流程如下
     (1)空域列表的建立。假设当前PU的左下角是A0,左侧是A1,左上角是B2,上方是B1,右上角是B0。当前PU的左侧和上方需要各产生一个候选MV。对于左侧的候选MV的筛选,处理顺序是A0->A1->scaled A0->scaled A1;对上侧的候选MV的筛选,处理的顺序是B0->B1->B2(如果这几个都不存在,那么继续处理-> scaled B0-> scaled B2)。对于左侧(上方)来说,只要找到一个候选MV,就不继续处理后面的候选者了
     (2)时域列表的建立。与空域情况不同,时域候选列表不能直接使用候选块的运动信息,需要根据当前帧和参考帧之间的位置关系做相应的伸缩调整。时域最多只能提供一个候选MV。如果此时候选列表的候选MV的数量还不足2个,那么需要填充零向量。

AMVP模式的MVP候选列表是通过fillMvpCand函数建立的

Void TComDataCU::fillMvpCand ( UInt uiPartIdx, UInt uiPartAddr, RefPicList eRefPicList, Int iRefIdx, AMVPInfo* pInfo )
{
TComMv cMvPred;
Bool bAddedSmvp = false;

pInfo->iN = 0;
if (iRefIdx < 0)
{
return;
}

//-- Get Spatial MV
UInt uiPartIdxLT, uiPartIdxRT, uiPartIdxLB;
// 在宽度(横向)上有多少个分割数
UInt uiNumPartInCUWidth = m_pcPic->getNumPartInWidth();
Bool bAdded = false;

// 该函数调用的三个参数是,当前分割的索引,左下角分割的索引,右上角分割的索引
deriveLeftRightTopIdx( uiPartIdx, uiPartIdxLT, uiPartIdxRT );
deriveLeftBottomIdx( uiPartIdx, uiPartIdxLB );
// 空域候选MV的选择方式
// 要找出两个候选MV
// 假设 左下角a0,左边a1,右上角b0,上方b1,左上角b2
// 首先 按照a0->a1->scale a0->scale a1的顺序找到一个候选MV,只要找到一个就不再继续判断后面
// 然后 按照scale b0->scale b1->scale b2->b0->b1->b2的顺序再找出一个候选MV,只有在这个顺序中找到一个,就不再判断后面的了

/*
比例伸缩模式(scaled)是当参考块存在,当前Pu的参考图像跟参考块的参考图像不一致时才选用的方式(因为是空域MV选择,即参考块和当前块在同一帧中,因此才会出现
当前块和参考块参考的图像是否一致这种问题;如果是时域MV候选,那么当前块和参考块的参考图像肯定是不一致的)。
具体计算方法如下公式所示,curMV为当前Pu的候选MV,td与tb分别为当前块与参考块到他们的参考图像间的距离,colMV 为参考块的mv。
*/


TComDataCU* tmpCU = NULL;
UInt idx;
// 得到左下角的相邻的cu
tmpCU = getPUBelowLeft(idx, uiPartIdxLB);
bAddedSmvp = (tmpCU != NULL) && (tmpCU->getPredictionMode(idx) != MODE_INTRA);

// 没过没有左下角的
if (!bAddedSmvp)
{
// 得到左边的相邻的cu
tmpCU = getPULeft(idx, uiPartIdxLB);
bAddedSmvp = (tmpCU != NULL) && (tmpCU->getPredictionMode(idx) != MODE_INTRA);
}

// Left predictor search
// 添加到候选列表中
bAdded = xAddMVPCand( pInfo, eRefPicList, iRefIdx, uiPartIdxLB, MD_BELOW_LEFT);
if (!bAdded)
{
// 左边相邻的cu
bAdded = xAddMVPCand( pInfo, eRefPicList, iRefIdx, uiPartIdxLB, MD_LEFT );
}

if(!bAdded)
{
bAdded = xAddMVPCandOrder( pInfo, eRefPicList, iRefIdx, uiPartIdxLB, MD_BELOW_LEFT);
if (!bAdded)
{
xAddMVPCandOrder( pInfo, eRefPicList, iRefIdx, uiPartIdxLB, MD_LEFT );
}
}

// Above predictor search
// 右上角
bAdded = xAddMVPCand( pInfo, eRefPicList, iRefIdx, uiPartIdxRT, MD_ABOVE_RIGHT);

if (!bAdded)
{
// 上方
bAdded = xAddMVPCand( pInfo, eRefPicList, iRefIdx, uiPartIdxRT, MD_ABOVE);
}

if(!bAdded)
{
// 左上角
xAddMVPCand( pInfo, eRefPicList, iRefIdx, uiPartIdxLT, MD_ABOVE_LEFT);
}

if (!bAddedSmvp)
{
bAdded = xAddMVPCandOrder( pInfo, eRefPicList, iRefIdx, uiPartIdxRT, MD_ABOVE_RIGHT);
if (!bAdded)
{
bAdded = xAddMVPCandOrder( pInfo, eRefPicList, iRefIdx, uiPartIdxRT, MD_ABOVE);
}

if(!bAdded)
{
xAddMVPCandOrder( pInfo, eRefPicList, iRefIdx, uiPartIdxLT, MD_ABOVE_LEFT);
}
}

// 判断列表中候选者的数量,如果等于2,那么进入下面的判断
if ( pInfo->iN == 2 )
{
if ( pInfo->m_acMvCand[ 0 ] == pInfo->m_acMvCand[ 1 ] )
{
pInfo->iN = 1;
}
}

// 判断是否启用时域的MVP功能(TMVP中的T即时域的意思),如果启用了,那么选择时域候选MV
// 选择的参考块的位置位于 (当前cu所在的LCU的右下角的相邻cu),注意这是位置上,因为实际上参考块在另一个帧
// 如果不存在,那么直接用该位置的mv即可——若H位置的同位Pu(邻近已编码图像对应位置Pu)不可用,则用位置的同位Pu代替
// 缩放的方式和上面的一样
if ( getSlice()->getEnableTMVPFlag() )
{
// Get Temporal Motion Predictor
Int iRefIdx_Col = iRefIdx;
TComMv cColMv;
UInt uiPartIdxRB;
UInt uiAbsPartIdx;
UInt uiAbsPartAddr;

deriveRightBottomIdx( uiPartIdx, uiPartIdxRB );
uiAbsPartAddr = m_uiAbsIdxInLCU + uiPartAddr;

//---- co-located RightBottom Temporal Predictor (H) ---//
uiAbsPartIdx = g_auiZscanToRaster[uiPartIdxRB];
Int uiLCUIdx = -1;
if ( ( m_pcPic->getCU(m_uiCUAddr)->getCUPelX() + g_auiRasterToPelX[uiAbsPartIdx] + m_pcPic->getMinCUWidth() ) >= m_pcSlice->getSPS()->getPicWidthInLumaSamples() ) // image boundary check
{
}
else if ( ( m_pcPic->getCU(m_uiCUAddr)->getCUPelY() + g_auiRasterToPelY[uiAbsPartIdx] + m_pcPic->getMinCUHeight() ) >= m_pcSlice->getSPS()->getPicHeightInLumaSamples() )
{
}
else
{
if ( ( uiAbsPartIdx % uiNumPartInCUWidth < uiNumPartInCUWidth - 1 ) && // is not at the last column of LCU
( uiAbsPartIdx / uiNumPartInCUWidth < m_pcPic->getNumPartInHeight() - 1 ) ) // is not at the last row of LCU
{
uiAbsPartAddr = g_auiRasterToZscan[ uiAbsPartIdx + uiNumPartInCUWidth + 1 ];
uiLCUIdx = getAddr();
}
else if ( uiAbsPartIdx % uiNumPartInCUWidth < uiNumPartInCUWidth - 1 ) // is not at the last column of LCU But is last row of LCU
{
uiAbsPartAddr = g_auiRasterToZscan[ (uiAbsPartIdx + uiNumPartInCUWidth + 1) % m_pcPic->getNumPartInCU() ];
}
else if ( uiAbsPartIdx / uiNumPartInCUWidth < m_pcPic->getNumPartInHeight() - 1 ) // is not at the last row of LCU But is last column of LCU
{
uiAbsPartAddr = g_auiRasterToZscan[ uiAbsPartIdx + 1 ];
uiLCUIdx = getAddr() + 1;
}
else //is the right bottom corner of LCU
{
uiAbsPartAddr = 0;
}
}

// 对MV进行缩放处理
if ( uiLCUIdx >= 0 && xGetColMVP( eRefPicList, uiLCUIdx, uiAbsPartAddr, cColMv, iRefIdx_Col ) )
{
// 存进MVP列表候选列表中
pInfo->m_acMvCand[pInfo->iN++] = cColMv;
}
else
{
UInt uiPartIdxCenter;
UInt uiCurLCUIdx = getAddr();
xDeriveCenterIdx( uiPartIdx, uiPartIdxCenter );
if (xGetColMVP( eRefPicList, uiCurLCUIdx, uiPartIdxCenter, cColMv, iRefIdx_Col ))
{
pInfo->m_acMvCand[pInfo->iN++] = cColMv;
}
}
//---- co-located RightBottom Temporal Predictor ---//
}

if (pInfo->iN > AMVP_MAX_NUM_CANDS)
{
pInfo->iN = AMVP_MAX_NUM_CANDS;
}

// 如果没有达到指定的数量,就填充0向量
while (pInfo->iN < AMVP_MAX_NUM_CANDS)
{
pInfo->m_acMvCand[pInfo->iN].set(0,0);
pInfo->iN++;
}

// MVP候选列表中MV存放的顺序是先空域MV(左边的MV,然后上方的MV)然后再时域MV
return ;
}