H.266/VVC代码学习:帧内预测之角度预测函数(predIntraAng、xPredIntraAng)

predIntraAng函数

VTM中,帧内预测的角度预测的入口函数为predIntraAng函数,该函数主要是用于进行传统的帧内预测(Planar、DC、角度预测),然后对Planar和DC模式使用PDPC(其余角度模式的PDPC在xPredIntraAng函数中进行)

void IntraPrediction::predIntraAng( const ComponentID compId, PelBuf &piPred, const PredictionUnit &pu)
{
  const ComponentID    compID       = MAP_CHROMA( compId );
  const ChannelType    channelType  = toChannelType( compID );
  const int            iWidth       = piPred.width;
  const int            iHeight      = piPred.height;
#if JVET_P0641_REMOVE_2xN_CHROMA_INTRA
  CHECK(iWidth == 2, "Width of 2 is not supported");
#endif
#if JVET_P0059_CHROMA_BDPCM
  //获取预测模式
  const uint32_t       uiDirMode    = isLuma( compId ) && pu.cu->bdpcmMode ? BDPCM_IDX : !isLuma(compId) && pu.cu->bdpcmModeChroma ? BDPCM_IDX : PU::getFinalIntraMode(pu, channelType);
#else
  const uint32_t       uiDirMode    = isLuma( compId ) && pu.cu->bdpcmMode ? BDPCM_IDX : PU::getFinalIntraMode( pu, channelType );
#endif

  CHECK( floorLog2(iWidth) < 2 && pu.cs->pcv->noChroma2x2, "Size not allowed" );
  CHECK( floorLog2(iWidth) > 7, "Size not allowed" );

  const int srcStride  = m_refBufferStride[compID];
  const int srcHStride = 2;

  const CPelBuf & srcBuf = CPelBuf(getPredictorPtr(compID), srcStride, srcHStride);//参考像素
  const ClpRng& clpRng(pu.cu->cs->slice->clpRng(compID));

  switch (uiDirMode)
  {
    case(PLANAR_IDX): xPredIntraPlanar(srcBuf, piPred); break;//Planar模式
    case(DC_IDX):     xPredIntraDc(srcBuf, piPred, channelType, false); break;//DC模式
#if JVET_P0059_CHROMA_BDPCM
    case(BDPCM_IDX):  xPredIntraBDPCM(srcBuf, piPred, isLuma(compID) ? pu.cu->bdpcmMode : pu.cu->bdpcmModeChroma, clpRng); break;
#else
    case(BDPCM_IDX):  xPredIntraBDPCM(srcBuf, piPred, pu.cu->bdpcmMode, clpRng); break;
#endif
    default:          xPredIntraAng(srcBuf, piPred, channelType, clpRng); break;
  }
  //PDPC,这里进对Planar和DC做,其余角度模式的PDPC在xPredIntraAng函数中进行
  if (m_ipaParam.applyPDPC)
  {
    PelBuf dstBuf = piPred;
    const int scale = ((floorLog2(iWidth) - 2 + floorLog2(iHeight) - 2 + 2) >> 2);
    CHECK(scale < 0 || scale > 31, "PDPC: scale < 0 || scale > 31");

    if (uiDirMode == PLANAR_IDX || uiDirMode == DC_IDX)//PLANAR和DC的PDPC
    {
      for (int y = 0; y < iHeight; y++)
      {
        const int wT   = 32 >> std::min(31, ((y << 1) >> scale)); //32是2的5次方。即超过5就会变为0,不再受上影响
        const Pel left = srcBuf.at(y + 1, 1);
        for (int x = 0; x < iWidth; x++)
        {
          const int wL    = 32 >> std::min(31, ((x << 1) >> scale));
          const Pel top   = srcBuf.at(x + 1, 0);
          const Pel val   = dstBuf.at(x, y);
          dstBuf.at(x, y) = val + ((wL * (left - val) + wT * (top - val) + 32) >> 6);
        }
      }
    }
  }
}

xPredIntraAng函数

角度预测模式的步骤:

  1. 填充参考像素向量ref(包括扩展参考像素)
  2. 根据角度计算投影到参考像素位置的偏移
  3. 根据偏移值获取预测值

1. 填充参考像素向量ref(包括扩展参考像素)

