VVC代码 BMS 帧内预测学习之四:参考像素的获取——xFillReferenceSamples()

xFillReferenceSamples()函数是参考像素的获取过程。
主要步骤:
1、分析邻近的像素是否可获取

2、进行参考样本的填充:若临近的像素全部可获取,则赋值;全部不可获取,则赋默认值;若部分可获取,则对可获取的赋对应的值,不可获取的用默认值填充:

  • 首先全部填充为默认值。
  • 填充左上角单元,Idx为totalLeftUnits,通过neighborFlags[totalLeftUnits]判断是否可获取,并赋值。
  • 填充左侧单元,从上到下反向进行,Idx小于totalLeftUnits,同样通过标志位判断并赋值。
  • 填充上方单元,从左到右,Idx大于totalLeftUnits,操作相同。
  • 上述操作后,进行padding操作,即为内容仍为默认值的部分采取用相邻像素填补的操作。找到第一个可以获取的单元,记录像素值,对不可获取的部分进行填充。从左侧的下到上,到上方的左到右进行。

3、复制样本。
注:帧内预测过程中有一个比较混淆的地方,预测过程中会采取unitWidth及unitHeight的方式,将预测像素分为unit进行操作的。(如一个4x32的块,若unitWidth为4,unitHeight为4,则预测过程的Idx等都是按照横向为4/4=1,纵向为32/4=8进行的,左上角的点单元采用unitWidth)

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  tuWidth            = area.width;
  const int  tuHeight           = area.height;
  const int  predSize           = tuWidth + tuHeight;
  const int  predStride         = predSize + 1;

//帧内预测中,是以unit为单位进行的,宽高的unit计算如下
  const bool noShift            = pcv.noChroma2x2 && area.width == 4; // don't shift on the lowest level (chroma not-split)
  const int  unitWidth          = pcv.minCUWidth  >> (noShift ? 0 : getComponentScaleX( area.compID, sps.getChromaFormatIdc() ));
  const int  unitHeight         = pcv.minCUHeight >> (noShift ? 0 : getComponentScaleY( area.compID, sps.getChromaFormatIdc() ));
//totalAboveUnits是上方全部参考单元数量(上方+左侧个数和),即全部参考像素数量对unitWidth做四舍五入处理得到的。totalLeftUnits同理。totalUnits在二者的基础上+1,包括了左上角的unit,即为总的参考unit数量。
  const int  totalAboveUnits    = (predSize + (unitWidth - 1)) / unitWidth;
  const int  totalLeftUnits     = (predSize + (unitHeight - 1)) / unitHeight;
  const int  totalUnits         = totalAboveUnits + totalLeftUnits + 1; //+1 for top-left
//这里将totalAboveUnits分为numAboveUnits以及numAboveRightUnits,左侧的同理。
  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 -----
  //首先分析临近像素是否可获取。
  const Position posLT          = area;
  const Position posRT          = area.topRight();
  const Position posLB          = area.bottomLeft();
//neighborFlags标记着当前unit是否可获取,在isxxxxAvailable()函数中赋值。numIntraNeighbor表示全部的可获取unit数量。
  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;
//观察输入参数:
//isAboveAvailable函数最后一个参数 (neighborFlags + totalLeftUnits + 1)即为其在neighborFlags标志位开始的位置。函数中按照unitWidth循环检测所在的CU是否可获取,可获取且为帧内预测模式,就叫标志位置为true。
//isAboveRightAvailable函数最后一个参数(neighborFlags + totalLeftUnits + 1 + numAboveUnits) 即为其在neighborFlags标志位开始的位置。
//isLeftAvailable函数最后一个参数(neighborFlags + totalLeftUnits - 1) 即为其在neighborFlags标志位结束的位置(在该函数中,标志位减减循环)。          
//isBelowLeftAvailable函数最后一个参数  (neighborFlags + totalLeftUnits - 1 - numLeftUnits)即为其在neighborFlags标志位结束的位置(在该函数中,标志位减减循环)。  
//numIntraNeighbor更新了全部可获取unit的数量。
  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) -----
  //根据临近内容填充参考样本
  CHECK( predStride * predStride > m_iYuvExtSize, "Reference sample area not supported" );

  const Pel*  srcBuf    = recoBuf.buf;//指向当前编码块内的左上第一个像素
  const int   srcStride = recoBuf.stride;
        Pel*  ptrDst    = refBufUnfiltered;
  const Pel*  ptrSrc;
  const Pel   valueDC   = 1 << (sps.getBitDepth( chType ) - 1);//若临近内容不可获取情况下的默认填充值。

