H.266/VVC-VTM代码学习-帧内预测02-获取参考像素并对其滤波xFillReferenceSamples、xFilterReferenceSamples

H.266/VVC专栏传送

上一篇:H.266/VVC-VTM代码学习-帧内预测01-初始化帧内预测参数IntraPrediction::initPredIntraParams

下一篇:H.266/VVC-VTM代码学习-帧内预测03-DC模式下计算预测像素值xPredIntraDc、xGetPredValDc

前言

VTM是H.266/VVC视频编码标准的参考软件,研究VTM代码给研究人员解释了VVC编码标准的详细标准规范与细节。

本文是笔者对VTM代码的一点学习记录,成文于笔者刚开始接触VVC期间,期间很多概念和理论框架还很不成熟,若文中存在错误欢迎批评指正,也欢迎广大视频编码学习者沟通交流、共同进步。

VTM代码的下载及编译请参考博文:
【视频编码学习】H.266/VVC参考软件VTM配置运行(VTM-6.0版本)
本文涉及的代码存在于工程下的/lib/CommonLib/SourceFiles/IntraPrediction.cpp文件中。

一、主要函数

1.函数代码

(1)入口函数 IntraPrediction::initIntraPatternChType

//获取参考像素并对参考像素滤波的入口函数
void IntraPrediction::initIntraPatternChType(const CodingUnit &cu, const CompArea &area, const bool forceRefFilterFlag)
{
  CHECK(area.width == 2, "Width of 2 is not supported");
  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];
  //-------- step0: 获取参考像素的长度 ---------
  //m_leftRefLength = 2H
  //m_topRefLength = 2W
  setReferenceArrayLengths( area );

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

(2)获取参考像素函数IntraPrediction::xFillReferenceSamples

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;
  //左侧参考像素长度  
  const int  predHSize          = m_leftRefLength; 
  //当前参考行长度
  const int predStride = predSize + 1 + multiRefIdx;
  m_refBufferStride[area.compID] = predStride;

  // don't shift on the lowest level (chroma not-split)
  const bool noShift            = pcv.noChroma2x2 && area.width == 4;
  // 单元宽 
  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<int>( tuWidth / unitWidth, 1 );
  //正左侧参考像素单元个数
  const int  numLeftUnits       = std::max<int>( 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 );
  //若左上角有效,则有效参考像素+1
  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: 填充参考像素 -----
  //图像像素原始buffer
  const Pel*  srcBuf    = recoBuf.buf;
  //图像原始宽度
  const int   srcStride = recoBuf.stride;
  //未滤波的参考像素
        Pel*  ptrDst    = refBufUnfiltered; 
  const Pel*  ptrSrc;
  //设置valueDC为像素最大值的一半
  const Pel   valueDC   = 1 << (sps.getBitDepth( chType ) - 1);

  //参考像素都不可用,则填充valueDC
  if( numIntraNeighbor == 0 )               
  {
    //参考像素行填充valueDC
    for (int j = 0; j <= predSize + multiRefIdx; j++)
    {
      ptrDst[j] = valueDC;
    }
    //参考像素列填充valueDC
    for (int i = 0; i <= predHSize + multiRefIdx; i++)
    {
      ptrDst[i + predStride] = valueDC;
    }
  }
  //所有参考像素都可用时,直接复制
  else if( numIntraNeighbor == totalUnits )  
  {
    //先将srcBuf后退(1 + multiRefIdx) * srcStride + (1 + multiRefIdx)个单位
    //srcBuf指向当前块的左上位置,该操作后ptrSrc指向其上侧参考行的第一个像素位置
    ptrSrc = srcBuf - (1 + multiRefIdx) * srcStride - (1 + multiRefIdx);
    //将当前块上侧参考像素赋值给ptrDst[i]
    for (int j = 0; j <= predSize + multiRefIdx; j++)
    {
      ptrDst[j] = ptrSrc[j];
    }
    //将当前块左侧参考像素赋值给ptrDst[i+predStride]
    for (int i = 0; i <= predHSize + multiRefIdx; i++)
    {
      ptrDst[i + predStride] = ptrSrc[i * srcStride];
    }
  }
  //reference samples are partially available 参考像素部分不可用
  else 
  {
    //将指针移动到segmentD的第一个
    ptrSrc = srcBuf - (1 + multiRefIdx) * srcStride - (1 + multiRefIdx);
    ptrDst = refBufUnfiltered;
    //如果左上方参考像素单元可用
    if (neighborFlags[totalLeftUnits])
    {
      //填充左上角参考点
      ptrDst[0] = ptrSrc[0];
      ptrDst[predStride] = ptrSrc[0];
      //填充segmentC和segmentD除左上角以外的参考点
      for (int i = 1; i <= multiRefIdx; i++)
      {
        //填充SegmentD中除左上角点之外的参考点
        ptrDst[i] = ptrSrc[i];
        //填充SegmentC中除左上角点之外的参考点
        ptrDst[i + predStride] = ptrSrc[i * srcStride];
      }
    }

    // Fill left & below-left samples if available (downwards) 填充左和左下方参考点
    //将指针移动到SegmentB第一个参考点
    ptrSrc += (1 + multiRefIdx) * srcStride;
    ptrDst += (1 + multiRefIdx) + predStride;
    //从上往下遍历左侧参考像素单元
    for (int unitIdx = totalLeftUnits - 1; unitIdx > 0; unitIdx--)
    {
      //若当前参考像素单元可用
      if (neighborFlags[unitIdx])
      {
        //遍历将该参考像素单元复制到prtDst中
        for (int i = 0; i < unitHeight; i++)
        {
          ptrDst[i] = ptrSrc[i * srcStride];
        }
      }
      //将ptrSrc指向下一个参考像素单元起始位置
      ptrSrc += unitHeight * srcStride;
      //将ptrDst指向下一个参考像素单元起始存放位置
      ptrDst += unitHeight;
    }
    // Fill last below-left sample(s) 填充最后的左下方参考点
    //若左下方参考像素可用
    if (neighborFlags[0])
    {
      //左下方参考像素单元高度为unitHeight或unitHeight
      int lastSample = (predHSize % unitHeight == 0) ? unitHeight : predHSize % unitHeight;
      //遍历赋值左下方参考像素单元
      for (int i = 0; i < lastSample; i++)
      {
        ptrDst[i] = ptrSrc[i * srcStride];
      }
    }

    // Fill above & above-right samples if available (left-to-right) 填充上方和右上方参考点
    //将指针移动到segmentE的第一个参考点
    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++;
    }
  }
}

