1. 程式人生 > >VVC程式碼 BMS 幀內預測學習之六:Planar、DC及角度模式下預測值的計算

VVC程式碼 BMS 幀內預測學習之六:Planar、DC及角度模式下預測值的計算

1、Planar模式,函式xPredIntraPlanar(): 預測畫素是水平、垂直兩個方向上4個參考畫素的平均值。 left, top為預測畫素正左,正上方參考畫素值; right = leftColumn[height]- left, bottom = topRow[width] - top; 最終預測值:

pred = (( left << log2W + right ) << log2H
			+ ( top << log2H + bottom ) << log2W + W*H)	//W * H = offset
			>> (
1 + log2W + log2H)

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

3、角度模式,函式xPredIntraAng(): 注:

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

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

模式號 偏移值 模式號 偏移值 模式號 偏移值 模式號 偏移值 模式號 偏移值
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
//注:本文的函式中已經將預設關閉的巨集相關內容刪除。
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中
    }
  }
  //************************** 偏移值 > 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)
    {
      const Int deltaInt   = deltaPos >> 5;//計算當前畫素對應參考畫素在ref中的位置
      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
}