H.266/VVC代码学习21:帧内角度预测的实现 / 近对角模式的PDPC(xPredIntraAng)

  • Post author:
  • Post category:其他


xPredIntraAng函数的作用是对任意大小的块和任意模式,如何将参考像素的值根据其模式的角度填充进每一个像素。

下图是basketball drill的一个16*16的块,其预测模式为10(偏斜上方),得到如下预测结果:

在这里插入图片描述

水平模式18可以简单地通过投影的方式得到预测值。但由上图可见,10这个模式并不能简单地通过投影得到像素值。如果单纯看代码,会感觉到这是一个不讲道理的填充方式。

这个函数的最后还进行了近对角模式(2-10和58-66)的PDPC。有关PDPC的内容:

H.266/VVC代码学习20:PDPC技术(predIntraAng)


关于该预测的计算方法,见万帅书P122-123。如果想对这个黑盒进行理解,见朱秀昌书P98-99

VTM4.0中代码如下,可结合注释阅读:

#if JVET_M0102_INTRA_SUBPARTITIONS
void IntraPrediction::xPredIntraAng( const CPelBuf &pSrc, PelBuf &pDst, const ChannelType channelType, const uint32_t dirMode, const ClpRng& clpRng, const SPS& sps,
                                           int      multiRefIdx,//参考行索引
                                     const bool     useFilteredPredSamples ,
                                     const bool     useISP,
                                     const Size     cuSize )
#else
void IntraPrediction::xPredIntraAng( const CPelBuf &pSrc, PelBuf &pDst, const ChannelType channelType, const uint32_t dirMode, const ClpRng& clpRng, const SPS& sps
  , int multiRefIdx
  , const bool useFilteredPredSamples )
