H266/VVC 帧间预测中 DMVR 技术
解码端运动矢量修正 DMVR
- 为了增强双向预测 merge 模式的 MV 的准确性,VVC 中应用了解码端运动矢量修正(Decoder side motion vector refinement, DMVR)。此技术主要计算参考帧列表 L0 和 L1 中两个候选块之间的失真。双向预测是在 L0 和 L1 中分别寻找一 个运动向量 MV0 和 MV1,然后将 MV0 和 MV1 所指向的预测块进行加权得到最终预测块,而 DMVR 技术不是直接使用 MV0 和 MV1,而是在 MV0 和 MV1 周围 1-2 个像素范围内搜索一个更加精确的 MV。 BM(双向匹配)方法计算参考图像列表L0和列表L1中两个候选块之间的失真。如图 36 所示,基于每个MV候选计算红色块之间的SAD。具有最低SAD的MV候选成为细化后的MV,并用于生成双预测信号。
- 在 VVC 中,CU 满足以下条件时,可以应用 DMVR 技术:
- 使用双预测MV的CU级 merge 模式
- 相对于当前图像,一个参考图像在之前,另一个参考图像在之后
- 两个参考图像到当前图像的距离(即POC差异)相同
- 两个参考图像都是短期参考图像
- CU具有超过64个亮度样本
- CU的高度和宽度都大于或等于8个亮度样本
- BCW权重索引表示等权重
- 当前块未启用WP(Weighted Prediction,加权预测)
- 当前块未使用CIIP(Combined Inter/Intra Prediction,组合帧间/帧内预测)模式
- DMVR过程生成的修正运动矢量 mv 用于生成帧间预测样本值,也用于未来图像编码的时域运动矢量预测(TMVP)。原始MV用于去块处理过程,也用于未来CU编码的空域运动矢量预测。
- 搜索方案: DMVR 技术第一步需要对初始点周边的位置进行搜索,以寻求更优的 MV 的偏移。计算前向、后向两个候选 MV 对的预测块之间的 SAD,使得每一个搜索点处 MV 的偏移都遵守镜像原则,即前、后向两个初始 MV 都必须同时使用同一个搜索点处的 MV 的偏移,按照镜像规则,前向初始点需要加上一个偏移, 后向的初始点减去对应的相同的偏移,如下公式所示:
- 其中 MVoffset 代表修正后 MV 和初始 MV 之间的偏移量。DMVR 的搜索范围是初始 MV 的 2 个整像素范围。
- 搜索过程包括整像素偏移搜索阶段和分像素修正阶段。整像素搜索偏移阶段以最多 2 个整像素为偏移,搜索初始 MV 和初始 MV 附近的 25 个点。首先计算初始 MV0 和 MV1 的 SAD(以 merge 模式 MV 作为初始 MV)。如果初始 MV0 和 MV1 的 SAD 小于阈值(阈值是 DMVR 搜索单元的像素数,对于 16x16 子 PU 阈值为 256),则终止 DMVR 的整数搜索过程。(为了减少 DMVR 改进不确定性的损失,进行阈值比较时使用的 SAD 为原始 SAD 减去其 SAD 值的 1/4,阈值为 DMVR 搜索单元的像素数)。否则,按照光栅扫描顺序计算并检查其余 24 点的 SAD,并选择 SAD 最小的点作为整像素搜索的结果。第二步就是对每个子块进行 MV 细化,利用子块的参考区域生成子块的最终运动补偿预测值。
- 为了减小计算复杂度,分像素搜索使用参数误差曲面方程(parametric error surface equation)来代替 SAD 计算。在整像素搜索中,如果初始 MV0 和 MV1 的 SAD 小于阈值,会提前终止整像素搜索过程,也不再进行分像素搜索过程。
- 在基于参数误差曲面的子像素偏移估计中,以整像素搜索得到的结果为中心, 其相邻的四个位置的 Cost 用于拟合以下形式的二维抛物线误差曲面方程:
-
其中 xmin , ymin 对应于 Cost 最小的分数位置,C 对应于最小 Cost 值。通过使用五个搜索点的 Cost 求解上述等式,其中 xmin , ymin 计算公式为:
-
由于所有 Cost 值均为正且最小值为 E(0,0),因此 xmin 和 ymin 的值会自动限制在-8 到 8 之间。将计算出的分像素(xmin, ymin)加到整像素细化的 MV 以获得分像素精确细化 MV。
-
- 在 DMVR 中为了降低计算复杂度,使用双线性插值滤波器生成分数像素, 用于 DMVR 中的搜索过程。通过使用双线性插值滤波器,在 2 个像素的搜索范围内,与正常的运动补偿过程相比,DVMR 不会访问更多的参考像素。在通过 DMVR 搜索过程获得修正 MV 之后,将使用常规的 8 抽头插值滤波器来生成最终预测值。
- 当CU(编码单元)的宽度和/高度大于16个亮度样本时,它将进一步被分割成宽度和/高度等于16个亮度样本的子块。DMVR搜索过程的最大单元尺寸限制为16x16。
- 在 2019 年 6 月的JVET-O0297[CE9-related: Simplification for DMVR padding process] 提案中对 DMVR 技术的填充过程进行了简化。
- 在 2020 年 1 月的 JVET-Q2002[Algorithm description for Versatile Video Coding and Test Model 8 (VTM 8)] 提案中对 DMVR 技术进行了总结描述。
- 在 VVenC 编码器中在 UnitTools.cpp 文件中
checkDMVRCondition
函数用来检测 DMVR 的应用条件。
bool CU::checkDMVRCondition(const CodingUnit& cu)
{
if (!cu.cs->sps->DMVR || cu.cs->slice->picHeader->disDmvrFlag)
{
return false;
}
return cu.mergeFlag
&& cu.mergeType == MRG_TYPE_DEFAULT_N
&& !cu.ciip
&& !cu.affine
&& !cu.mmvdMergeFlag
&& !cu.mmvdSkip
&& CU::isBiPredFromDifferentDirEqDistPoc(cu)
&& (cu.lheight() >= 8)
&& (cu.lwidth() >= 8)
&& ((cu.lheight() * cu.lwidth()) >= 128)
&& (cu.BcwIdx == BCW_DEFAULT);
}
- 在 VVenC 编码器中 InterPrediction.h 文件中 DMVR 技术的 25 个 搜索点如下【但实际并没有应用到该数组?应该是从 VTM 移植算法直接拷贝该数组定义的】。
class DMVR : public InterPredInterpolation
{
RdCost* m_pcRdCost;
PelStorage m_yuvPred[NUM_REF_PIC_LIST_01];
PelStorage m_yuvTmp[NUM_REF_PIC_LIST_01];
PelStorage m_yuvPad[NUM_REF_PIC_LIST_01];
const 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(1,-1), Mv(2,-1),
Mv(-2, 0), Mv(-1, 0), Mv(0, 0), Mv(1, 0), Mv(2, 0),
Mv(-2, 1), Mv(-1, 1), Mv(0, 1), Mv(1, 1), Mv(2, 1),
Mv(-2, 2), Mv(-1, 2), Mv(0, 2), Mv(1, 2), Mv(2, 2) };
private:
void xCopyAndPad ( const CodingUnit& cu, PelUnitBuf& pcPad, RefPicList refId, bool forLuma);
void xFinalPaddedMCForDMVR( const CodingUnit& cu, PelUnitBuf* dstBuf, const PelUnitBuf *refBuf, const bool bioApplied, const Mv startMV[NUM_REF_PIC_LIST_01], const Mv& refMV );
protected:
DMVR();
virtual ~DMVR();
void destroy();
void init ( RdCost* pcRdCost, const ChromaFormat chFormat );
void xProcessDMVR ( const CodingUnit& cu, PelUnitBuf& pcYuvDst, const ClpRngs &clpRngs, const bool bioApplied );
};
- 在 VVenC 编码器中 InterPrediction.cpp 文件中 DMVR 技术的处理过程如
xProcessDMVR
函数。
void DMVR::xProcessDMVR( const CodingUnit& cu, PelUnitBuf& pcYuvDst, const ClpRngs &clpRngs, const bool bioApplied )
{
PROFILER_SCOPE_AND_STAGE_EXT( 1, _TPROF, P_INTER_MRG_DMVR, cu.cs, CH_L );
/*Always High Precision*/
const int csx = getChannelTypeScaleX( CH_C, cu.chromaFormat );
const int csy = getChannelTypeScaleY( CH_C, cu.chromaFormat );
const int mvShift = MV_FRACTIONAL_BITS_INTERNAL;
const int mvShiftC = mvShift + csx;
/*use merge MV as starting MV*/
const Mv mergeMv[] = { cu.mv[REF_PIC_LIST_0][0], cu.mv[REF_PIC_LIST_1][0] };
const int dy = std::min<int>(cu.lumaSize().height, DMVR_SUBCU_SIZE);
const int dx = std::min<int>(cu.lumaSize().width, DMVR_SUBCU_SIZE);
const Position& puPos = cu.lumaPos();
bool bioAppliedType[MAX_NUM_SUBCU_DMVR];
// Do refinement search
{
const int bilinearBufStride = (cu.Y().width + (2 * DMVR_NUM_ITERATION));
const int padSize = DMVR_NUM_ITERATION << 1;
const int dstOffset = -( DMVR_NUM_ITERATION * bilinearBufStride + DMVR_NUM_ITERATION );
/*use merge MV as starting MV*/
Mv mergeMVL0 = cu.mv[L0][0];
Mv mergeMVL1 = cu.mv[L1][0];
/*Clip the starting MVs*/
clipMv(mergeMVL0, cu.lumaPos(), cu.lumaSize(), *cu.cs->pcv);
clipMv(mergeMVL1, cu.lumaPos(), cu.lumaSize(), *cu.cs->pcv);
/*L0 MC for refinement*/
{
const Picture* refPic = cu.slice->getRefPic(L0, cu.refIdx[L0]);
PelUnitBuf yuvTmp = PelUnitBuf(cu.chromaFormat, PelBuf(m_yuvTmp[L0].getBuf(COMP_Y).buf + dstOffset, bilinearBufStride, cu.lwidth() + padSize, cu.lheight() + padSize));
mergeMVL0.hor -= (DMVR_NUM_ITERATION << MV_FRACTIONAL_BITS_INTERNAL);
mergeMVL0.ver -= (DMVR_NUM_ITERATION << MV_FRACTIONAL_BITS_INTERNAL);
xPredInterBlk(COMP_Y, cu, refPic, mergeMVL0, yuvTmp, true, clpRngs[COMP_Y], false, false, L0, cu.lwidth() + padSize, cu.lheight() + padSize, true);
}
/*L1 MC for refinement*/
{
const Picture* refPic = cu.slice->getRefPic(L1, cu.refIdx[L1]);
PelUnitBuf yuvTmp = PelUnitBuf(cu.chromaFormat, PelBuf(m_yuvTmp[L1].getBuf(COMP_Y).buf + dstOffset, bilinearBufStride, cu.lwidth() + padSize, cu.lheight() + padSize));
mergeMVL1.hor -= (DMVR_NUM_ITERATION << MV_FRACTIONAL_BITS_INTERNAL);
mergeMVL1.ver -= (DMVR_NUM_ITERATION << MV_FRACTIONAL_BITS_INTERNAL);
xPredInterBlk(COMP_Y, cu, refPic, mergeMVL1, yuvTmp, true, clpRngs[COMP_Y], false, false, L1, cu.lwidth() + padSize, cu.lheight() + padSize, true);
}
// point mc buffer to center point to avoid multiplication to reach each iteration to the beginning
const Pel* biLinearPredL0 = m_yuvTmp[0].getBuf( COMP_Y ).buf;
const Pel* biLinearPredL1 = m_yuvTmp[1].getBuf( COMP_Y ).buf;
const int bioEnabledThres = 2 * dy * dx;
const int bd = cu.cs->slice->clpRngs[COMP_Y].bd;
DistParam distParam = m_pcRdCost->setDistParam( nullptr, nullptr, bilinearBufStride, bilinearBufStride, bd, COMP_Y, dx, dy, 1, true );
int num = 0;
int yStart = 0;
uint64_t sadArray[((2 * DMVR_NUM_ITERATION) + 1) * ((2 * DMVR_NUM_ITERATION) + 1)];
for( int y = puPos.y; y < ( puPos.y + cu.lumaSize().height ); y = y + dy, yStart = yStart + dy )
{
for( int x = puPos.x, xStart = 0; x < ( puPos.x + cu.lumaSize().width ); x = x + dx, xStart = xStart + dx )
{
uint64_t minCost = MAX_UINT64;
// set all entries to MAX_UNIT64
uint64_t *pSADsArray = &sadArray[( ( ( 2 * DMVR_NUM_ITERATION ) + 1 ) * ( ( 2 * DMVR_NUM_ITERATION ) + 1 ) ) >> 1];
const Pel* addrL0Centre = biLinearPredL0 + yStart * bilinearBufStride + xStart;
const Pel* addrL1Centre = biLinearPredL1 + yStart * bilinearBufStride + xStart;
const Pel* addrL0 = addrL0Centre;
const Pel* addrL1 = addrL1Centre;
distParam.org.buf = addrL0;
distParam.cur.buf = addrL1;
minCost = distParam.distFunc( distParam ) >> 1;
minCost -= ( minCost >> 2 );
if( minCost < ( dx * dy ) )
{
cu.mvdL0SubPu[num] = Mv( 0, 0 );
}
else
{
int16_t totalDeltaMV[2] = { 0, 0 };
int16_t deltaMV[2] = { 0, 0 };
pSADsArray[0] = minCost;
pSADsArray = sadArray;
for( int ver = -2; ver <= 2; ver++ )
{
const int initHor = -2;
const ptrdiff_t offset = initHor + ver * bilinearBufStride;
distParam.org.buf = addrL0 + offset;
distParam.cur.buf = addrL1 - offset;
distParam.dmvrSadX5( distParam, pSADsArray, ver != 0 );
for( int hor = -2; hor <= 2; hor++, pSADsArray++ )
{
Distortion cost = *pSADsArray;
if( cost < minCost )
{
minCost = cost;
deltaMV[0] = hor;
deltaMV[1] = ver;
}
}
}
pSADsArray = &sadArray[( ( ( 2 * DMVR_NUM_ITERATION ) + 1 ) * ( ( 2 * DMVR_NUM_ITERATION ) + 1 ) ) >> 1];
totalDeltaMV[0] += deltaMV[0];
totalDeltaMV[1] += deltaMV[1];
pSADsArray += ( ( deltaMV[1] * ( ( ( 2 * DMVR_NUM_ITERATION ) + 1 ) ) ) + deltaMV[0] );
totalDeltaMV[0] = totalDeltaMV[0] * ( 1 << mvShift );
totalDeltaMV[1] = totalDeltaMV[1] * ( 1 << mvShift );
xDMVRSubPixelErrorSurface( totalDeltaMV, deltaMV, pSADsArray );
cu.mvdL0SubPu[num] = Mv( totalDeltaMV[0], totalDeltaMV[1] );
}
bioAppliedType[num] = ( minCost < bioEnabledThres ) ? false : bioApplied;
num++;
}
}
}
// Final MC
CodingUnit subCu = cu;
subCu.UnitArea::operator=(UnitArea(cu.chromaFormat, Area(puPos.x, puPos.y, dx, dy)));
PelUnitBuf subPredBuf = pcYuvDst.subBuf(UnitAreaRelative(cu, subCu));
PelUnitBuf predBuf[NUM_REF_PIC_LIST_01];
predBuf[L0] = m_yuvPred[L0].getCompactBuf( subCu );
predBuf[L1] = m_yuvPred[L1].getCompactBuf( subCu );
/* For padding */
PelUnitBuf padBuf[NUM_REF_PIC_LIST_01];
padBuf[L0] = m_yuvPad[L0].getBufPart(subCu);
padBuf[L1] = m_yuvPad[L1].getBufPart(subCu);
int x = 0, y = 0;
int xStart = 0, yStart = 0;
int num = 0;
const int scaleX = getComponentScaleX(COMP_Cb, cu.chromaFormat);
const int scaleY = getComponentScaleY(COMP_Cb, cu.chromaFormat);
const ptrdiff_t dstStride[MAX_NUM_COMP] = { pcYuvDst.bufs[COMP_Y].stride, cu.chromaFormat != CHROMA_400 ? pcYuvDst.bufs[COMP_Cb].stride : 0, cu.chromaFormat != CHROMA_400 ? pcYuvDst.bufs[COMP_Cr].stride : 0 };
for( y = puPos.y; y < ( puPos.y + cu.lumaSize().height ); y = y + dy, yStart = yStart + dy )
{
for( x = puPos.x, xStart = 0; x < ( puPos.x + cu.lumaSize().width ); x = x + dx, xStart = xStart + dx )
{
subCu.Y().x = x;
subCu.Y().y = y;
if( cu.chromaFormat != CHROMA_400 )
{
subCu.Cb().x = subCu.Cr().x = x >> csx;
subCu.Cb().y = subCu.Cr().y = y >> csy;
}
Mv mv0 = mergeMv[REF_PIC_LIST_0] + cu.mvdL0SubPu[num]; mv0.clipToStorageBitDepth();
Mv mv1 = mergeMv[REF_PIC_LIST_1] - cu.mvdL0SubPu[num]; mv1.clipToStorageBitDepth();
bool padBufL0 = (mv0.hor >> mvShift) != (mergeMv[0].hor >> mvShift) || (mv0.ver >> mvShift) != (mergeMv[0].ver >> mvShift);
bool padBufL0C = (mv0.hor >> mvShiftC) != (mergeMv[0].hor >> mvShiftC) || (mv0.ver >> mvShiftC) != (mergeMv[0].ver >> mvShiftC);
bool padBufL1 = (mv1.hor >> mvShift) != (mergeMv[1].hor >> mvShift) || (mv1.ver >> mvShift) != (mergeMv[1].ver >> mvShift);
bool padBufL1C = (mv1.hor >> mvShiftC) != (mergeMv[1].hor >> mvShiftC) || (mv1.ver >> mvShiftC) != (mergeMv[1].ver >> mvShiftC);
padBufL0C &= cu.chromaFormat != CHROMA_400;
padBufL1C &= cu.chromaFormat != CHROMA_400;
if (padBufL0) xCopyAndPad(subCu, padBuf[L0], L0, true);
if (padBufL0C) xCopyAndPad(subCu, padBuf[L0], L0, false);
if (padBufL1) xCopyAndPad(subCu, padBuf[L1], L1, true);
if (padBufL1C) xCopyAndPad(subCu, padBuf[L1], L1, false);
xFinalPaddedMCForDMVR( subCu, predBuf, padBuf, bioAppliedType[num], mergeMv, cu.mvdL0SubPu[num] );
subPredBuf.bufs[COMP_Y].buf = pcYuvDst.bufs[COMP_Y].buf + xStart + yStart * dstStride[COMP_Y];
if( cu.chromaFormat != CHROMA_400 )
{
subPredBuf.bufs[COMP_Cb].buf = pcYuvDst.bufs[COMP_Cb].buf + (xStart >> scaleX) + ((yStart >> scaleY) * dstStride[COMP_Cb]);
subPredBuf.bufs[COMP_Cr].buf = pcYuvDst.bufs[COMP_Cr].buf + (xStart >> scaleX) + ((yStart >> scaleY) * dstStride[COMP_Cr]);
}
xWeightedAverage( subCu, predBuf[L0], predBuf[L1], subPredBuf, bioAppliedType[num] );
num++;
}
}
}
- 在 VVenC 编码器中 InterPrediction.cpp 文件中 DMVR 技术的分像素搜索过程在
xDMVRSubPixelErrorSurface
函数中完成。
static void xDMVRSubPixelErrorSurface( int16_t *totalDeltaMV, int16_t *deltaMV, uint64_t *pSADsArray )
{
int sadStride = (((2 * DMVR_NUM_ITERATION) + 1));
uint64_t sadbuffer[5];
if( ( abs( totalDeltaMV[ 0 ] ) != ( 2 << MV_FRACTIONAL_BITS_INTERNAL ) )
&& ( abs( totalDeltaMV[ 1 ] ) != ( 2 << MV_FRACTIONAL_BITS_INTERNAL ) ) )
{
int32_t tempDeltaMv[2] = { 0,0 };
sadbuffer[0] = pSADsArray[0];
sadbuffer[1] = pSADsArray[-1];
sadbuffer[2] = pSADsArray[-sadStride];
sadbuffer[3] = pSADsArray[1];
sadbuffer[4] = pSADsArray[sadStride];
xSubPelErrorSrfc(sadbuffer, tempDeltaMv);
totalDeltaMV[0] += tempDeltaMv[0];
totalDeltaMV[1] += tempDeltaMv[1];
}
}
- 在 VVenC 编码器中 InterPrediction.cpp 文件中 DMVR 技术的运动补偿如
xFinalPaddedMCForDMVR
函数。
void DMVR::xFinalPaddedMCForDMVR( const CodingUnit& cu, PelUnitBuf* dstBuf, const PelUnitBuf *refBuf, const bool bioApplied, const Mv mergeMv[NUM_REF_PIC_LIST_01], const Mv& refMv )
{
int mvShift = MV_FRACTIONAL_BITS_INTERNAL;
Mv mv[2];
mv[L0] = mergeMv[L0] + refMv; mv[L0].clipToStorageBitDepth();
mv[L1] = mergeMv[L1] - refMv; mv[L1].clipToStorageBitDepth();
for (int k = 0; k < NUM_REF_PIC_LIST_01; k++)
{
RefPicList refId = (RefPicList)k;
const Mv& cMv = mv[refId];
Mv cMvClipped( cMv );
clipMv(cMvClipped, cu.lumaPos(), cu.lumaSize(), *cu.cs->pcv);
const Picture* refPic = cu.slice->getRefPic(refId, cu.refIdx[refId]);
const Mv& startMv = mergeMv[refId];
for (int compID = 0; compID < getNumberValidComponents(cu.chromaFormat); compID++)
{
int mvshiftTemp = mvShift + getComponentScaleX((ComponentID)compID, cu.chromaFormat);
int deltaIntMvX = (cMv.hor >> mvshiftTemp) - (startMv.hor >> mvshiftTemp);
int deltaIntMvY = (cMv.ver >> mvshiftTemp) - (startMv.ver >> mvshiftTemp);
CHECK((abs(deltaIntMvX) > DMVR_NUM_ITERATION) || (abs(deltaIntMvY) > DMVR_NUM_ITERATION), "not expected DMVR movement");
if (deltaIntMvX || deltaIntMvY)
{
const PelBuf& srcBuf = refBuf[refId].bufs[compID];
int offset = (deltaIntMvY)*srcBuf.stride + (deltaIntMvX);
xPredInterBlk( ( ComponentID ) compID, cu, nullptr, cMvClipped, dstBuf[refId], true, cu.cs->slice->clpRngs[compID], bioApplied, false, refId, 0, 0, 0, srcBuf.buf + offset, srcBuf.stride );
}
else
{
xPredInterBlk( ( ComponentID ) compID, cu, refPic, cMvClipped, dstBuf[refId], true, cu.cs->slice->clpRngs[compID], bioApplied, false, refId, 0, 0, 0 );
}
}
}
}
原文地址:https://blog.csdn.net/yanceyxin/article/details/145261874
免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!