HEVC中的Merge Mode——x265程式碼getInterMergeCandidates()函式解析
阿新 • • 發佈:2018-12-21
HEVC中的mv預測技術主要分為兩種:AMVP mode 和Merge mode,這裡就主要來聊聊Merge mode。 由於很多時候當前的編碼CU與相鄰已經完成編碼的CU具有相同的mv,因此從相鄰的若干個PU中獲取最佳的mv就是Merge mode要做的主要事情:
- 通過搜尋相鄰PU的motion information(包括reference index和mv)是否可用,可得時加入MergeCandidateList,作為motion information的候選。
- 逐個服複用MergeCandidateList中的motion information,計算殘差。
- 模型選擇最終選定Merge mode為預測模式,則將Merge index和相應的殘差寫入碼流。
這次要討論的getInterMergeCandidates()函式主要就是建立MergeCandidateList的過程。在x265的原始碼中,不同的preset對應的MergeCandidate個數是不一樣的。從ultrafast到medium的maxNumMergeCand為2,slow和slower為3,veryslow為4,placebo為5。不同的candidate個數會對編碼計算複雜度和壓縮效率產生影響,需要根據應用場景進行調整。 為了獲得更好的壓縮效果,在獲取MergeCandidateList的過程中,需要考慮以下幾點:
- 加入MergeCandidateList的motion information的次序會對編碼產生影響。放在candidate list中靠前位置的motion information應該是最有可能被選中的塊。這樣在對merge index進行變長編碼時就會佔用最小的bits。經過大量的試驗表明,周邊PU的搜尋順序為left(A1)->above(B1)->above right(B0)->left bottom(A0)->above left(B2)->temporal->填充candidate。
- Candidate的質量也會有影響,體現在有效的Candidate的個數。在getInterMergeCandidates()函式中,有pruning的過程來篩選掉重複的Candidate。pruning的演算法為B1與A1做pruning,B0與B1做pruning,A0與A1做pruning,B2與A1和B1做pruning。這樣可以減少candidate list中重複的motion information,將有效的放在list前面。
在3D-HEVC中MergeCandidateList的獲取過程中就加入了disparity motion vector作為candidate之一,也是提高了candidate的質量,從而提高編碼效果。
下面是x265中getInterMergeCandidates()函式的原始碼和註釋,供大家參考。
/* Construct list of merging candidates, returns count */
uint32_t CUData::getInterMergeCandidates(uint32_t absPartIdx, uint32_t puIdx, MVField(*candMvField)[2], uint8_t* candDir) const
{
uint32_t absPartAddr = m_absIdxInCTU + absPartIdx;//計算當前CU的絕對位置
const bool isInterB = m_slice->isInterB();
//最大的MergeCandidate個數,預設為2,其中slow和slower模式為3,veryslow模式為4,placebo模式為5
const uint32_t maxNumMergeCand = m_slice->m_maxNumMergeCand;
//assert(m_slice->m_maxNumMergeCand == 2);
for (uint32_t i = 0; i < maxNumMergeCand; ++i)//初始化Mergecandidates的mv和refidx為0
{
candMvField[i][0].mv = 0;
candMvField[i][1].mv = 0;
candMvField[i][0].refIdx = REF_NOT_VALID;
candMvField[i][1].refIdx = REF_NOT_VALID;
}
/* calculate the location of upper-left corner pixel and size of the current PU */
int xP, yP, nPSW, nPSH;
int cuSize = 1 << m_log2CUSize[0];//獲取CU的大小
int partMode = m_partSize[0];//獲取四叉樹劃分模式
int tmp = partTable[partMode][puIdx][0];
nPSW = ((tmp >> 4) * cuSize) >> 2;//PU的寬度
nPSH = ((tmp & 0xF) * cuSize) >> 2;//PU的高度
tmp = partTable[partMode][puIdx][1];
xP = ((tmp >> 4) * cuSize) >> 2;//PU top-left畫素x座標
yP = ((tmp & 0xF) * cuSize) >> 2;//PU top-left畫素y座標
if(xP == 16 || yP == 16)
int a = 0;
if(nPSW == 8 && nPSH == 8)
int b = 0;
uint32_t count = 0;
uint32_t partIdxLT, partIdxRT, partIdxLB = deriveLeftBottomIdx(puIdx);
PartSize curPS = (PartSize)m_partSize[absPartIdx];
//merge candidates的PU搜尋順序:left(A1)->above->above right->left bottom->above left->temporal->填充candidate
//按照統計規律,上述PU的排序按照與當前PU具備最佳mv的概率從高到低排列,被選中的candidate的index值越小,寫入碼流後佔用的bit越小
//因此需要將選中概率大的PU放在candidate list的靠前位置
// left
uint32_t leftPartIdx = 0;
const CUData* cuLeft = getPULeft(leftPartIdx, partIdxLB);//獲取left PU
//isavailableA1:A1是否可用,判斷條件為:
//1. A1存在
//2. A1與當前coding PU不屬於同一merge域(與當前coding PU不共用同一套merge info)
//3. 當前PU不能具備下列條件:index為1,劃分方式為Nx2N,nLx2N,nrx2N
//4. A1為inter mode(這樣才具備mv)
bool isAvailableA1 = cuLeft &&
cuLeft->isDiffMER(xP - 1, yP + nPSH - 1, xP, yP) &&
!(puIdx == 1 && (curPS == SIZE_Nx2N || curPS == SIZE_nLx2N || curPS == SIZE_nRx2N)) &&
cuLeft->isInter(leftPartIdx);
if (isAvailableA1)
{
// get Inter Dir
candDir[count] = cuLeft->m_interDir[leftPartIdx];//複用inter direction
// get Mv from Left
cuLeft->getMvField(cuLeft, leftPartIdx, 0, candMvField[count][0]);//P或者B的list0,複用mv
if (isInterB)
cuLeft->getMvField(cuLeft, leftPartIdx, 1, candMvField[count][1]);//B的list1,複用mv
if (++count == maxNumMergeCand)//MergeCand個數
return maxNumMergeCand;
}
deriveLeftRightTopIdx(puIdx, partIdxLT, partIdxRT);
// above
uint32_t abovePartIdx = 0;
const CUData* cuAbove = getPUAbove(abovePartIdx, partIdxRT);//獲取B1
bool isAvailableB1 = cuAbove &&
cuAbove->isDiffMER(xP + nPSW - 1, yP - 1, xP, yP) &&
!(puIdx == 1 && (curPS == SIZE_2NxN || curPS == SIZE_2NxnU || curPS == SIZE_2NxnD)) &&
cuAbove->isInter(abovePartIdx);
if (isAvailableB1 && (!isAvailableA1 || !cuLeft->hasEqualMotion(leftPartIdx, *cuAbove, abovePartIdx)))//與A1的motion info做互比,捨去相同的
{
// get Inter Dir
candDir[count] = cuAbove->m_interDir[abovePartIdx];
// get Mv from Left
cuAbove->getMvField(cuAbove, abovePartIdx, 0, candMvField[count][0]);
if (isInterB)
cuAbove->getMvField(cuAbove, abovePartIdx, 1, candMvField[count][1]);
if (++count == maxNumMergeCand)
return maxNumMergeCand;
}
// above right
uint32_t aboveRightPartIdx = 0;
const CUData* cuAboveRight = getPUAboveRight(aboveRightPartIdx, partIdxRT);
bool isAvailableB0 = cuAboveRight &&
cuAboveRight->isDiffMER(xP + nPSW, yP - 1, xP, yP) &&
cuAboveRight->isInter(aboveRightPartIdx);
if (isAvailableB0 && (!isAvailableB1 || !cuAbove->hasEqualMotion(abovePartIdx, *cuAboveRight, aboveRightPartIdx)))//與B1做互比
{
// get Inter Dir
candDir[count] = cuAboveRight->m_interDir[aboveRightPartIdx];
// get Mv from Left
cuAboveRight->getMvField(cuAboveRight, aboveRightPartIdx, 0, candMvField[count][0]);
if (isInterB)
cuAboveRight->getMvField(cuAboveRight, aboveRightPartIdx, 1, candMvField[count][1]);
if (++count == maxNumMergeCand)
return maxNumMergeCand;
}
// left bottom
uint32_t leftBottomPartIdx = 0;
const CUData* cuLeftBottom = this->getPUBelowLeft(leftBottomPartIdx, partIdxLB);
bool isAvailableA0 = cuLeftBottom &&
cuLeftBottom->isDiffMER(xP - 1, yP + nPSH, xP, yP) &&
cuLeftBottom->isInter(leftBottomPartIdx);
if (isAvailableA0 && (!isAvailableA1 || !cuLeft->hasEqualMotion(leftPartIdx, *cuLeftBottom, leftBottomPartIdx)))//與A1互比
{
// get Inter Dir
candDir[count] = cuLeftBottom->m_interDir[leftBottomPartIdx];
// get Mv from Left
cuLeftBottom->getMvField(cuLeftBottom, leftBottomPartIdx, 0, candMvField[count][0]);
if (isInterB)
cuLeftBottom->getMvField(cuLeftBottom, leftBottomPartIdx, 1, candMvField[count][1]);
if (++count == maxNumMergeCand)
return maxNumMergeCand;
}
// above left
if (count < 4)
{
uint32_t aboveLeftPartIdx = 0;
const CUData* cuAboveLeft = getPUAboveLeft(aboveLeftPartIdx, absPartAddr);
bool isAvailableB2 = cuAboveLeft &&
cuAboveLeft->isDiffMER(xP - 1, yP - 1, xP, yP) &&
cuAboveLeft->isInter(aboveLeftPartIdx);
if (isAvailableB2 && (!isAvailableA1 || !cuLeft->hasEqualMotion(leftPartIdx, *cuAboveLeft, aboveLeftPartIdx))
&& (!isAvailableB1 || !cuAbove->hasEqualMotion(abovePartIdx, *cuAboveLeft, aboveLeftPartIdx)))//與A1和B1做互比
{
// get Inter Dir
candDir[count] = cuAboveLeft->m_interDir[aboveLeftPartIdx];
// get Mv from Left
cuAboveLeft->getMvField(cuAboveLeft, aboveLeftPartIdx, 0, candMvField[count][0]);
if (isInterB)
cuAboveLeft->getMvField(cuAboveLeft, aboveLeftPartIdx, 1, candMvField[count][1]);
if (++count == maxNumMergeCand)
return maxNumMergeCand;
}
}
//檢查當前幀是否打開了temporal MVP,如果存在,則檢查temporal mv是否可加入candidate list
if (m_slice->m_sps->bTemporalMVPEnabled)
{
uint32_t partIdxRB = deriveRightBottomIdx(puIdx);
MV colmv;
int ctuIdx = -1;
// image boundary check
if (m_encData->getPicCTU(m_cuAddr)->m_cuPelX + g_zscanToPelX[partIdxRB] + UNIT_SIZE < m_slice->m_sps->picWidthInLumaSamples &&
m_encData->getPicCTU(m_cuAddr)->m_cuPelY + g_zscanToPelY[partIdxRB] + UNIT_SIZE < m_slice->m_sps->picHeightInLumaSamples)
{
uint32_t absPartIdxRB = g_zscanToRaster[partIdxRB];
uint32_t numUnits = s_numPartInCUSize;
bool bNotLastCol = lessThanCol(absPartIdxRB, numUnits - 1); // is not at the last column of CTU
bool bNotLastRow = lessThanRow(absPartIdxRB, numUnits - 1); // is not at the last row of CTU
if (bNotLastCol && bNotLastRow)//如果不是當前CTU的最後一行、最後一列,則取當前PU的right bottom的address
{
absPartAddr = g_rasterToZscan[absPartIdxRB + RASTER_SIZE + 1];
ctuIdx = m_cuAddr;
}
//如果是最後一行,但不是最後一列,則取下一行CTU中對應當前PU的right bottom的address
//注意,這裡並沒有把CTU的index更新,這裡為了在編碼時避免CTU跨行,在這種情況下直接取中心位置的PU的mv作為candidate
else if (bNotLastCol)
absPartAddr = g_rasterToZscan[(absPartIdxRB + 1) & (numUnits - 1)];
else if (bNotLastRow)//如果是最後一列,但不是最後一行,則取下一個CU的中對應當前PU的right bottom的address
{
absPartAddr = g_rasterToZscan[absPartIdxRB + RASTER_SIZE - numUnits + 1];
ctuIdx = m_cuAddr + 1;
}
else // is the right bottom corner of CTU如果是當前CTU的右下角,
absPartAddr = 0;
}
int maxList = isInterB ? 2 : 1;
int dir = 0, refIdx = 0;
for (int list = 0; list < maxList; list++)
{
bool bExistMV = ctuIdx >= 0 && getColMVP(colmv, refIdx, list, ctuIdx, absPartAddr);//取MVP
if (!bExistMV)//如果沒有取成功,則取中央位置PU的mvp,這裡對應上述的CTU跨行的問題
{
uint32_t partIdxCenter = deriveCenterIdx(puIdx);
bExistMV = getColMVP(colmv, refIdx, list, m_cuAddr, partIdxCenter);
}
if (bExistMV)//如果取成功,將mvp加入candidate list
{
dir |= (1 << list);
candMvField[count][list].mv = colmv;
candMvField[count][list].refIdx = refIdx;
}
}
if (dir != 0)
{
candDir[count] = (uint8_t)dir;
if (++count == maxNumMergeCand)
return maxNumMergeCand;
}
}
//當前如果是interB時,之前的candidate個數大於1,則按照已制定好的優先順序組合已有的candidate info,形成一個新的candidate,並加入list
if (isInterB)
{
const uint32_t cutoff = count * (count - 1);
uint32_t priorityList0 = 0xEDC984; // { 0, 1, 0, 2, 1, 2, 0, 3, 1, 3, 2, 3 }
uint32_t priorityList1 = 0xB73621; // { 1, 0, 2, 0, 2, 1, 3, 0, 3, 1, 3, 2 }
for (uint32_t idx = 0; idx < cutoff; idx++, priorityList0 >>= 2, priorityList1 >>= 2)
{
int i = priorityList0 & 3;
int j = priorityList1 & 3;
if ((candDir[i] & 0x1) && (candDir[j] & 0x2))
{
// get Mv from cand[i] and cand[j]
int refIdxL0 = candMvField[i][0].refIdx;
int refIdxL1 = candMvField[j][1].refIdx;
int refPOCL0 = m_slice->m_refPOCList[0][refIdxL0];
int refPOCL1 = m_slice->m_refPOCList[1][refIdxL1];
if (!(refPOCL0 == refPOCL1 && candMvField[i][0].mv == candMvField[j][1].mv))
{
candMvField[count][0].mv = candMvField[i][0].mv;
candMvField[count][0].refIdx = refIdxL0;
candMvField[count][1].mv = candMvField[j][1].mv;
candMvField[count][1].refIdx = refIdxL1;
candDir[count] = 3;
if (++count == maxNumMergeCand)
return maxNumMergeCand;
}
}
}
}
//上述的過程選出的candidate個數如果還小於maxNumMergeCand,則補充0
int numRefIdx = (isInterB) ? X265_MIN(m_slice->m_numRefIdx[0], m_slice->m_numRefIdx[1]) : m_slice->m_numRefIdx[0];
int r = 0;
int refcnt = 0;
while (count < maxNumMergeCand)
{
candDir[count] = 1;
candMvField[count][0].mv.word = 0;
candMvField[count][0].refIdx = r;
if (isInterB)
{
candDir[count] = 3;
candMvField[count][1].mv.word = 0;
candMvField[count][1].refIdx = r;
}
count++;
if (refcnt == numRefIdx - 1)
r = 0;
else
{
++r;
++refcnt;
}
}
return count;
}