xPredIntraAng函数主要进行传统的角度预测,需要注意的是变量intraPredAngle和absInvAngle

  const int  intraPredAngle = m_ipaParam.intraPredAngle;
  const int  absInvAngle    = m_ipaParam.absInvAngle;

这两变量是在initPredIntraParams函数中初始化的,其中intraPredAngle变量指代的是角度偏移值,,即每一种角度模式都相当于在水平方向或者垂直方向上做了一个角度偏移,从角度模式到偏移值的映射如下表所示:

absInvAngle变量表示反角度参数,具体地计算为

  在VTM中为了消除除法运算,预先将(512 * 32) /Angle的值存了起来,存在了invAngTable表中。

 VTM中具体实现代码如下所示:

    // 先乘以256(左移8位),运算出结果再右移8位,以定点实现浮点运算精度
    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 };
    // 设置invAngTable的目的是为了消除帧内角度预测模式在计算预测值时麻烦的除法运算
    static const int invAngTable[32] = {
      0,   16384, 8192, 5461, 4096, 2731, 2048, 1638, 1365, 1170, 1024, 910, 819, 712, 630, 565,
      512, 468,   420,  364,  321,  287,  256,  224,  191,  161,  128,  96,  64,  48,  32,  16
    };   // (512 * 32) /Angle 投影的位置,用的时候需要右移9位

    const int     absAngMode         = abs(intraPredAngleMode);//值为0~32
    const int     signAng            = intraPredAngleMode < 0 ? -1 : 1;
                  absAng             = angTable  [absAngMode];//当前角度的对应的下标值 得到的表格值

    m_ipaParam.absInvAngle           = invAngTable[absAngMode]; // 每种角度对应的投影位置
    m_ipaParam.intraPredAngle        = signAng * absAng;// 角度偏移值 offset

设置反角度模式是用来扩展参考像素的,具体地:

  • 对于水平类模式(19-33),需要将左侧参考像素投影到上侧参考像素的左侧,以扩展上侧参考像素;
  • 对于垂直类模式(34-49),需要将上侧参考像素投影到左侧参考像素的上方,以扩展左侧参考像素,一个例子如下:

上图中,以预测模式21为例(HEVC中的21),其进行预测时,参考像素为上一行的参考像素。但是对于某些位置,需要用到上一行的左侧的参考像素(即p[-2][-1]、p[-3][-1]、p[-4][-1]位置处的像素),此时的做法是将左侧参考像素投影到上侧参考像素的左侧,以扩展上侧参考像素。

如上图,当x < 0时,需要使用左侧参考进行投影,在投影的过程中,具体投影到的左侧参考像素的位置是通过intraPredAngle变量确定的,即y(x) = -1 + Round(32 * x / intraPredAngle(k)),为了消除除法运算,使用invAngle代替x / intraPredAngle(k),即y(x) = -1 + (x * invAngle)。

注:将参考像素投影到扩展行/列的投影位置可由invAngle得到,即上图x<0的情况。如果投影的时候出现非整数位置处的像素,则使用最近的参考像素进行投影,如下图所示:

2.根据角度计算投影到参考像素位置的偏移

填充完参考像素后,根据角度偏移值intraPredAngle进行预测值的计算,具体的:根据角度偏移值intraPredAngle得到索引变量iIdx和乘法因子iFact:(iFact应该表示的是从待预测像素投影到参考像素的两个整数参考像素间的分像素的位置(以32点为单位),idx表示投影到参考像素的偏移)

对于垂直类角度模式

iIdx = ( ( ( y + 1 + refIdx ) * intraPredAngle )  >>  5 ) + refIdx 

iFact = ( ( y + 1 + refIdx ) * intraPredAngle ) & 31 

其中refIdx表示的是参考行的索引。 

通过iIdx和iFact计算预测值: 

 对于水平类角度模式:

iIdx = ( ( ( x + 1 + refIdx ) * intraPredAngle )  >>  5 ) + refIdx

iFact = ( ( x + 1 + refIdx ) * intraPredAngle ) & 31

3. 根据偏移值获取预测值

通过iIdx和iFact计算预测值:  

