1. 程式人生 > >【HEVC學習與研究】39、HEVC幀內編碼的原理和實現(上)

【HEVC學習與研究】39、HEVC幀內編碼的原理和實現(上)

【前面N篇博文都講了一些HEVC幀內預測的程式碼結構和簡單的方法,但是尚未對整體的演算法和實現做一個比較完整的描述。本篇藉助參考文獻《High Efficiency Video Coding (HEVC) -- Algorithms and Architectures》的相關章節的閱讀筆記,對HEVC的幀內預測演算法做一個比較完整的闡述。】

【摘要】HEVC的幀內預測的架構分為三個步驟:①構建參考畫素陣列;②生成預測畫素;③後處理操作。HEVC標準將這三個步驟進行了精密設計,以求達到較高的編碼效率,同時降低編碼和解碼端的運算要求。HEVC標準的多種預定義的預測模式構成一個模式集合,可以對包括視訊和靜態影象中的多種內容進行預測建模的方法。HEVC的角度預測提供了對包含方向性紋理的物體更高的預測精確度,此外平面和DC模式可以高效地表示影象的平滑區域。

1.引言

HEVC的幀內預測方法可以分為兩大類:第一類為角度預測,用於準確預測角度結構;第二類為平面模式和DC模式,用於估計平滑影象區域。HEVC總計支援35種幀內預測的模式,如下表所示:


HEVC所有的35種預測模式使用相鄰的重建畫素塊的資訊作為參考畫素。由於重建畫素塊在Transform Block級別上實現,執行幀內預測操作的資料同Transform Block大小相同,從4×4到32×32。HEVC支援各種塊尺寸上的各種預測模式,因此各種預測組合模式的數量龐大,HEVC對於各種預測模式進行了特別設計以利於對任意塊尺寸/模式進行演算法的實現。為了提高獲得更準確預測的可能性,HEVC在進行實際的幀內預測過程之前,對參考畫素進行濾波預處理操作。某些預測模式還包含後處理操作用於優化畫素塊邊緣的連續性,如DC模式,水平模式(Mode 10)和垂直模式(Mode 26)。

2、生成參考畫素

HEVC幀內預測由重建的參考畫素根據預測模式推導而得。相比較H.264,HEVC引入了參考畫素替換方法,該方法無論相鄰畫素是否可得都可以獲取完整的參考畫素集合。另外,HEVC還定義了自適應的濾波過程,可以根據預測模式、塊大小和方向性對參考畫素進行預濾波處理。

(1)參考畫素替換

有時候由於多種原因,幀內預測的時候無法獲取部分或全部參考畫素。例如,位於影象、slice或ties之外的畫素在預測中被認為是不可得的。另外,當constrained intra prediction開啟時,來自幀間預測的PU的畫素將會被忽略,這是為了防止前面被錯誤地傳輸和重建的影象對當前影象造成影響。

在H.264中,這些情況都會被當做DC模式進行處理,而在HEVC中,在對無效的參考畫素進行替換後可以當做普通的畫素塊進行處理。

對於極端的情況,即所有參考畫素都不可得的情況下,所有參考點都被設定為一個常量(8bit下為128)。

在HM10中的方法:

Void TComPattern::initAdiPattern( TComDataCU* pcCU, UInt uiZorderIdxInPart, UInt uiPartDepth, Int* piAdiBuf, Int iOrgBufStride, Int iOrgBufHeight, Bool& bAbove, Bool& bLeft, Bool bLMmode )
{
    //.......
    //計算有效相鄰點的個數
    bNeighborFlags[iNumUnitsInCu*2] = isAboveLeftAvailable( pcCU, uiPartIdxLT );
  iNumIntraNeighbor  += (Int)(bNeighborFlags[iNumUnitsInCu*2]);
  iNumIntraNeighbor  += isAboveAvailable     ( pcCU, uiPartIdxLT, uiPartIdxRT, bNeighborFlags+(iNumUnitsInCu*2)+1 );
  iNumIntraNeighbor  += isAboveRightAvailable( pcCU, uiPartIdxLT, uiPartIdxRT, bNeighborFlags+(iNumUnitsInCu*3)+1 );
  iNumIntraNeighbor  += isLeftAvailable      ( pcCU, uiPartIdxLT, uiPartIdxLB, bNeighborFlags+(iNumUnitsInCu*2)-1 );
  iNumIntraNeighbor  += isBelowLeftAvailable ( pcCU, uiPartIdxLT, uiPartIdxLB, bNeighborFlags+ iNumUnitsInCu   -1 );
    //......
    fillReferenceSamples (g_bitDepthY, piRoiOrigin, piAdiTemp, bNeighborFlags, iNumIntraNeighbor, iUnitSize, iNumUnitsInCu, iTotalUnits, uiCuWidth, uiCuHeight, uiWidth, uiHeight, iPicStride, bLMmode);
    //......
}

