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
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章