目的:为了提高merge模式下双向预测MV的准确性
基本思路:双向预测是在list0和list1中分别寻找一个运动向量,然后将MV0和MV1所指向的预测块进行加权得到最终预测块,而DMVR技术不是直接使用MV0和MV1,而是在MV0和MV1周围1-2个像素范围内搜索一个更加精确的MV,具体做法是直接计算前后两个经过偏移后的预测块(下图中红色的部分)之间的SAD值,选取SAD值较小的那个偏移后的MV的组合,即为要选取的修正后的MV,并最终利用修正后的MV生成当前块的双向加权预测值。
注意:DMVR生成的修正MV用于生成帧间预测值和后续连续图像编码的时域运动向量预测值(TMVR);原始的MV用于去方块效应和后续CU编码的空域运动向量预测值(SMVP)
当满足下列所有条件时,可以使用该技术,即dmvrFlag被置为1
- CU使用merge模式,且是双向预测
- 两个参考帧分别位于当当前帧之前和当前帧之后
- 当前图片与前向参考图片的POC差和当前图片与后向参考图片的POC差相等
- CU至少需要有128个像素点
- CU的宽和高都大于或等于8
- BCW使用相等的权值
- 当前块使不使用WP模式
所以条件在具体代码中实现如下:
bool PU::checkDMVRCondition(const PredictionUnit& pu) //传入的参数为当前编码的PU
{
WPScalingParam *wp0;//前向WP加权权重
WPScalingParam *wp1;//后向WP加权权重
int refIdx0 = pu.refIdx[REF_PIC_LIST_0]; //前向参考帧在L0中的索引值
int refIdx1 = pu.refIdx[REF_PIC_LIST_1]; //后向参考帧在L1中的索引值
pu.cu->slice->getWpScaling(REF_PIC_LIST_0, refIdx0, wp0); //获取前向WP的权重
pu.cu->slice->getWpScaling(REF_PIC_LIST_1, refIdx1, wp1); //获取后向WP的权重
if (pu.cs->sps->getUseDMVR() && (!pu.cs->picHeader->getDisDmvrFlag()))
{
//满足下列所有条件函数的返回值为真
return pu.mergeFlag //为merge模式
&& pu.mergeType == MRG_TYPE_DEFAULT_N //为常规的merge模式
&& !pu.ciipFlag //非CIIP模式
&& !pu.cu->affine //非Affine模式
&& !pu.mmvdMergeFlag //非MMVD_Merge模式
&& !pu.cu->mmvdSkip //非mmvd_Skip模式
&& PU::isBiPredFromDifferentDirEqDistPoc(pu) //当前图片与前向参考图片的POC差和当前图片与后向参考图片的POC差相等
&& (pu.lheight() >= 8) //PU的高大于或等于8
&& (pu.lwidth() >= 8) //PU的宽大于或等于8
&& ((pu.lheight() * pu.lwidth()) >= 128) //PU的尺寸大于或等于128
&& (pu.cu->BcwIdx == BCW_DEFAULT)
#if JVET_Q0128_DMVR_BDOF_ENABLING_CONDITION
//亮度分量和相应的色度分量的前向MV权重和后向MV权重存在flag都等于0(即都不可以用)
&& ((!wp0[COMPONENT_Y].bPresentFlag) && (!wp0[COMPONENT_Cb].bPresentFlag) && (!wp0[COMPONENT_Cr].bPresentFlag) && (!wp1[COMPONENT_Y].bPresentFlag) && (!wp1[COMPONENT_Cb].bPresentFlag) && (!wp1[COMPONENT_Cr].bPresentFlag))
#else
&& ((!wp0[COMPONENT_Y].bPresentFlag) && (!wp1[COMPONENT_Y].bPresentFlag))
#endif
#if JVET_Q0487_SCALING_WINDOW_ISSUES //缩放窗口问题
&& ( refIdx0 < 0 ? true : (pu.cu->slice->getRefPic( REF_PIC_LIST_0, refIdx0 )->isRefScaled( pu.cs->pps ) == false) )
&& ( refIdx1 < 0 ? true : (pu.cu->slice->getRefPic( REF_PIC_LIST_1, refIdx1 )->isRefScaled( pu.cs->pps ) == false) )
#else
&& ( refIdx0 < 0 ? true : pu.cu->slice->getScalingRatio( REF_PIC_LIST_0, refIdx0 ) == SCALE_1X ) && ( refIdx1 < 0 ? true : pu.cu->slice->getScalingRatio( REF_PIC_LIST_1, refIdx1 ) == SCALE_1X )
#endif
;
}
else
{
return false;
}
}
DMVR技术在VTM8.0中的具体实现
- 第一步:对初始点的周边位置进行搜索,以寻求更优的MV的偏移位置
由于在VTM中需要计算前后两向的两个候选MV的预测块之间的SAD值,因此使得每一个搜索点处MV的偏移都要遵循一个镜像原则,意思就是前后两个初始MV都必须同时使用同一个搜索点处的MV的偏移,然后按照镜像规则,前向初始点加一个偏移,后向的初始点减去对应的相同的偏移,具体公式如下所示:
M V L 0 ′ = M V L 0 + M V o f f s e t M V L 1 ′ = M V L 1 − M V o f f s e t MV_{L_0}'=MV_{L_0}+MV_{offset}\\ MV_{L_1}'=MV_{L_1}-MV_{offset} MVL0′=MVL0+MVoffsetMVL1′=MVL1−MVoffset
void InterPrediction::xProcessDMVR
相关代码:
//初始的MV计算出的子CU的前向及后向预测值(这里前后向的搜索点遵循净吸纳过方式,即对于同一个MV偏移,前向MV是加上偏移量,后向MV是减去偏移量)
Pel *addrL0 = biLinearPredL0 + totalDeltaMV[0] + (totalDeltaMV[1] * m_biLinearBufStride);
Pel *addrL1 = biLinearPredL1 - totalDeltaMV[0] - (totalDeltaMV[1] * m_biLinearBufStride);
//子pu的最终修正后的MV是原来的Merge的初始MV加上子pu的修正MVD(即MV的偏置,前向加偏置,后向减去偏置,呈现镜像的方式)
subPu.mv[0] = mergeMv[REF_PIC_LIST_0] + pu.mvdL0SubPu[num];
subPu.mv[1] = mergeMv[REF_PIC_LIST_1] - pu.mvdL0SubPu[num];
其中MV偏移量代表的是修正后的MV和初始MV之间的偏移量,在VTM中,搜索的最大偏移量为2个整像素,因此总共有25个搜索点(一个初始点和24个周围的点),在VTM8.0代码中的缓存如下(可以在commmonLib库中的InterPrediction.h头文件中找到):
//25个搜索偏置,其中一个是零偏置,代表初始MV本身,其余24个搜素点都是在初始MV基础上在水平和垂直方向上进行一定整像素的偏置,最多偏移2个整像素位置
Mv m_pSearchOffset[25] = {
Mv(-2,-2), Mv(-1,-2), Mv(0,-2), Mv(1,-2), Mv(2,-2),
Mv(-2,-1), Mv(-1,-1), Mv(0,-1), Mv(