1. 程式人生 > >HEVC中的Merge Mode——x265程式碼getInterMergeCandidates()函式解析

HEVC中的Merge Mode——x265程式碼getInterMergeCandidates()函式解析

HEVC中的mv預測技術主要分為兩種:AMVP mode 和Merge mode,這裡就主要來聊聊Merge mode。 由於很多時候當前的編碼CU與相鄰已經完成編碼的CU具有相同的mv,因此從相鄰的若干個PU中獲取最佳的mv就是Merge mode要做的主要事情:

  1. 通過搜尋相鄰PU的motion information(包括reference index和mv)是否可用,可得時加入MergeCandidateList,作為motion information的候選。
  2. 逐個服複用MergeCandidateList中的motion information,計算殘差。
  3. 模型選擇最終選定Merge mode為預測模式,則將Merge index和相應的殘差寫入碼流。

這次要討論的getInterMergeCandidates()函式主要就是建立MergeCandidateList的過程。在x265的原始碼中,不同的preset對應的MergeCandidate個數是不一樣的。從ultrafast到medium的maxNumMergeCand為2,slow和slower為3,veryslow為4,placebo為5。不同的candidate個數會對編碼計算複雜度和壓縮效率產生影響,需要根據應用場景進行調整。 為了獲得更好的壓縮效果,在獲取MergeCandidateList的過程中,需要考慮以下幾點:

  1. 加入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。
  2. 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;
}