解碼端運動向量修正(Decoder side motion vector refinement ,DMVR)是爲了提高merge模式下雙向預測MV的準確性而提出的技術。雙向預測是在list0和list1中分別找一個運動向量MV0和MV1,然後將MV0和MV1所指向的預測塊進行加權得到最終的預測塊。而DMVR不是直接使用MV0和MV1,而是在MV0和MV1附近搜索更準確的MV0‘和MV1’。
如上圖所示,在初始MV附件搜索MV‘,計算MV’指向的塊(紅色塊)間的SAD,選擇SAD值最小的MV‘作爲最終修正的MV。
在VTM5中滿足以下條件的CU纔可以使用DMVR模式:
-
CU使用merge模式,且是雙向預測。
-
兩個參考幀分別位於當前幀之前和之後。
-
即兩個參考幀離當前幀一樣遠(即兩個參考幀的POC與當前幀POC的差值相等)。
-
CU至少有64個亮度像素。
-
CU的寬和高都大於等於8。
-
BCW使用相等權值。
-
當前塊不使用WP模式。
DMVR生成的修正MV用於生成幀間預測值和後續圖像編碼的時域運動向量預測值(TMVP)。原始MV用於去方塊效應過程和後續CU編碼的空域運動向量預測值(SMVP)。
VTM5中DMVR的具體過程如下:
搜索
DMVR中要搜索初始MV周圍的點以找到最優的修正MV。待搜索的MV滿足以下方程:
MV_offset表示修正MV相對初始MV的偏移量。VTM5規定搜索範圍是初始MV附近2個整數亮度像素點內,搜索過程包括兩個階段,第一階段搜索整像素第二階段搜索分像素。
整像素搜索:
由於搜索範圍是初始MV附近2個整數亮度像素點,所以一共有25個整像素點。
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) };
uint64_t m_SADsArray[((2 * DMVR_NUM_ITERATION) + 1) * ((2 * DMVR_NUM_ITERATION) + 1)];
首先計算初始MV0和MV1對應的塊的SAD,如果該SAD小於閾值則終止整像素搜索過程。否則按掃描順序計算剩下24個點的SAD,選擇SAD最小的作爲整數像素搜索階段的輸出。
分像素搜索:
爲了減少計算複雜性,進行分像素搜索時用參數誤差曲面方程(parametric error surface equation)代替SAD。只有當第一輪或第二輪整像素搜索階段中心像素SAD最小時,才需要進行分像素搜索。
在計算分像素參數誤差曲面時,需要使用中心位置和其周圍四個位置的cost來擬合一共二維拋物線誤差曲面方程:
通過使用5個點的cost值可以求解上面的方程:
x_min和y_min的值會被自動限制在-8~8間,因爲所有的cost都爲正且最小的cost是E(0,0)。計算得到的分像素(x_min,y_min)加上整像素MV得到最終修正MV。
雙線性插值和像素填充
VVC中MV是1/16亮度像素精度。這些分像素是通過8抽頭插值濾波器生成的。在DMVR中,爲了減少計算複雜性在分像素搜索階段使用雙線性插值濾波器生成分像素。另外,使用雙線性插值只需要訪問周圍2像素範圍,而不用像普通運動補償一樣要參考更多像素。當獲得修正MV後,在生成最終預測值時需要使用8抽頭插值濾波器。
DMVR最大能處理16x16的塊,如果塊的寬和/或高大於16,需要將其分爲寬和/或高等於16的子塊。
VTM5中相關代碼如下:
for (int y = puPos.y; y < (puPos.y + pu.lumaSize().height); y = y + dy, yStart = yStart + dy)
{
for (int x = puPos.x, xStart = 0; x < (puPos.x + pu.lumaSize().width); x = x + dx, xStart = xStart + dx)
{
uint64_t minCost = MAX_UINT64;
bool notZeroCost = true;
int16_t totalDeltaMV[2] = { 0,0 };
int16_t deltaMV[2] = { 0, 0 };
uint64_t *pSADsArray;
for (int i = 0; i < (((2 * DMVR_NUM_ITERATION) + 1) * ((2 * DMVR_NUM_ITERATION) + 1)); i++)
{//!<25個整像素點SAD初始化
m_SADsArray[i] = MAX_UINT64;
}
pSADsArray = &m_SADsArray[(((2 * DMVR_NUM_ITERATION) + 1) * ((2 * DMVR_NUM_ITERATION) + 1)) >> 1];
Pel *addrL0Centre = biLinearPredL0 + yStart * m_biLinearBufStride + xStart;
Pel *addrL1Centre = biLinearPredL1 + yStart * m_biLinearBufStride + xStart;
for (int i = 0; i < iterationCount; i++)
{
deltaMV[0] = 0;
deltaMV[1] = 0;
Pel *addrL0 = addrL0Centre + totalDeltaMV[0] + (totalDeltaMV[1] * m_biLinearBufStride);
Pel *addrL1 = addrL1Centre - totalDeltaMV[0] - (totalDeltaMV[1] * m_biLinearBufStride);
if (i == 0)
{//!<計算初始MV對應的cost
minCost = xDMVRCost(clpRngs.comp[COMPONENT_Y].bd, addrL0, m_biLinearBufStride, addrL1, m_biLinearBufStride, dx, dy);
if (minCost < ((4 * dx * (dy >> 1/*for alternate line*/))))
{//!<判斷是否小於閾值
notZeroCost = false;
break;
}
pSADsArray[0] = minCost;
}
if (!minCost)
{
notZeroCost = false;
break;
}
//!<計算25個整像素點對應的cost
xBIPMVRefine(bd, addrL0, addrL1, minCost, deltaMV, pSADsArray, dx, dy);
if (deltaMV[0] == 0 && deltaMV[1] == 0)
{
break;
}
totalDeltaMV[0] += deltaMV[0];
totalDeltaMV[1] += deltaMV[1];
pSADsArray += ((deltaMV[1] * (((2 * DMVR_NUM_ITERATION) + 1))) + deltaMV[0]);
}
totalDeltaMV[0] = (totalDeltaMV[0] << mvShift);
totalDeltaMV[1] = (totalDeltaMV[1] << mvShift);
//!<計算分像素代價
xDMVRSubPixelErrorSurface(notZeroCost, totalDeltaMV, deltaMV, pSADsArray);
pu.mvdL0SubPu[num] = Mv(totalDeltaMV[0], totalDeltaMV[1]);
num++;
}
}
//計算整像素點cost
void InterPrediction::xBIPMVRefine(int bd, Pel *pRefL0, Pel *pRefL1, uint64_t& minCost, int16_t *deltaMV, uint64_t *pSADsArray, int width, int height)
{
const int32_t refStrideL0 = m_biLinearBufStride;
const int32_t refStrideL1 = m_biLinearBufStride;
Pel *pRefL0Orig = pRefL0;
Pel *pRefL1Orig = pRefL1;
for (int nIdx = 0; (nIdx < 25); ++nIdx)
{//!<計算25個整像素點對應的cost,找出代價最小的
int32_t sadOffset = ((m_pSearchOffset[nIdx].getVer() * ((2 * DMVR_NUM_ITERATION) + 1)) + m_pSearchOffset[nIdx].getHor());
pRefL0 = pRefL0Orig + m_pSearchOffset[nIdx].hor + (m_pSearchOffset[nIdx].ver * refStrideL0);
pRefL1 = pRefL1Orig - m_pSearchOffset[nIdx].hor - (m_pSearchOffset[nIdx].ver * refStrideL1);
if (*(pSADsArray + sadOffset) == MAX_UINT64)
{
const uint64_t cost = xDMVRCost(bd, pRefL0, refStrideL0, pRefL1, refStrideL1, width, height);
*(pSADsArray + sadOffset) = cost;
}
if (*(pSADsArray + sadOffset) < minCost)
{
minCost = *(pSADsArray + sadOffset);
deltaMV[0] = m_pSearchOffset[nIdx].getHor();
deltaMV[1] = m_pSearchOffset[nIdx].getVer();
}
}
}
//計算分像素點cost
void xSubPelErrorSrfc(uint64_t *sadBuffer, int32_t *deltaMv)
{
int64_t numerator, denominator;
int32_t mvDeltaSubPel;
int32_t mvSubPelLvl = 4;/*1: half pel, 2: Qpel, 3:1/8, 4: 1/16*/
//!<計算x_min /*horizontal*/
numerator = (int64_t)((sadBuffer[1] - sadBuffer[3]) << mvSubPelLvl);
denominator = (int64_t)((sadBuffer[1] + sadBuffer[3] - (sadBuffer[0] << 1)));
if (0 != denominator)
{
if ((sadBuffer[1] != sadBuffer[0]) && (sadBuffer[3] != sadBuffer[0]))
{
mvDeltaSubPel = div_for_maxq7(numerator, denominator);
deltaMv[0] = (mvDeltaSubPel);
}
else
{
if (sadBuffer[1] == sadBuffer[0])
{
deltaMv[0] = -8;// half pel
}
else
{
deltaMv[0] = 8;// half pel
}
}
}
/*vertical*/ //!<計算y_min
numerator = (int64_t)((sadBuffer[2] - sadBuffer[4]) << mvSubPelLvl);
denominator = (int64_t)((sadBuffer[2] + sadBuffer[4] - (sadBuffer[0] << 1)));
if (0 != denominator)
{
if ((sadBuffer[2] != sadBuffer[0]) && (sadBuffer[4] != sadBuffer[0]))
{
mvDeltaSubPel = div_for_maxq7(numerator, denominator);
deltaMv[1] = (mvDeltaSubPel);
}
else
{
if (sadBuffer[2] == sadBuffer[0])
{
deltaMv[1] = -8;// half pel
}
else
{
deltaMv[1] = 8;// half pel
}
}
}
return;
}
感興趣的請關注微信公衆號Video Coding