H.266/VVC代码学习:帧内预测之参考像素获取及滤波

帧内预测首先需要获取参考像素并对参考像素进行滤波。

VTM7.0中,initIntraPatternChType函数是获取参考像素和对参考像素滤波的入口函数,主要包含三步:

  1. 获取参考像素长度
  2. 获取参考像素
  3. 对参考像素进行滤波

代码如下:

//forceRefFilterFlag表示对参考像素强制滤波标志
void IntraPrediction::initIntraPatternChType(const CodingUnit &cu, const CompArea &area, const bool forceRefFilterFlag)
{
#if JVET_P0641_REMOVE_2xN_CHROMA_INTRA
  CHECK(area.width == 2, "Width of 2 is not supported");
#endif
  const CodingStructure& cs   = *cu.cs;

  if (!forceRefFilterFlag)
  {
    initPredIntraParams(*cu.firstPU, area, *cs.sps);//初始化帧内预测参数
  }

  Pel *refBufUnfiltered = m_refBuffer[area.compID][PRED_BUF_UNFILTERED];//未滤波的参考像素
  Pel *refBufFiltered   = m_refBuffer[area.compID][PRED_BUF_FILTERED];//滤波后的参考像素
  // ----- Step 0:获取参考像素的长度 -----
  // m_leftRefLength = 2H
  // m_topRefLength = 2W
  setReferenceArrayLengths( area );

  // ----- Step 1: unfiltered reference samples -----
  // ----- Step 1:获取参考像素 -----
  xFillReferenceSamples( cs.picture->getRecoBuf( area ), refBufUnfiltered, area, cu );
  // ----- Step 2: filtered reference samples -----
  // ----- Step 2:参考像素的滤波 -----
  if( m_ipaParam.refFilterFlag || forceRefFilterFlag )
  {
    xFilterReferenceSamples( refBufUnfiltered, refBufFiltered, area, *cs.sps, cu.firstPU->multiRefIdx );
  }
}

其中,setReferenceArrayLengths函数主要是用来设置上侧和左侧参考像素的长度

代码如下:

void IntraPrediction::setReferenceArrayLengths( const CompArea &area )
{
  // set Top and Left reference samples length
  // 设置上侧和左侧参考像素的长度
  const int  width    = area.width;
  const int  height   = area.height;

  m_leftRefLength     = (height << 1);//左侧长度为高的2倍
  m_topRefLength      = (width << 1);//上侧参考像素为宽的2倍

}

xFillReferenceSamples函数主要是用来获取参考像素,主要包含两个步骤:

  1. 分析当前预测块边界,判断当前预测块上方、右上、左侧、左下重建像素可用的数目
  2. 使用重建像素填充参考像素

这里填充参考像素时,有以下三种情况:

  1. 重建像素全部不可用,则参考像素全部填充1<<(bitDepth-1)
  2. 重建像素全部可用,则直接使用重建像素填充参考像素
  3. 重建像素部分可用部分不可用时,则先查看最左下角的重建像素是否可用,有以下两个规则
    1. 如果可用,则从下往上遍历,不可用的重建像素值用其下方最相邻的像素值填充,到达左上角后,从左到右遍历,若有某点的重建像素值不可用,则用其左边最相邻的像素填充;
    2. 如果不可用,则先从下往上,从左往右遍历一次直到找到第一个可用的重建像素值,将该重建值填充到最左下角的位置,然后将其之前遍历到的不可用的重建像素都使用该重建值填充,接着按照规则1填充