其中fillReferenceSamples的實現如下:
Void TComPattern::fillReferenceSamples(Int bitDepth, Pel* piRoiOrigin, Int* piAdiTemp, Bool* bNeighborFlags, Int iNumIntraNeighbor, Int iUnitSize, Int iNumUnitsInCu, Int iTotalUnits, UInt uiCuWidth, UInt uiCuHeight, UInt uiWidth, UInt uiHeight, Int iPicStride, Bool bLMmode )
{
  Pel* piRoiTemp;
  Int  i, j;
  Int  iDCValue = 1 << (bitDepth - 1);

  if (iNumIntraNeighbor == 0)
  {
    // Fill border with DC value
    for (i=0; i<uiWidth; i++)
    {
      piAdiTemp[i] = iDCValue;
    }
    for (i=1; i<uiHeight; i++)
    {
      piAdiTemp[i*uiWidth] = iDCValue;
    }
  }
  //......
}
當左側和上方的相鄰畫素均可得時,參考畫素集合直接拷貝這些畫素。在HM中也在fillReferenceSamples中實現:
Void TComPattern::fillReferenceSamples(Int bitDepth, Pel* piRoiOrigin, Int* piAdiTemp, Bool* bNeighborFlags, Int iNumIntraNeighbor, Int iUnitSize, Int iNumUnitsInCu, Int iTotalUnits, UInt uiCuWidth, UInt uiCuHeight, UInt uiWidth, UInt uiHeight, Int iPicStride, Bool bLMmode )
{
  //......
  else if (iNumIntraNeighbor == iTotalUnits)
  {
    // Fill top-left border with rec. samples
    piRoiTemp = piRoiOrigin - iPicStride - 1;
    piAdiTemp[0] = piRoiTemp[0];

    // Fill left border with rec. samples
    piRoiTemp = piRoiOrigin - 1;

    if (bLMmode)
    {
      piRoiTemp --; // move to the second left column
    }

    for (i=0; i<uiCuHeight; i++)
    {
      piAdiTemp[(1+i)*uiWidth] = piRoiTemp[0];
      piRoiTemp += iPicStride;
    }

    // Fill below left border with rec. samples
    for (i=0; i<uiCuHeight; i++)
    {
      piAdiTemp[(1+uiCuHeight+i)*uiWidth] = piRoiTemp[0];
      piRoiTemp += iPicStride;
    }

    // Fill top border with rec. samples
    piRoiTemp = piRoiOrigin - iPicStride;
    for (i=0; i<uiCuWidth; i++)
    {
      piAdiTemp[1+i] = piRoiTemp[i];
    }
    
    // Fill top right border with rec. samples
    piRoiTemp = piRoiOrigin - iPicStride + uiCuWidth;
    for (i=0; i<uiCuWidth; i++)
    {
      piAdiTemp[1+uiCuWidth+i] = piRoiTemp[i];
    }
  }
  //......
}

如果至少一個畫素為可用資料,其他不可得的畫素都將使用該畫素按順時針的順序進行替換。

①當p[-1][2N-1]不可得時,則沿著p[-1][2N-2],p[-1][2N-3].....p[-1][-1],p[0][-1],p[1][-1]......p[2N-1][-1],即先自下而上,後從左向右查詢,由找到的第一個有效的點替換。

②所有的無效的點p[-1][y], y=2N-2~1,都由p[-1][y+1]替換,即對於垂直方向的參考畫素,無效點都由下方的點替換。

③所有的無效的點p[x][-1], x=0~2N-1,都由p[x-1][-1]替換,即對於水平方向的參考畫素,無效點都由左側的點替換。

