VVC代碼 BMS 幀內預測學習之六:Planar、DC及角度模式下預測值的計算 xPredIntraPlanar(),xPredIntraDC(),xPredIntraAng()

1、Planar模式,函數xPredIntraPlanar()
預測像素是水平、垂直兩個方向上4個參考像素的平均值。
獲取左側及上方的參考像素,隨後給右側及下方參考像素賦值:
BL = leftColumn[height], TR = topRow[width]
rightColumn[y] = TR - leftColumn[y]
bottomRow[x] = BL - topRow[x]
預測值首先分爲垂直,水平兩部分:

verPred[x] = topRow[x] * h + bottomRow[x] * ( y + 1) 
		   = topRow[x] * h + ( BL - topRow[x] ) * ( y + 1) 
	   	   = topRow[x] * ( h - y -1 ) + BL * ( y + 1) 
		
horPred[y] = leftColumn[y] * w + rightColumn[y] * ( x + 1) 
		   = leftColumn[y] * w + ( TR - leftColumn[y] ) * ( x + 1) 
		   = leftColumn[y] * ( w - x -1 ) + TR* ( x + 1) 
		
pred[x][y] = (horPred[y] * h + verPred[x]   * w + w * h) >> ( 1 + log2w + log2h)

2、DC模式,函數xPredIntraDC()
DC模式下,首先通過函數xGetPredValDc()獲取平均值dcval,爲上方像素與左側像素和的平均值(不包括左上角像素),將當前塊的全部像素賦值爲dcval(在BMS中,HEVC中默認的亮度預測塊DC模式下的相關加權處理默認關閉)。

3、角度模式,函數xPredIntraAng()

3.1 角度模式預測值計算的理論
假設預測塊上一個預測位置(x,y)(x,y)的預測值爲p(x,y)p(x,y),設偏移值爲d[32,32]d\in[-32, 32](個人理解:偏移值最大應該爲正負1,在函數處理中以32爲單位進行),當前點在預測過程中實際投影到參考像素的座標,相對於當前點座標,位移爲CxCx(假設投影爲x方向)。

(圖片摘自《H.263/HEVC視頻編碼新標準及其擴展》)

滿足相似三角形的特性:
CxCx/y=d/32/y = d/32

所以可以知道:
Cx=(yd)/32Cx = (y*d)/32
( 爲函數中後續循環的deltaInt )

deltaFract=(yd)&31deltaFract = (y*d)\&31
( 去掉大於32的部分,即爲當前像素投影至參考像素的投影位置,到左側相鄰的整像素點的位置,同時作爲後續求預測值的權重 )

對於65種角度模式而言,其對應的偏移值d表如下:

模式號 偏移值 模式號 偏移值 模式號 偏移值 模式號 偏移值 模式號 偏移值
2 32 17 1 32 -26 47 -3 62 21
3 29 18 0 33 -29 48 -2 63 23
4 26 19 -1 34 -32 49 -1 64 26
5 23 20 -2 35 -29 50 0 65 29
6 21 21 -3 36 -26 51 1 66 32
7 19 22 -5 37 -23 52 2
8 17 23 -7 38 -21 53 3
9 15 24 -9 39 -19 54 5
10 13 25 -11 40 -17 55 7
11 11 26 -13 41 -15 56 9
12 9 27 -15 42 -13 57 11
13 7 28 -17 43 -11 58 13
14 5 29 -19 44 -9 59 15
15 3 30 -21 45 -7 60 17
16 2 31 -23 46 -5 61 19

反角度參數用來消除計算預測值過程中的除法運算,用查表來代替。偏移值的正值與反角度參數的值對應關係如下表所示,負值對應的參數取負:

偏移值的正值 反角度參數 偏移值的正值 反角度參數 偏移值的正值 反角度參數
0 0 9 910 21 390
1 8192 11 745 23 356
2 4096 13 630 26 315
3 2731 15 546 29 282
5 1638 17 482 32 256
7 1170 19 431

3.2 注意事項

  • a、角度模式下,爲了便於後續循環的統一書寫(寫爲width),統一將水平類模式下的寬/高值進行交換,即將其旋轉90度(此說法便於理解)進行預測,預測完成後,再將其旋轉歸位。
  • b、在預測實現的過程中,參考像素是存儲在一維數組refMain中的。

3.3 代碼解析