//填充参考像素
//recoBuf重建的像素,refBufUnfiltered未滤波的参考像素
void IntraPrediction::xFillReferenceSamples( const CPelBuf &recoBuf, Pel* refBufUnfiltered, const CompArea &area, const CodingUnit &cu )
{
  const ChannelType      chType = toChannelType( area.compID );
  const CodingStructure &cs     = *cu.cs;
  const SPS             &sps    = *cs.sps;
  const PreCalcValues   &pcv    = *cs.pcv;

  const int multiRefIdx         = (area.compID == COMPONENT_Y) ? cu.firstPU->multiRefIdx : 0;//参考行索引

  const int  tuWidth            = area.width;
  const int  tuHeight           = area.height;
  const int  predSize           = m_topRefLength;//上侧的参考行长度2W
  const int  predHSize          = m_leftRefLength;//左侧的参考行长度2H
  const int predStride = predSize + 1 + multiRefIdx;//2W + 1 + multiRefIdx
  m_refBufferStride[area.compID] = predStride;
  //不要在最底层移动(色度不分离)
  const bool noShift            = pcv.noChroma2x2 && area.width == 4; // don't shift on the lowest level (chroma not-split)
  //宽度最小单位长度
  const int  unitWidth          = tuWidth  <= 2 && cu.ispMode && isLuma(area.compID) ? tuWidth  : pcv.minCUWidth  >> (noShift ? 0 : getComponentScaleX(area.compID, sps.getChromaFormatIdc()));
  //色度最小单位长度
  const int  unitHeight         = tuHeight <= 2 && cu.ispMode && isLuma(area.compID) ? tuHeight : pcv.minCUHeight >> (noShift ? 0 : getComponentScaleY(area.compID, sps.getChromaFormatIdc()));

  const int  totalAboveUnits    = (predSize + (unitWidth - 1)) / unitWidth;//整个上参考行有几个块
  const int  totalLeftUnits     = (predHSize + (unitHeight - 1)) / unitHeight;//整个左参考列有几个块
  const int  totalUnits         = totalAboveUnits + totalLeftUnits + 1; //+1 for top-left 整个参考像素的数目
  const int  numAboveUnits      = std::max( tuWidth / unitWidth, 1 );//上参考行有几个块
  const int  numLeftUnits       = std::max( tuHeight / unitHeight, 1 );//左参考列有几个块
  const int  numAboveRightUnits = totalAboveUnits - numAboveUnits;//右上参考行有几个块
  const int  numLeftBelowUnits  = totalLeftUnits - numLeftUnits;//左下参考列有几个块

  CHECK( numAboveUnits <= 0 || numLeftUnits <= 0 || numAboveRightUnits <= 0 || numLeftBelowUnits <= 0, "Size not supported" );

  // ----- Step 1: analyze neighborhood -----
  // ----- Step 1:分析边界 -----
  const Position posLT          = area;//预测块内部的左上像素
  const Position posRT          = area.topRight();//预测块右部的右上像素
  const Position posLB          = area.bottomLeft();//预测块内部的左下参考像素

  bool  neighborFlags[4 * MAX_NUM_PART_IDXS_IN_CTU_WIDTH + 1];
  int   numIntraNeighbor = 0;//可用参考像素的个数

  memset( neighborFlags, 0, totalUnits );

  neighborFlags[totalLeftUnits] = isAboveLeftAvailable( cu, chType, posLT );//判断左上角参考像素是否可用
  numIntraNeighbor += neighborFlags[totalLeftUnits] ? 1 : 0;
  //判断上方、右上、左侧、左下参考像素可用的数目
  numIntraNeighbor += isAboveAvailable     ( cu, chType, posLT, numAboveUnits,      unitWidth,  (neighborFlags + totalLeftUnits + 1) );
  numIntraNeighbor += isAboveRightAvailable( cu, chType, posRT, numAboveRightUnits, unitWidth,  (neighborFlags + totalLeftUnits + 1 + numAboveUnits) );
  numIntraNeighbor += isLeftAvailable      ( cu, chType, posLT, numLeftUnits,       unitHeight, (neighborFlags + totalLeftUnits - 1) );
  numIntraNeighbor += isBelowLeftAvailable ( cu, chType, posLB, numLeftBelowUnits,  unitHeight, (neighborFlags + totalLeftUnits - 1 - numLeftUnits) );

  // ----- Step 2: fill reference samples (depending on neighborhood) -----
  // ----- Step 2:填充参考像素 -----
  const Pel*  srcBuf    = recoBuf.buf;//重建像素
  const int   srcStride = recoBuf.stride;
        Pel*  ptrDst    = refBufUnfiltered;//未滤波的参考像素
  const Pel*  ptrSrc;
  const Pel   valueDC   = 1 << (sps.getBitDepth( chType ) - 1);// 1<<(bitDepth-1)


  if( numIntraNeighbor == 0 )//如果全部参考像素都不可用时,参考像素全部填充1<<(bitDepth-1)
  {
    // Fill border with DC value
    for (int j = 0; j <= predSize + multiRefIdx; j++) { ptrDst[j] = valueDC; }
    for (int i = 0; i <= predHSize + multiRefIdx; i++)
    {
      ptrDst[i + predStride] = valueDC;
    }
  }
  else if( numIntraNeighbor == totalUnits )//参考像素全部可用
  {
    //! 0; unitIdx--)
    {
      if (neighborFlags[unitIdx])
      {
        for (int i = 0; i < unitHeight; i++)
        {
          ptrDst[i] = ptrSrc[i * srcStride];
        }
      }
      ptrSrc += unitHeight * srcStride;
      ptrDst += unitHeight;
    }
    // Fill last below-left sample(s)
    // 填充最后的左下像素
    if (neighborFlags[0])
    {
      int lastSample = (predHSize % unitHeight == 0) ? unitHeight : predHSize % unitHeight;
      for (int i = 0; i < lastSample; i++)
      {
        ptrDst[i] = ptrSrc[i * srcStride];
      }
    }
    //!<填充Segment E
    // Fill above & above-right samples if available (left-to-right)
    ptrSrc = srcBuf - srcStride * (1 + multiRefIdx);
    ptrDst = refBufUnfiltered + 1 + multiRefIdx;
    for (int unitIdx = totalLeftUnits + 1; unitIdx < totalUnits - 1; unitIdx++)
    {
      if (neighborFlags[unitIdx])
      {
        for (int j = 0; j < unitWidth; j++)
        {
          ptrDst[j] = ptrSrc[j];
        }
      }
      ptrSrc += unitWidth;
      ptrDst += unitWidth;
    }
    // Fill last above-right sample(s)
    // 填充最后的右上角像素
    if (neighborFlags[totalUnits - 1])
    {
      int lastSample = (predSize % unitWidth == 0) ? unitWidth : predSize % unitWidth;
      for (int j = 0; j < lastSample; j++)
      {
        ptrDst[j] = ptrSrc[j];
      }
    }

    // pad from first available down to the last below-left 
    // 如果左下角像素不可用,从左下角往上寻找可用的参考像素
    ptrDst = refBufUnfiltered;
    int lastAvailUnit = 0;
    if (!neighborFlags[0])
    {
      int firstAvailUnit = 1;
      while (firstAvailUnit < totalUnits && !neighborFlags[firstAvailUnit])
      {
        firstAvailUnit++;
      }

      // first available sample
      // 第一个可用的像素的位置
      int firstAvailRow = -1;
      int firstAvailCol = 0;
      if (firstAvailUnit < totalLeftUnits)
      {
        firstAvailRow = (totalLeftUnits - firstAvailUnit) * unitHeight + multiRefIdx;
      }
      else if (firstAvailUnit == totalLeftUnits)
      {
        firstAvailRow = multiRefIdx;
      }
      else
      {
        firstAvailCol = (firstAvailUnit - totalLeftUnits - 1) * unitWidth + 1 + multiRefIdx;
      }
      //根据位置获取到第一个可用参考像素值
      const Pel firstAvailSample = ptrDst[firstAvailRow < 0 ? firstAvailCol : firstAvailRow + predStride];

      // last sample below-left (n.a.)
      // 左下最后一个参考像素
      int lastRow = predHSize + multiRefIdx;

      // fill left column
      // 将第一个可用像素下的像素都用该值填充
      for (int i = lastRow; i > firstAvailRow; i--)
      {
        ptrDst[i + predStride] = firstAvailSample;
      }
      // fill top row
      // 填充上一行参考像素
      if (firstAvailCol > 0)
      {
        for (int j = 0; j < firstAvailCol; j++)
        {
          ptrDst[j] = firstAvailSample;
        }
      }
      lastAvailUnit = firstAvailUnit;
    }

    // pad all other reference samples.
    // 填充剩余的参考像素
    int currUnit = lastAvailUnit + 1;
    while (currUnit < totalUnits)
    {
      if (!neighborFlags[currUnit]) // samples not available 像素不可用
      {
        // last available sample
        // 寻找最近的可用参考像素
        int lastAvailRow = -1;
        int lastAvailCol = 0;
        if (lastAvailUnit < totalLeftUnits)
        {
          lastAvailRow = (totalLeftUnits - lastAvailUnit - 1) * unitHeight + multiRefIdx + 1;
        }
        else if (lastAvailUnit == totalLeftUnits)
        {
          lastAvailCol = multiRefIdx;
        }
        else
        {
          lastAvailCol = (lastAvailUnit - totalLeftUnits) * unitWidth + multiRefIdx;
        }
        const Pel lastAvailSample = ptrDst[lastAvailRow < 0 ? lastAvailCol : lastAvailRow + predStride];

        // fill current unit with last available sample
        // 用租金的参考像素填充
        if (currUnit < totalLeftUnits)
        {
          for (int i = lastAvailRow - 1; i >= lastAvailRow - unitHeight; i--)
          {
            ptrDst[i + predStride] = lastAvailSample;
          }
        }
        else if (currUnit == totalLeftUnits)
        {
          for (int i = 0; i < multiRefIdx + 1; i++)
          {
            ptrDst[i + predStride] = lastAvailSample;
          }
          for (int j = 0; j < multiRefIdx + 1; j++)
          {
            ptrDst[j] = lastAvailSample;
          }
        }
        else
        {
          int numSamplesInUnit = (currUnit == totalUnits - 1) ? ((predSize % unitWidth == 0) ? unitWidth : predSize % unitWidth) : unitWidth;
          for (int j = lastAvailCol + 1; j <= lastAvailCol + numSamplesInUnit; j++)
          {
            ptrDst[j] = lastAvailSample;
          }
        }
      }
      lastAvailUnit = currUnit;
      currUnit++;
    }
  }
}

 xFilterReferenceSamples函数主要是使用[1,2,1]滤波器对参考像素进行滤波。