fillReferenceSamples函式的最後一個選擇結構中實現的便是該方法:
Void TComPattern::fillReferenceSamples(Int bitDepth, Pel* piRoiOrigin, Int* piAdiTemp, Bool* bNeighborFlags, Int iNumIntraNeighbor, Int iUnitSize, Int iNumUnitsInCu, Int iTotalUnits, UInt uiCuWidth, UInt uiCuHeight, UInt uiWidth, UInt uiHeight, Int iPicStride, Bool bLMmode )
{
  //......
  else // reference samples are partially available
  {
    Int  iNumUnits2 = iNumUnitsInCu<<1;
    Int  iTotalSamples = iTotalUnits*iUnitSize;
    Pel  piAdiLine[5 * MAX_CU_SIZE];
    Pel  *piAdiLineTemp; 
    Bool *pbNeighborFlags;
    Int  iNext, iCurr;
    Pel  piRef = 0;

    // Initialize
    for (i=0; i<iTotalSamples; i++)
    {
      piAdiLine[i] = iDCValue;
    }
    
    // Fill top-left sample
    piRoiTemp = piRoiOrigin - iPicStride - 1;
    piAdiLineTemp = piAdiLine + (iNumUnits2*iUnitSize);
    pbNeighborFlags = bNeighborFlags + iNumUnits2;
    if (*pbNeighborFlags)
    {
      piAdiLineTemp[0] = piRoiTemp[0];
      for (i=1; i<iUnitSize; i++)
      {
        piAdiLineTemp[i] = piAdiLineTemp[0];
      }
    }

    // Fill left & below-left samples
    piRoiTemp += iPicStride;
    if (bLMmode)
    {
      piRoiTemp --; // move the second left column
    }
    piAdiLineTemp--;
    pbNeighborFlags--;
    for (j=0; j<iNumUnits2; j++)
    {
      if (*pbNeighborFlags)
      {
        for (i=0; i<iUnitSize; i++)
        {
          piAdiLineTemp[-i] = piRoiTemp[i*iPicStride];
        }
      }
      piRoiTemp += iUnitSize*iPicStride;
      piAdiLineTemp -= iUnitSize;
      pbNeighborFlags--;
    }

    // Fill above & above-right samples
    piRoiTemp = piRoiOrigin - iPicStride;
    piAdiLineTemp = piAdiLine + ((iNumUnits2+1)*iUnitSize);
    pbNeighborFlags = bNeighborFlags + iNumUnits2 + 1;
    for (j=0; j<iNumUnits2; j++)
    {
      if (*pbNeighborFlags)
      {
        for (i=0; i<iUnitSize; i++)
        {
          piAdiLineTemp[i] = piRoiTemp[i];
        }
      }
      piRoiTemp += iUnitSize;
      piAdiLineTemp += iUnitSize;
      pbNeighborFlags++;
    }

    // Pad reference samples when necessary
    iCurr = 0;
    iNext = 1;
    piAdiLineTemp = piAdiLine;
    while (iCurr < iTotalUnits)
    {
      if (!bNeighborFlags[iCurr])
      {
        if(iCurr == 0)
        {
          while (iNext < iTotalUnits && !bNeighborFlags[iNext])
          {
            iNext++;
          }
          piRef = piAdiLine[iNext*iUnitSize];
          // Pad unavailable samples with new value
          while (iCurr < iNext)
          {
            for (i=0; i<iUnitSize; i++)
            {
              piAdiLineTemp[i] = piRef;
            }
            piAdiLineTemp += iUnitSize;
            iCurr++;
          }
        }
        else
        {
          piRef = piAdiLine[iCurr*iUnitSize-1];
          for (i=0; i<iUnitSize; i++)
          {
            piAdiLineTemp[i] = piRef;
          }
          piAdiLineTemp += iUnitSize;
          iCurr++;
        }
      }
      else
      {
        piAdiLineTemp += iUnitSize;
        iCurr++;
      }
    }

    // Copy processed samples
    piAdiLineTemp = piAdiLine + uiHeight + iUnitSize - 2;
    for (i=0; i<uiWidth; i++)
    {
      piAdiTemp[i] = piAdiLineTemp[i];
    }
    piAdiLineTemp = piAdiLine + uiHeight - 1;
    for (i=1; i<uiHeight; i++)
    {
      piAdiTemp[i*uiWidth] = piAdiLineTemp[-i];
    }
  }
}


(2)參考畫素的濾波

同H264的8×8幀內預測類似,HEVC的幀內預測參考畫素都會依據條件進行平滑濾波處理。濾波的目的在於提升幀內預測的畫素塊的視覺效果,減小邊緣可能產生的突變感。是否對參考畫素進行濾波取決於幀內預測模式和預測畫素塊的大小。