其中cIdx = 0表示亮度,否则表示色度。 在计算预测值的时候,由于一些角度模式会使用到非整数位置处的像素,对于亮度分量,对位于非整数位置处的像素使用三次插值滤波器或者高斯插值滤波器对分数像素位置处的像素进行插值。插值滤波器的选择参考H.266/VVC技术学习:帧内预测之角度预测模式的第三部分的内容。对于色度分量,使用HEVC中的两抽头插值滤波器。

获得预测像素之后,在还需要对以下几种模式进行PDPC操作:

  • 垂直模式(模式50)
  • 水平模式(模式18)
  • 左下角对角线模式和与其相邻的8个模式(模式2~10)
  • 右上角对角线模式和与其相邻的8个模式(模式58~66)。
void IntraPrediction::xPredIntraAng( const CPelBuf &pSrc, PelBuf &pDst, const ChannelType channelType, const ClpRng& clpRng)
{
  int width =int(pDst.width);
  int height=int(pDst.height);

  const bool bIsModeVer     = m_ipaParam.isModeVer;
  const int  multiRefIdx    = m_ipaParam.multiRefIndex;
  const int  intraPredAngle = m_ipaParam.intraPredAngle;
  const int  absInvAngle    = m_ipaParam.absInvAngle;

  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];

  // Initialize the Main and Left reference array.
  // 初始化参考像素
  // 偏移值offset小于0,即模式为19~49
  // 对于垂直类模式(34~49),需要将左侧参考行映射到上方参考行
  // 对于水平类模式(18~33),需要将上方参考行映射到左侧参考行
  if (intraPredAngle < 0)
  {
    for (int x = 0; x <= width + 1 + multiRefIdx; x++)
    {
      refAbove[x + height] = pSrc.at(x, 0);
    }
    for (int y = 0; y <= height + 1 + multiRefIdx; y++)
    {
      refLeft[y + width] = pSrc.at(y, 1);
    }
    // 如果是垂直类模式(34~49),则主要侧为正上方参考行,次要侧为正左侧参考行
    // 如果是水平类模式(19~33),则主要侧为正左侧参考行,次要侧为正上方参考行
    refMain = bIsModeVer ? refAbove + height : refLeft + width;// 主要侧
    refSide = bIsModeVer ? refLeft + width : refAbove + height; //次要测

    // Extend the Main reference to the left. 向左侧扩展次要参考行
    // 将次要侧参考行投影到主要侧参考
    int sizeSide = bIsModeVer ? height : width;
    for (int k = -sizeSide; k <= -1; k++)
    {
      // 右移9位是因为存储在表里的是 (512 * 32) /Angle,需要再给它除以512
      refMain[k] = refSide[std::min((-k * absInvAngle + 256) >> 9, sizeSide)];
    }
  }
  else// 偏移值大于等于0,只需要用到上方参考像素或者左侧参考像素
  {
    for (int x = 0; x <= m_topRefLength + multiRefIdx; x++)
    {
      refAbove[x] = pSrc.at(x, 0);
    }
    for (int y = 0; y <= m_leftRefLength + multiRefIdx; y++)
    {
      refLeft[y] = pSrc.at(y, 1);
    }
    // 垂直类模式,主参考行为上方参考行
    // 水平类模式,主参考行为左侧参考行
    refMain = bIsModeVer ? refAbove : refLeft;
    refSide = bIsModeVer ? refLeft : refAbove;

    // Extend main reference to right using replication
    // 使用复制将主参考行向右扩展或者向下扩展,即填充Segment A和Segment F(MRL)
    const int log2Ratio = floorLog2(width) - floorLog2(height);
    const int s         = std::max<int>(0, bIsModeVer ? log2Ratio : -log2Ratio);
    const int maxIndex  = (multiRefIdx << s) + 2; // 扩展长度
    const int refLength = bIsModeVer ? m_topRefLength : m_leftRefLength;
    const Pel val       = refMain[refLength + multiRefIdx]; //边界处的像素值
    for (int z = 1; z <= maxIndex; z++)
    {
      refMain[refLength + multiRefIdx + z] = val;
    }
  }

  // swap width/height if we are doing a horizontal mode:
  // 如果是水平类模式则交换width和height
  if (!bIsModeVer)
  {
    std::swap(width, height);
  }
  Pel       tempArray[MAX_CU_SIZE * MAX_CU_SIZE];
  const int dstStride = bIsModeVer ? pDst.stride : width;
  Pel *     pDstBuf   = bIsModeVer ? pDst.buf : tempArray;

  // compensate for line offset in reference line buffers
  // 补充多参考行中的偏移
  refMain += multiRefIdx;
  refSide += multiRefIdx;

  Pel *pDsty = pDstBuf;

  if( intraPredAngle == 0 )  // pure vertical or pure horizontal 水平模式和垂直模式(18和50)
  {
    for( int y = 0; y < height; y++ )
    {
      for( int x = 0; x < width; x++ )
      {
        pDsty[x] = refMain[x + 1];//直接投影
      }

      if (m_ipaParam.applyPDPC) // 使用PDPC技术
      {
        const int scale   = (floorLog2(width) + floorLog2(height) - 2) >> 2;
        const Pel topLeft = refMain[0];
        const Pel left    = refSide[1 + y];
        for (int x = 0; x < std::min(3 << scale, width); x++)
        {
          const int wL  = 32 >> (2 * x >> scale);
          const Pel val = pDsty[x];
          pDsty[x]      = ClipPel(val + ((wL * (left - topLeft) + 32) >> 6), clpRng);
        }
      }

      pDsty += dstStride;
    }
  }
  else//对于其余模式
  {
    for (int y = 0, deltaPos = intraPredAngle * (1 + multiRefIdx); y<height; y++, deltaPos += intraPredAngle, pDsty += dstStride)
    {
      const int deltaInt   = deltaPos >> 5; // 整像素位置
      const int deltaFract = deltaPos & 31; // 分像素位置
      //对于除水平、垂直、模式2、34、66之外的模式需进行使用高斯插值滤波器进行插值处理
      if ( !isIntegerSlope( abs(intraPredAngle) ) )
      {
        if( isLuma(channelType) ) // 亮度
        {
          const bool useCubicFilter = !m_ipaParam.interpolationFlag;
          // 四抽头高斯插值滤波器
          const TFilterCoeff        intraSmoothingFilter[4] = {TFilterCoeff(16 - (deltaFract >> 1)), TFilterCoeff(32 - (deltaFract >> 1)), TFilterCoeff(16 + (deltaFract >> 1)), TFilterCoeff(deltaFract >> 1)};
          // 判断是使用三次插值滤波器还是高斯插值滤波器
          const TFilterCoeff* const f                       = (useCubicFilter) ? InterpolationFilter::getChromaFilterTable(deltaFract) : intraSmoothingFilter;

          for (int x = 0; x < width; x++)
          {
            Pel p[4];

            p[0] = refMain[deltaInt + x];
            p[1] = refMain[deltaInt + x + 1];
            p[2] = refMain[deltaInt + x + 2];
            p[3] = refMain[deltaInt + x + 3];

            Pel val = (f[0] * p[0] + f[1] * p[1] + f[2] * p[2] + f[3] * p[3] + 32) >> 6;

            pDsty[x] = ClipPel(val, clpRng);   // always clip even though not always needed
          }
        }
        else // 色度
        {
          // Do linear filtering
          // 线性滤波
          for (int x = 0; x < width; x++)
          {
            Pel p[2];

            p[0] = refMain[deltaInt + x + 1];
            p[1] = refMain[deltaInt + x + 2];

            pDsty[x] = p[0] + ((deltaFract * (p[1] - p[0]) + 16) >> 5);
          }
        }
      }
      else
      {
        // Just copy the integer samples 直接复制整数位置处的参考像素
        for( int x = 0; x < width; x++ )
        {
          pDsty[x] = refMain[x + deltaInt + 1];
        }
      }
      if (m_ipaParam.applyPDPC)//使用PDPC技术
      {
        const int scale       = m_ipaParam.angularScale;
        int       invAngleSum = 256;

        for (int x = 0; x < std::min(3 << scale, width); x++)
        {
          invAngleSum += absInvAngle;

          int wL   = 32 >> (2 * x >> scale);
          Pel left = refSide[y + (invAngleSum >> 9) + 1];
          pDsty[x] = pDsty[x] + ((wL * (left - pDsty[x]) + 32) >> 6);
        }
      }
    }
  }

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

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值