// 参考像素的滤波
// 使用[1 2 1]滤波器
void IntraPrediction::xFilterReferenceSamples(const Pel *refBufUnfiltered, Pel *refBufFiltered, const CompArea &area,
                                              const SPS &sps, int multiRefIdx
)
{
  /************************ 初始化 **************************/
  if (area.compID != COMPONENT_Y)//色度只用一行参考行
  {
    multiRefIdx = 0;
  }
  const int predSize = m_topRefLength + multiRefIdx;//2W + multiRefIdx
  const int predHSize = m_leftRefLength + multiRefIdx;//2H + multiRefIdx
  const size_t predStride = m_refBufferStride[area.compID];//2W + 1 + multiRefIdx

  const Pel topLeft =
    (refBufUnfiltered[0] + refBufUnfiltered[1] + refBufUnfiltered[predStride] + refBufUnfiltered[predStride + 1] + 2)
    >> 2;
  //左上角滤波后像素
  refBufFiltered[0] = topLeft;
  //上一行参考像素滤波
  for (int i = 1; i < predSize; i++)
  {
    refBufFiltered[i] = (refBufUnfiltered[i - 1] + 2 * refBufUnfiltered[i] + refBufUnfiltered[i + 1] + 2) >> 2;
  }
  //最右上角像素
  refBufFiltered[predSize] = refBufUnfiltered[predSize];

  refBufFiltered += predStride;
  refBufUnfiltered += predStride;

  refBufFiltered[0] = topLeft;

  for (int i = 1; i < predHSize; i++)
  {
    refBufFiltered[i] = (refBufUnfiltered[i - 1] + 2 * refBufUnfiltered[i] + refBufUnfiltered[i + 1] + 2) >> 2;
  }
  //最左下角像素
  refBufFiltered[predHSize] = refBufUnfiltered[predHSize];
}

 

你可能感兴趣的:(H.266/VVC)