當採用DC模式,或預測畫素塊為4×4大小時,不需要進行濾波操作。對於其他情況,塊的大小和預測的方向決定了是否要進行濾波操作。對於8×8大小的編碼畫素塊,僅僅針對對角線方向的預測模式(即模式2,18,34)進行濾波。對於16×16的畫素塊,大部分模式都會進行參考點濾波,除了幾個近似水平和垂直模式(即模式9,10,11,25,27)。對於32×32的畫素塊,除了垂直和水平模式(即模式10和26)外所有的模式都要進行濾波。

對參考畫素進行濾波時,共有兩種濾波方法可供選擇。預設情況下,濾波器採用一種[1,2,1]一維濾波器對參考畫素進行處理。另一種方法,如果畫素塊的大小為32×32,且參考畫素的走勢足夠平坦,那麼參考畫素將依據起點、終點和拐點三個特殊點(p[-1][63],p[-1][-1],p[63][-1])進行線性插值的方法獲得。關於是否“足夠平坦”這一問題,則依以下兩個準則確定(b取表示一個顏色分量的bit位數):

|p[-1][-1]+p[2N-1][-1]-2p[N-1][-1]| < (1<<(b-5))

|p[-1][-1]+p[-1][2N-1]-2p[-1][N-1]| < (1<<(b-5))

HM中的實現方法如:

Void TComPattern::initAdiPattern( TComDataCU* pcCU, UInt uiZorderIdxInPart, UInt uiPartDepth, Int* piAdiBuf, Int iOrgBufStride, Int iOrgBufHeight, Bool& bAbove, Bool& bLeft, Bool bLMmode )  
{  
//......  
if (pcCU->getSlice()->getSPS()->getUseStrongIntraSmoothing())  
  {  
    Int blkSize = 32;  
    Int bottomLeft = piFilterBuf[0];  
    Int topLeft = piFilterBuf[uiCuHeight2];  
    Int topRight = piFilterBuf[iBufSize-1];  
    Int threshold = 1 << (g_bitDepthY - 5);  
    Bool bilinearLeft = abs(bottomLeft+topLeft-2*piFilterBuf[uiCuHeight]) < threshold;  
    Bool bilinearAbove  = abs(topLeft+topRight-2*piFilterBuf[uiCuHeight2+uiCuHeight]) < threshold;  
    
    if (uiCuWidth>=blkSize && (bilinearLeft && bilinearAbove))  
    {  
      Int shift = g_aucConvertToBit[uiCuWidth] + 3;  // log2(uiCuHeight2)  
      piFilterBufN[0] = piFilterBuf[0];  
      piFilterBufN[uiCuHeight2] = piFilterBuf[uiCuHeight2];  
      piFilterBufN[iBufSize - 1] = piFilterBuf[iBufSize - 1];  
      for (i = 1; i < uiCuHeight2; i++)  
      {  
        piFilterBufN[i] = ((uiCuHeight2-i)*bottomLeft + i*topLeft + uiCuHeight) >> shift;  
      }  
    
      for (i = 1; i < uiCuWidth2; i++)  
      {  
        piFilterBufN[uiCuHeight2 + i] = ((uiCuWidth2-i)*topLeft + i*topRight + uiCuWidth) >> shift;  
      }  
    }  
    else   
    {  
      // 1. filtering with [1 2 1]  
      piFilterBufN[0] = piFilterBuf[0];  
      piFilterBufN[iBufSize - 1] = piFilterBuf[iBufSize - 1];  
      for (i = 1; i < iBufSize - 1; i++)  
      {  
        piFilterBufN[i] = (piFilterBuf[i - 1] + 2 * piFilterBuf[i]+piFilterBuf[i + 1] + 2) >> 2;  
      }  
    }  
  }  
  else   
  {  
    // 1. filtering with [1 2 1]  
    piFilterBufN[0] = piFilterBuf[0];  
    piFilterBufN[iBufSize - 1] = piFilterBuf[iBufSize - 1];  
    for (i = 1; i < iBufSize - 1; i++)  
    {  
      piFilterBufN[i] = (piFilterBuf[i - 1] + 2 * piFilterBuf[i]+piFilterBuf[i + 1] + 2) >> 2;  
    }  
  }  
//......  
}