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;
}
Open Source (GPL) H.265/HEVC video encoder 下载网址:https://bitbucket.org/multicoreware/x265/src x265 developer wiki To compile x265 you must first install Mercurial (or TortoiseHg on Windows) and CMake. Then follow these easy steps: (for the most definitive instructions, consult our build README) Linux Instructions # ubuntu packages: $ sudo apt-get install mercurial cmake cmake-curses-gui build-essential yasm # Note: if the packaged yasm is older than 1.2, you must download yasm-1.2 and build it $ hg clone https://bitbucket.org/multicoreware/x265 $ cd x265/build/linux $ ./make-Makefiles.bash $ make Windows (Visual Studio) Instructions $ hg clone https://bitbucket.org/multicoreware/x265 Then run make-solutions.bat in the build\ folder that corresponds to your favorite compiler, configure your build options, click 'configure', click 'generate', then close cmake-gui. You will be rewarded with an x265.sln file. Also see cmake documentation. Intel Compiler Instructions On Windows, you should open an Intel Compiler command prompt and within it run one of the make-makefiles.bat scripts in build/icl32 or build/icl64, then run nmake. On Linux, you can tell cmake to build Makefiles for icpc directly. This requires you to have configured Intel's compiler environment (by sourcing the appropriate shell script). For example: $ source /opt/intel/composer_xe_2013/bin/compilervars.sh intel64 $ cd repos/x265/build/linux $ export CXX=icpc $ export CC=icc $ ./make-Makefiles $ make Command line interface The Makefile/solution builds a static encoder.lib library and a standalone x265 executable that aims to be similar to x264 in its command line interface. Running without arguments shows you the command line help. Info Mission Statement Road Map TODO HOWTO add a new encoder performance primitive HOWTO Contribute patches to x265 HOWTO cross compile from Linux to Windows Coding Style Helpful links
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值