//****************如果相邻内容全部不可获取
  if( numIntraNeighbor == 0 )
  {
    // Fill border with DC value
    for( int j = 0; j <= predSize; j++ ) { ptrDst[j]            = valueDC; }
    for( int i = 1; i <= predSize; i++ ) { ptrDst[i*predStride] = valueDC; }
  }
  //****************如果相邻内容全部可获取
  else if( numIntraNeighbor == totalUnits )
  {
    // Fill top-left border and top and top right with rec. samples
    ptrSrc = srcBuf - srcStride - 1;//指向当前块外左上角的参考像素
    for( int j = 0; j <= predSize; j++ ) { ptrDst[j] = ptrSrc[j]; }//左上角开始一直将上方全部循环完毕,循环predSize+1次
    // Fill left and below left border with rec. samples
    ptrSrc = srcBuf - 1;//指向当前块外左边参考像素
    for( int i = 1; i <= predSize; i++ ) { ptrDst[i*predStride] = *(ptrSrc); ptrSrc += srcStride; }//将左侧全部循环完成,循环predSize次
  }
  //****************相邻内容部分可获取
  else // reference samples are partially available
  {
    //****************内容可获取的操作****************
    // BB: old implementation using tmpLineBuf
    // ---------------------------------------
    Pel  tmpLineBuf[5 * MAX_CU_SIZE];//像素级别的参考像素值缓存
    Pel* ptrTmp;
    int  unitIdx;

    // Initialize
    const int totalSamples = (totalLeftUnits * unitHeight) + ((totalAboveUnits + 1) * unitWidth); // all above units have "unitWidth" samples each, all left/below-left units have "unitHeight" samples each
    //初始化,全部采用固定值填充
    for( int k = 0; k < totalSamples; k++ ) { tmpLineBuf[k] = valueDC; }

    // Fill top-left sample,填充左上角样本
    ptrSrc = srcBuf - srcStride - 1;//指向当前块外的左上角
    ptrTmp = tmpLineBuf + (totalLeftUnits * unitHeight);//指向缓存中的左上角填充值
    unitIdx = totalLeftUnits;//Idx为totalLeftUnits,此时代表左上角
    if( neighborFlags[unitIdx] )//如果左上角unit可获取,对其unitWidth个参考样本赋值;若不可用,跳过
    {
      Pel topLeftVal = ptrSrc[0];
      for( int j = 0; j < unitWidth; j++ ) { ptrTmp[j] = topLeftVal; }
    }

    // Fill left & below-left samples (downwards),填充左侧全部样本,从上往下
    ptrSrc += srcStride;//从左上角移动到左侧,即左边列的第一个像素
    ptrTmp--;//缓存指针前移
    unitIdx--;//Idx前移

    for( int k = 0; k < totalLeftUnits; k++ )
    {
      if( neighborFlags[unitIdx] )//判断当前Idx的参考unit是否可获取,可获取则循环unitHeight次进行赋值
      {
        for( int i = 0; i < unitHeight; i++ ) { ptrTmp[-i] = ptrSrc[i*srcStride]; }
      }
      ptrSrc += unitHeight*srcStride;//从上到下的进行
      ptrTmp -= unitHeight;//缓存为了对应ptrSrc的读取,前移,反向进行
      unitIdx--;//索引与缓存一致
    }

    // Fill above & above-right samples (left-to-right) (each unit has "unitWidth" samples),填充上方全部样本,从左至右
    ptrSrc = srcBuf - srcStride;//指向水平方向参考的第一个,也是左上角的unit的右边第一个
    ptrTmp = tmpLineBuf + (totalLeftUnits * unitHeight) + unitWidth; // offset line buffer by totalLeftUnits*unitHeight (for left/below-left) + unitWidth (for above-left)
    //ptrTmp指向ptrSrc像素的临时缓存
    unitIdx = totalLeftUnits + 1;//Idx算完左侧的+1,才是上方的。
    for( int k = 0; k < totalAboveUnits; k++ )
    {
      if( neighborFlags[unitIdx] )//可获取
      {
        for( int j = 0; j < unitWidth; j++ ) { ptrTmp[j] = ptrSrc[j]; }//从默认值更新为可获取的值,通过ptrTmp写入tmpLineBuf中
      }
      ptrSrc += unitWidth;//一个unit完成,指向下一个
      ptrTmp += unitWidth;
      unitIdx++;//Idx正序,加加
    }


  //****************经过上述操作后,对内容为默认值部分采取的措施,填补为相邻可获取样本值****************
    // Pad reference samples when necessary
    int  currUnit       = 0;//从0开始
    Pel* ptrTmpCurrUnit = tmpLineBuf;//指向存储每个像素点值的地址

    if( !neighborFlags[0] )//Idx = 0表示左下列最下方的unit,不可获取,从其上面一个开始循环,所以nextUnit从1开始
    {
      int nextUnit = 1;
      //找到第一个可以获取的点,nextUnit即为其Idx
      while( nextUnit < totalUnits && !neighborFlags[nextUnit] )
      {
        nextUnit++;
      }
      //记录可获取的点的值(需要判断是左侧还是上方),nextUnit < totalLeftUnits是在左侧, = totalLeftUnits是左上角,否则在上方
      Pel* ptrTmpRef = tmpLineBuf + ((nextUnit < totalLeftUnits) ? (nextUnit * unitHeight) : ((totalLeftUnits * (unitHeight - unitWidth)) + (nextUnit * unitWidth)));
      const Pel refSample = *ptrTmpRef;//获取像素值
      
      // Pad unavailable samples with new value
      // fill left column
      //可获取的nextUnit可能是上方的,此时大于totalLeftUnits,currUnit取totalLeftUnits,说明左侧全部不可获取,全部赋值为refSample;
      //可获取的nextUnit若在左侧,即小于totalLeftUnits,小于totalLeftUnits的单元即为不可获取的内容,赋值为refSample
      while( currUnit < std::min<int>( nextUnit, totalLeftUnits ) )
      {
      //当前单元中每个像素均赋值为可获取的unit的第一个像素的值
        for( int i = 0; i < unitHeight; i++ ) { ptrTmpCurrUnit[i] = refSample; }
        ptrTmpCurrUnit += unitHeight;
        currUnit++;
      }
      // fill top row
      //currUnit在上面的循环中已经= totalLeftUnits,若<nextUnit,说明可获取的Idx在上方。
      while( currUnit < nextUnit )
      {
        for( int j = 0; j < unitWidth; j++ ) { ptrTmpCurrUnit[j] = refSample; }
        ptrTmpCurrUnit += unitWidth;
        currUnit++;
      }
    }

    // pad all other reference samples.
    while( currUnit < totalUnits )
    {
      const int numSamplesInCurrUnit = (currUnit >= totalLeftUnits) ? unitWidth : unitHeight;
      if( !neighborFlags[currUnit] ) // samples not available
      {
        const Pel refSample = *(ptrTmpCurrUnit - 1);//不可获取的unit前一个可获取的单元,numSamplesInCurrUnit 个像素中最后一个像素的值。因为ptrTmpCurrUnit已经指向了当前不可获取的像素。
        for( int k = 0; k < numSamplesInCurrUnit; k++ ) { ptrTmpCurrUnit[k] = refSample; }

      }
      ptrTmpCurrUnit += numSamplesInCurrUnit;
      currUnit++;
    }

    // Copy processed samples,输出tmpLineBuf 中缓存的参考像素值
    ptrTmp = tmpLineBuf + (totalLeftUnits * unitHeight) + (unitWidth - 1);
    for( int j = 0; j <= predSize; j++ ) { ptrDst[j] = ptrTmp[j]; } // top left, top and top right samples

    ptrTmp = tmpLineBuf + (totalLeftUnits * unitHeight);
    for( int i = 1; i <= predSize; i++ ) { ptrDst[i*predStride] = ptrTmp[-i]; }
  }
}

猜你喜欢

转载自blog.csdn.net/yolo_life/article/details/82789470
今日推荐