(3)对参考像素进行滤波的函数IntraPrediction::xFilterReferenceSamples

void IntraPrediction::xFilterReferenceSamples(const Pel *refBufUnfiltered, Pel *refBufFiltered, const CompArea &area,
                                              const SPS &sps, int multiRefIdx)
{
  if (area.compID != COMPONENT_Y)// 色度用0号参考行
  {
    multiRefIdx = 0;
  }
  //上方参考行长度
  const int predSize = m_topRefLength + multiRefIdx;
  //左侧参考列长度
  const int predHSize = m_leftRefLength + multiRefIdx;
  //(2w + 1 + multiRefIdx)参考行长度
  const size_t predStride = m_refBufferStride[area.compID];
  
  //左上角滤波后像素(当前位置*2 + 下像素 + 右像素) / 4
  const Pel topLeft =
    (refBufUnfiltered[0] + refBufUnfiltered[1] + refBufUnfiltered[predStride] + refBufUnfiltered[predStride + 1] + 2)
    >> 2;
   
  refBufFiltered[0] = topLeft;

  //对上侧一整行参考像素进行滤波
  for (int i = 1; i < predSize; i++)
  {
    //[0.25, 0.5, 0.25]滤波
    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];
}

2.逻辑结构

(1)获取参考像素函数

1.分析边界:确定当前块参考像素长度和参考像素块尺寸、参考像素块数量等参数。求得当前块各个方向的可用参考像素块数量以及总数,并记录各块是否可用。

2.复制填充所有可用参考像素。

3.填充参考像素:
① 若参考像素都不可用,则所有参考像素都以像素最大值的一半填充。
② 若参考像素都可用,则直接复制填充参考像素。
③ 若参考像素部分可用且左下方参考像素可用,则从左下方参考像素开始向上向右填充最邻近的可用像素。
④ 若参考像素部分可用且左下方参考像素不可用,则从左下方参考像素开始向上向右找到第一个可用参考像素,并以其值填充之前的像素。再遍历之后的像素,填充最邻近的可用像素。

(2)对参考像素进行滤波函数

1.对左上角参考像素单独滤波。

2.对上侧参考像素进行滤波,右上角参考像素不进行滤波。

3.对左侧参考像素进行滤波,左下角参考像素不进行滤波。

二、MRL(多参考行)

需要注意的是,MRL 中的参考行实际上不是相邻的关系,index=1 和 index=2 的参考行之间间隔了一行。


上一篇:H.266/VVC-VTM代码学习-帧内预测01-初始化帧内预测参数IntraPrediction::initPredIntraParams

下一篇:H.266/VVC-VTM代码学习-帧内预测03-DC模式下计算预测像素值xPredIntraDc、xGetPredValDc

posted @ 2020-11-02 23:52  liaojq2020  阅读(201)  评论(0)    收藏  举报