#endif
#endif
{
	/************************************************ 初始化 *********************************************/
  int width =int(pDst.width);	//块的宽
  int height=int(pDst.height);	//块的高

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

#if JVET_M0102_INTRA_SUBPARTITIONS
  int              predMode           = useISP ? getWideAngle( cuSize.width, cuSize.height, dirMode ) : getWideAngle( width, height, dirMode );//宽角度模式下的模式编号
#else
  int              predMode           = getWideAngle(width, height, dirMode);
#endif
  const bool       bIsModeVer         = predMode >= DIA_IDX;//大于等于34,视为垂直类型的模式,
  const int        intraPredAngleMode = (bIsModeVer) ? predMode - VER_IDX : -(predMode - HOR_IDX);//-16~16之间的值,越靠近2和66越大(16),越靠近34越小(-16)
  const int        absAngMode         = abs(intraPredAngleMode);//0~16的值
  const int        signAng            = intraPredAngleMode < 0 ? -1 : 1;//19~49是-1,其余为1
#if HEVC_USE_HOR_VER_PREDFILTERING
  const bool       edgeFilter         = bEnableEdgeFilters && isLuma(channelType) && (width <= MAXIMUM_INTRA_FILTERED_WIDTH) && (height <= MAXIMUM_INTRA_FILTERED_HEIGHT);
#endif

  // Set bitshifts and scale the angle parameter to block size 设置位移并将角度参数缩放为块大小
  // 设置invAngTable的nudity是为了消除帧内角度预测模式在计算预测值时麻烦的除法运算;
  static const int angTable[32]    = { 0,    1,    2,    3,    4,    6,     8,   10,   12,   14,   16,   18,   20,   23,   26,   29,   32,   35,   39,  45,  51,  57,  64,  73,  86, 102, 128, 171, 256, 341, 512, 1024 };
  static const int invAngTable[32] = { 0, 8192, 4096, 2731, 2048, 1365,  1024,  819,  683,  585,  512,  455,  410,  356,  315,  282,  256,  234,  210, 182, 160, 144, 128, 112,  95,  80,  64,  48,  32,  24,  16,    8 }; // (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 + 3 + 33 * MAX_REF_LINE_IDX];//上侧参考像素
  Pel  refLeft [2 * MAX_CU_SIZE + 3 + 33 * MAX_REF_LINE_IDX];//左侧参考像素

#if JVET_M0102_INTRA_SUBPARTITIONS
  const int whRatio = useISP ? std::max( unsigned( 1 ), cuSize.width / cuSize.height ) : std::max( 1, width / height );//宽高比,最小为1
  const int hwRatio = useISP ? std::max( unsigned( 1 ), cuSize.height / cuSize.width ) : std::max( 1, height / width );//高宽比,最小为1
#else
  int whRatio = std::max(1, width / height);
  int hwRatio = std::max(1, height / width);
#endif

  // Initialize the Main and Left reference array.
  /************************************** 获取并初始化参考像素(左和上) **********************************/

  /********** 模式19-49的操作 **********/
  if (intraPredAngle < 0)//模式19~49
  {
    auto width    = int(pDst.width) +1;//扩展的宽
    auto height   = int(pDst.height)+1;//扩展的高
    auto lastIdx  = (bIsModeVer ? width : height) + multiRefIdx;//如果是垂直类,用宽度,否则用高度
    auto firstIdx = ( ((bIsModeVer ? height : width) -1) * intraPredAngle ) >> 5;
    for (int x = 0; x < width + 1 + multiRefIdx; x++)//填充上侧参考像素(这些模式下只需要块正上方那部分即可)
    {
      refAbove[x + height - 1] = pSrc.at( x, 0 );
    }
    for (int y = 0; y < height + 1 + multiRefIdx; y++)//填充左侧参考像素(这些模式下只需要块正左方那部分即可)
    {
      refLeft[y + width - 1] = pSrc.at( 0, y );
    }
    refMain = (bIsModeVer ? refAbove + height : refLeft  + width ) - 1;//主要侧
    refSide = (bIsModeVer ? refLeft  + width  : refAbove + height) - 1;//次要侧

    // Extend the Main reference to the left. 把主参考行放到左边(实际上不用左和上两部分预测,只用上面一排)
    int invAngleSum    = 128;       // rounding for (shift by 8)
    for( int k = -1; k > firstIdx; k-- )//即数组负数标号的部分填充左边的值
    {
      invAngleSum += invAngle;
      refMain[k] = refSide[invAngleSum>>8];
    }
    refMain[lastIdx] = refMain[lastIdx-1];		//处理最边界:填充左下角的值
    refMain[firstIdx] = refMain[firstIdx+1];	//处理最边界:填充右上角的值
  }
  /****** 模式2-18 和 50-66的操作 *****/
  else
  {
    for (int x = 0; x < m_topRefLength + 1 + (whRatio + 1) * multiRefIdx; x++)//填充上方参考像素
    {
      refAbove[x+1] = pSrc.at(x, 0);
    }
    for (int y = 0; y < m_leftRefLength + 1 + (hwRatio + 1) * multiRefIdx; y++)//填充左方参考像素
    {
      refLeft[y+1]  = pSrc.at(0, y);
    }
    refMain = bIsModeVer ? refAbove : refLeft ;//主要侧
    refSide = bIsModeVer ? refLeft  : refAbove;//次要侧

    refMain++;//指向第一个像素的正主要侧方
    refSide++;//指向第一个像素的正次要侧方
    refMain[-1] = refMain[0];				//处理最边界:把第一个像素的左上角填充上一个像素的正主要侧方
    auto lastIdx = 1 + ((bIsModeVer) ? m_topRefLength + (whRatio + 1) * multiRefIdx : m_leftRefLength +  (hwRatio + 1) * multiRefIdx);
    refMain[lastIdx] = refMain[lastIdx-1];	//处理最边界:把右上角填充上最右侧的值或左下角填充上最下侧像素
  }

  // swap width/height if we are doing a horizontal mode:
  /******************** 为方便,若不是垂直模式,把width和height交换 *******************/
  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);
  }

  // compensate for line offset in reference line buffers 
  /****************************** 找到参考像素中的多参考行的选中行 **********************/
  refMain += multiRefIdx;
  refSide += multiRefIdx;

  /***************************************** 对纯水平或垂直类型的操作 ******************************************/
  if( intraPredAngle == 0 )  // pure vertical or pure horizontal
  {
    for( int y = 0; y < height; y++ )
    {
      for( int x = 0; x < width; x++ )
      {
        pDstBuf[y*dstStride + x] = refMain[x + 1];//直接投影即可
      }
    }
#if HEVC_USE_HOR_VER_PREDFILTERING
    if (edgeFilter && multiRefIdx == 0)
    {
      for( int y = 0; y < height; y++ )
      {
        pDstBuf[y*dstStride] = ClipPel( pDstBuf[y*dstStride] + ( ( refSide[y + 1] - refSide[0] ) >> 1 ), clpRng );
      }
    }
#endif
  }
  /*************************************** 对不是纯水平或垂直类型的操作 ****************************************/
  else
  {
    Pel *pDsty=pDstBuf;//重建buffer
    for (int y = 0, deltaPos = intraPredAngle * (1 + multiRefIdx); y<height; y++, deltaPos += intraPredAngle, pDsty += dstStride)
    {//对高度循环
      const int deltaInt   = deltaPos >> 5;			//当前像素对应参考像素在“改变为只用上一行”中的位置;
      const int deltaFract = deltaPos & (32 - 1);	//当前像素对应参考像素的加权因子。这两个值参见万帅书P123

	  /******** 当模式不是2,34,66的条件下,对宽度如下循环 *******/
      if (absAng != 0 && absAng != 32)//模式是不是2,18,34,50,66,如是,则不能直接投影
      {
		  /** 如果是亮度,则四抽头滤波 **/
        if( isLuma(channelType) )
        {
          Pel                        p[4];
#if JVET_M0102_INTRA_SUBPARTITIONS
          const bool                 useCubicFilter = useISP ? ( width <= 8 ) : ( !useFilteredPredSamples || multiRefIdx > 0 );
#else
          const bool                 useCubicFilter = !useFilteredPredSamples || multiRefIdx > 0;
#endif
          TFilterCoeff const * const f              = (useCubicFilter) ? InterpolationFilter::getChromaFilterTable(deltaFract) : g_intraGaussFilter[deltaFract];//根据deltaFract选取滤波器的类型

          int         refMainIndex   = deltaInt + 1;

          for( int x = 0; x < width; x++, refMainIndex++ )//对参考像素进行四抽头滤波!!!
          {
            p[0] = refMain[refMainIndex - 1];
            p[1] = refMain[refMainIndex];
            p[2] = refMain[refMainIndex + 1];
            p[3] = f[3] != 0 ? refMain[refMainIndex + 2] : 0;


			//插值滤波,f[0], f[1], f[2], f[3]是滤波系数
            pDstBuf[y*dstStride + x] = static_cast<Pel>((static_cast<int>(f[0] * p[0]) + static_cast<int>(f[1] * p[1]) + static_cast<int>(f[2] * p[2]) + static_cast<int>(f[3] * p[3]) + 32) >> 6);

            if( useCubicFilter ) // only cubic filter has negative coefficients and requires clipping
            {//方形滤波器具有负的系数,这时需要进行嵌位
              pDstBuf[y*dstStride + x] = ClipPel( pDstBuf[y*dstStride + x], clpRng );
            }
          }
        }
		  /** 如果是色度,则线性滤波 **/
        else
        {
          // Do linear filtering
          const Pel *pRM = refMain + deltaInt + 1;
          int lastRefMainPel = *pRM++;
          for( int x = 0; x < width; pRM++, x++ )
          {
            int thisRefMainPel = *pRM;
			//计算方式参见万帅书P123
            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];
        }
      }      
	  
	  /************* 后续处理 —— 近对角模式的PDPC ************/
	  const int numModes = 8;
      const int scale = ((g_aucLog2[width] - 2 + g_aucLog2[height] - 2 + 2) >> 2);
      CHECK(scale < 0 || scale > 31, "PDPC: scale < 0 || scale > 31");
#if JVET_M0102_INTRA_SUBPARTITIONS
      if( !useISP )
      {
#endif
      if ((predMode == 2 || predMode == VDIA_IDX) && multiRefIdx == 0)//如果是模式2或66
      {
        int wT = 16 >> std::min(31, ((y << 1) >> scale));

        for (int x = 0; x < width; x++)
        {
          int wL = 16 >> std::min(31, ((x << 1) >> scale));
          if (wT + wL == 0) break;

          int c = x + y + 1;
          if (c >= 2 * height) { wL = 0; }
          if (c >= 2 * width)  { wT = 0; }
          const Pel left = (wL != 0) ? refSide[c + 1] : 0;
          const Pel top  = (wT != 0) ? refMain[c + 1] : 0;

          pDsty[x] = ClipPel((wL * left + wT * top + (64 - wL - wT) * pDsty[x] + 32) >> 6, clpRng);
		  //“ wL * left + wT * top ”是未滤波的,“ (64 - wL - wT) * pDsty[x]” 是滤波后的,“ +32 ”用于四舍五入
        }
      }
      else if (((predMode >= VDIA_IDX - numModes && predMode != VDIA_IDX) || (predMode != 2 && predMode <= (2 + numModes))) && multiRefIdx == 0)//如果是模式58~65和2~10
      {
        int invAngleSum0 = 2;
        for (int x = 0; x < width; x++)
        {
          invAngleSum0 += invAngle;
          int deltaPos0 = invAngleSum0 >> 2;
          int deltaFrac0 = deltaPos0 & 63;
          int deltaInt0 = deltaPos0 >> 6;

          int deltay = y + deltaInt0 + 1;
          if (deltay >(bIsModeVer ? m_leftRefLength : m_topRefLength) - 1) break;

          int wL = 32 >> std::min(31, ((x << 1) >> scale));
          if (wL == 0) break;
          Pel *p = refSide + deltay;

#if JVET_M0238_PDPC_NO_INTERPOLATION
          Pel left = p[deltaFrac0 >> 5];
#else
          Pel left = (((64 - deltaFrac0) * p[0] + deltaFrac0 * p[1] + 32) >> 6);
#endif
          pDsty[x] = ClipPel((wL * left + (64 - wL) * pDsty[x] + 32) >> 6, clpRng);
		  //“ wL * left ”是未滤波的,“ (64 - wL) * pDsty[x]” 是滤波后的,“ +32 ”用于四舍五入
        }
      }
#if JVET_M0102_INTRA_SUBPARTITIONS
      }
#endif
    }
#if HEVC_USE_HOR_VER_PREDFILTERING
    if( edgeFilter && absAng <= 1 )
    {
      for( int y = 0; y < height; y++ )
      {
        pDstBuf[y*dstStride] = ClipPel( pDstBuf[y*dstStride] + ((refSide[y + 1] - refSide[0]) >> 2), clpRng );
      }
    }
#endif
  }

  // 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;
    }
  }
}



版权声明:本文为weixin_42979679原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。