//注:本文的函數中已經將默認關閉的宏相關內容刪除。
Void IntraPrediction::xPredIntraAng( const CPelBuf &pSrc, PelBuf &pDst, const ChannelType channelType, const UInt dirMode, const ClpRng& clpRng, const SPS& sps, const bool enableBoundaryFilter )
{
  Int width =Int(pDst.width);
  Int height=Int(pDst.height);

  CHECK( !( dirMode > DC_IDX && dirMode < NUM_LUMA_MODE ), "Invalid intra dir" );

  const Bool       bIsModeVer         = (dirMode >= DIA_IDX);
  const Int        intraPredAngleMode = (bIsModeVer) ? (Int)dirMode - VER_IDX :  -((Int)dirMode - HOR_IDX);
  const Int        absAngMode         = abs(intraPredAngleMode);
  const Int        signAng            = intraPredAngleMode < 0 ? -1 : 1;


  // Set bitshifts and scale the angle parameter to block size

  static const Int angTable[17]    = { 0,    1,    2,    3,    5,    7,    9,   11,   13,   15,   17,   19,   21,   23,   26,   29,   32 };
  static const Int invAngTable[17] = { 0, 8192, 4096, 2731, 1638, 1170,  910,  745,  630,  546,  482,  431,  390,  356,  315,  282,  256 }; // (256 * 32) / Angle

  Int invAngle                    = invAngTable[absAngMode];
  Int absAng                      = angTable   [absAngMode];
  Int intraPredAngle              = signAng * absAng;//上文表中的偏移值

  Pel* refMain;//主參考
  Pel* refSide;//副參考

  Pel  refAbove[2 * MAX_CU_SIZE + 1];
  Pel  refLeft [2 * MAX_CU_SIZE + 1];

  // Initialize the Main and Left reference array.
    //****************************************** refMain[*]獲取過程 ************************************************
    //************************** 第一個表中模式對應的偏移值 < 0 ********************************
  if (intraPredAngle < 0)//即模式[19,49],此時需要進行“投影像素”法
  {
    for( Int x = 0; x < width + 1; x++ )
    {
      refAbove[x + height - 1] = pSrc.at( x, 0 );//獲取上方參考像素,置於refAbove[height - 1]開始及之後,共width+1個
    }
    for( Int y = 0; y < height + 1; y++ )
    {
      refLeft[y + width - 1] = pSrc.at( 0, y );//獲取上方參考像素,置於refLeft[width - 1]開始及之後,共height +1個
    }
    refMain = (bIsModeVer ? refAbove + height : refLeft  + width ) - 1;//refMain指向水平類/垂直類對應ref數據開始的部分。若爲垂直類,refAbove 爲主參考,refMain指向refAbove數據開始的部分。
    refSide = (bIsModeVer ? refLeft  + width  : refAbove + height) - 1;//refSide 指向水平類/垂直類對應ref數據開始的部分。若爲垂直類,refLeft  爲副參考。

    // Extend the Main reference to the left.即“投影像素”過程,將副參考值對應給refMain空缺的部分
    Int invAngleSum    = 128;       // rounding for (shift by 8)
    const Int refMainOffsetPreScale = bIsModeVer ? height : width;
    for( Int k = -1; k > (refMainOffsetPreScale * intraPredAngle) >> 5; k-- )
    {
      invAngleSum += invAngle;
      refMain[k] = refSide[invAngleSum>>8];//所有相關的參考像素值均放置在refMain中
      //refMain[k] = refSide[((k*invAngle)+128)>>8], k = -1……,H或者W
      //此操作中利用反角度參數invAngle避免除法運算
    }
  }
  //************************** 偏移值 > 0********************************
  else
  {
    for( Int x = 0; x < width + height + 1; x++ )
    {
      refAbove[x] = pSrc.at(x, 0);//refAbove[*]爲參考樣本對應位置的值,共width + height + 1個
      refLeft[x]  = pSrc.at(0, x);
    }
    refMain = bIsModeVer ? refAbove : refLeft ;//水平類、垂直類refMain不同
    refSide = bIsModeVer ? refLeft  : refAbove;
  }

  // swap width/height if we are doing a horizontal mode:
  //爲了簡化後續步驟進行的操作,否則需要判斷是根據高還是寬進行循環
  Pel tempArray[MAX_CU_SIZE*MAX_CU_SIZE];
  const Int dstStride = bIsModeVer ? pDst.stride : MAX_CU_SIZE;
  Pel *pDstBuf = bIsModeVer ? pDst.buf : tempArray;
  if (!bIsModeVer)
  {
    std::swap(width, height);
  }

  //****************************************** 預測值賦值過程 ************************************************
  if( intraPredAngle == 0 )  // pure vertical or pure horizontal,純水平或垂直模式,即模式18/50
  {
    for( Int y = 0; y < height; y++ )
    {
      for( Int x = 0; x < width; x++ )
      {
        pDstBuf[y*dstStride + x] = refMain[x + 1];//refMain[1] ~ refMain[width] 賦值給pDstBuf的每一行
      }
    }

  }
  else//非純水平/垂直模式
  {
    Pel *pDsty=pDstBuf;//指向預測緩存的指針

    for (Int y=0, deltaPos=intraPredAngle; y<height; y++, deltaPos+=intraPredAngle, pDsty+=dstStride)
    {
    //deltaPos = (y+1)*intraPredAngle(即理論闡述中的偏移值d)
      const Int deltaInt   = deltaPos >> 5;//計算當前像素對應參考像素在ref中的位置,此操作獲取的是Cx,在後續計算中還需要+x+1(+1是由於循環從0開始,根據您後續代碼自行理解)
      const Int deltaFract = deltaPos & (32 - 1);//計算當前像素對應參考像素的加權因子

      if( deltaFract )//加權因子是否爲1,即判斷是否需要進行插值,爲1表示投影點在非整像素位置,需要插值。
      {
#if JEM_TOOLS
//4抽頭濾波在BMS中目前默認關閉,因爲是新技術,故在本文的代碼中保留
        if( sps.getSpsNext().getUseIntra4Tap() )//採用4抽頭插值濾波器
        {
          Int         p[4];
          const Bool  useCubicFilter = (width <= 8);
          const Int  *f              = (useCubicFilter) ? g_intraCubicFilter[deltaFract] : g_intraGaussFilter[deltaFract];//根據寬是否小於等於8,判斷使用立方體濾波器還是高斯濾波器,獲取濾波係數
          Int         refMainIndex   = deltaInt + 1;

          for( Int x = 0; x < width; x++, refMainIndex++ )
          {
            p[1] = refMain[refMainIndex];//獲取refMain中對應位置的參考像素值
            p[2] = refMain[refMainIndex + 1];

            p[0] = x == 0 ? p[1] : refMain[refMainIndex - 1];//邊界處理
            p[3] = x == (width - 1) ? p[2] : refMain[refMainIndex + 2];//邊界處理

            pDstBuf[y*dstStride + x] =  (Pel)((f[0] * p[0] + f[1] * p[1] + f[2] * p[2] + f[3] * p[3] + 128) >> 8);//四抽頭濾波操作

            if( useCubicFilter ) // only cubic filter has negative coefficients and requires clipping
            {
              pDstBuf[y*dstStride + x] = ClipPel( pDstBuf[y*dstStride + x], clpRng );
            }
          }
        }
        else//採用線性插值
#endif
        {
          // Do linear filtering
          const Pel *pRM = refMain + deltaInt + 1;
          Int lastRefMainPel = *pRM++;//左側相鄰整像素
          for( Int x = 0; x < width; pRM++, x++ )
          {
            Int thisRefMainPel = *pRM;//右側相鄰整像素
            //計算預測值
            pDsty[x + 0] = ( Pel ) ( ( ( 32 - deltaFract )*lastRefMainPel + deltaFract*thisRefMainPel + 16 ) >> 5 );
            lastRefMainPel = thisRefMainPel;//左側指向右側,再循環時右側指向下一個
          }
        }
      }
      else//無需插值
      {
        // Just copy the integer samples
        for( Int x = 0; x < width; x++ )
        {
          pDsty[x] = refMain[x + deltaInt + 1];
        }
      }
    }

  }

  // Flip the block if this is the horizontal mode,水平模式下旋轉當前塊
  if( !bIsModeVer )
  {
    for( Int y = 0; y < height; y++ )
    {
      for( Int x = 0; x < width; x++ )
      {
        pDst.at( y, x ) = pDstBuf[x];
      }
      pDstBuf += dstStride;
    }
  }
#if JEM_TOOLS && JEM_USE_INTRA_BOUNDARY //JEM_USE_INTRA_BOUNDARY 默認 0,不過因爲是邊界值平滑濾波這一新技術,所以放到這裏,具體參見上一篇文章

  if( sps.getSpsNext().getUseIntraBoundaryFilter() && enableBoundaryFilter && isLuma( channelType ) && width > 2 && height > 2 )
  {
    if( dirMode == VDIA_IDX )
    {
      xIntraPredFilteringMode34( pSrc, pDst );
    }
    else  if( dirMode == 2 )
    {
      xIntraPredFilteringMode02( pSrc, pDst );
    }
    else if( ( dirMode <= 10 && dirMode > 2 ) || ( dirMode >= ( VDIA_IDX - 8 ) && dirMode < VDIA_IDX ) )
    {
      xIntraPredFilteringModeDGL( pSrc, pDst, dirMode );
    }
  }
#endif
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章