H.265之三 帧内预测(1)

来源:互联网 发布:malware defender知乎 编辑:程序博客网 时间:2024/04/25 19:55

    今天开始进入实质性内容的讨论,主要是从代码实现的角度比较深入地研究帧内预测算法。由于帧内预测涉及到的函数的数量相对于编解码器复杂部分来说少,但事实上大大小小也牵涉到了十几二十个函数(没具体统计过,只是大概估算了下),想要一下子讨论完比较困难,所以打算在接下来的若干篇文章里逐步地尽可能详尽地分析每一个较为重要的函数。今天所要讨论的是fillReferenceSamples这个函数,它主要功能是在真正进行帧内预测之前,使用重建后的Yuv图像对当前PU的相邻样点进行赋值,为接下来进行的角度预测提供参考样点值。

这个函数实际上实现的是官方当前标准(JCTVC-J1003)draft 8.4.4.2.2(Reference sample substitution process for intra sample prediction),具体内容我这里就不重复了,有兴趣的朋友可以自己下下来去看看,我先简单把该过程复述一遍:(1)如果所有相邻点均不可用,则参考样点值均被赋值为DC值;(2)如果所有相邻点均可用,则参考样点值都会被赋值为重建Yuv图像中与其位置相同的样点值;(3)如果不满足上述两个条件,则按照从左下往左上,从左上往右上的扫描顺序进行遍历,(如下图所示),如果第一个点不可用,则使用下一个可用点对应的重建Yuv样点值对其进行赋值;对于除第一个点外的其它邻点,如果该点不可用,则使用它的前一个样点值进行赋值(前一个步骤保证了前一个样点值一定是存在的),直到遍历完毕.


      在了解了算法的大致流程后,我们就可以看看代码是怎么具体实现的了。我主要把我对代码的注释连同代码一起贴出来,以供大家参考,有些地方理解上肯定还是有所欠缺的,望大家不吝赐教。

Void TComPattern::fillReferenceSamples( TComDataCU* pcCU, Pel* piRoiOrigin, Int* piAdiTemp, Bool* bNeighborFlags, Int iNumIntraNeighbor, Int iUnitSize, Int iNumUnitsInCu, Int iTotalUnits, UInt uiCuWidth, UInt uiCuHeight, UInt uiWidth, UInt uiHeight, Int iPicStride, Bool bLMmode ){  Pel* piRoiTemp; //!< piRoiOrgin指向重建Yuv图像对应于当前PU所在位置的首地址,piRoiTemp用于指向所感兴趣的重建Yuv的位置,piAdiTemp  Int  i, j;  Int  iDCValue = ( 1<<( g_uiBitDepth + g_uiBitIncrement - 1) );  if (iNumIntraNeighbor == 0) // all samples are not available  {    // Fill border with DC value    for (i=0; i<uiWidth; i++)  //!< AboveLeft + Above + AboveRight    {      piAdiTemp[i] = iDCValue;    }    for (i=1; i<uiHeight; i++)  //!< Left + BelowLeft    {      piAdiTemp[i*uiWidth] = iDCValue;    }  }  else if (iNumIntraNeighbor == iTotalUnits) // all samples are available  {    // Fill top-left border with rec. samples    piRoiTemp = piRoiOrigin - iPicStride - 1;  //!< 左上    piAdiTemp[0] = piRoiTemp[0];    // Fill left border with rec. samples    piRoiTemp = piRoiOrigin - 1;  //!< 左    if (bLMmode) //!< bLMmode 默认值为false    {      piRoiTemp --; // move to the second left column    }    for (i=0; i<uiCuHeight; i++)    {      piAdiTemp[(1+i)*uiWidth] = piRoiTemp[0]; //!< 每个参考样点赋值为对应位置重建Yuv样点值      piRoiTemp += iPicStride; //!< 指向重建Yuv下一行    }    // Fill below left border with rec. samples    for (i=0; i<uiCuHeight; i++)  //!< 左下    {      piAdiTemp[(1+uiCuHeight+i)*uiWidth] = piRoiTemp[0]; //!< 每个参考样点赋值为对应位置重建Yuv样点值      piRoiTemp += iPicStride;    }    // Fill top border with rec. samples    piRoiTemp = piRoiOrigin - iPicStride; //!< 重新指向重建Yuv的上方    for (i=0; i<uiCuWidth; i++)    {      piAdiTemp[1+i] = piRoiTemp[i]; //!< 每个参考样点赋值为对应位置重建Yuv样点值    }        // Fill top right border with rec. samples    piRoiTemp = piRoiOrigin - iPicStride + uiCuWidth; //!< 指向右上    for (i=0; i<uiCuWidth; i++)    {      piAdiTemp[1+uiCuWidth+i] = piRoiTemp[i]; //!< 每个参考样点赋值为对应位置重建Yuv样点值    }  }  else // reference samples are partially available  {    Int  iNumUnits2 = iNumUnitsInCu<<1;    Int  iTotalSamples = iTotalUnits*iUnitSize;  //!< neighboring samples的总数,iTotalUnits以4x4块为单位,iUnitSize为块的大小    Pel  piAdiLine[5 * MAX_CU_SIZE];    Pel  *piAdiLineTemp; //!<临时存储用于填充neighboring samples的样点值    Bool *pbNeighborFlags;    Int  iNext, iCurr;    Pel  piRef = 0; //!< 存储临时样点值    // Initialize    for (i=0; i<iTotalSamples; i++)  //!< 先将所有样点值赋值为DC值    {      piAdiLine[i] = iDCValue;    }        // Fill top-left sample    piRoiTemp = piRoiOrigin - iPicStride - 1;  //!< 指向重建Yuv左上角    piAdiLineTemp = piAdiLine + (iNumUnits2*iUnitSize); //!< piAdiLine的扫描顺序为左下到左上,再从左到右上    pbNeighborFlags = bNeighborFlags + iNumUnits2; //!< 标记neighbor可用性的数组同样移动至左上角    if (*pbNeighborFlags) //!< 如果左上角可用,则左上角4个像素点均赋值为重建Yuv左上角的样点值    {      piAdiLineTemp[0] = piRoiTemp[0];      for (i=1; i<iUnitSize; i++)      {        piAdiLineTemp[i] = piAdiLineTemp[0];      }    }    // Fill left & below-left samples    piRoiTemp += iPicStride; //!< piRoiTemp指向重建Yuv的左边界    if (bLMmode)    {      piRoiTemp --; // move the second left column    }    piAdiLineTemp--;  //!< 移动指针置左边界    pbNeighborFlags--; //!< 移动指针置左边界    for (j=0; j<iNumUnits2; j++) //!< 从左往左下扫描    {      if (*pbNeighborFlags)  //!< 如果可用      {        for (i=0; i<iUnitSize; i++) //!< 每个4x4块里的4个样点分别被赋值为对应位置的重建Yuv的样点值        {          piAdiLineTemp[-i] = piRoiTemp[i*iPicStride];        }      }      piRoiTemp += iUnitSize*iPicStride; //!< 指针挪到下一个行(以4x4块为单位,即实际上下移了4行)      piAdiLineTemp -= iUnitSize; //!< 指针下移      pbNeighborFlags--; //!< 指针下移    }    // Fill above & above-right samples    piRoiTemp = piRoiOrigin - iPicStride; //!< piRoiTemp 指向重建Yuv的上边界    piAdiLineTemp = piAdiLine + ((iNumUnits2+1)*iUnitSize); //!< 指向上边界    pbNeighborFlags = bNeighborFlags + iNumUnits2 + 1; //!< 指向上边界    for (j=0; j<iNumUnits2; j++) //!< 从左扫描至右上    {      if (*pbNeighborFlags)      {        for (i=0; i<iUnitSize; i++)        {          piAdiLineTemp[i] = piRoiTemp[i]; //!< 每个4x4块里的4个样点分别被赋值为对应位置的重建Yuv的样点值        }      }      piRoiTemp += iUnitSize; //!< 指针右移      piAdiLineTemp += iUnitSize; //!< 指针右移      pbNeighborFlags++; //!< 指针右移    }    // Pad reference samples when necessary    iCurr = 0;    iNext = 1;    piAdiLineTemp = piAdiLine; //!< 指向左下角(纵坐标最大的那个位置,即扫描起点)    while (iCurr < iTotalUnits) //!< 遍历所有neighboring samples    {      if (!bNeighborFlags[iCurr]) //!< 该点不可用      {        if(iCurr == 0) //!< 第一个点就不可用        {          while (iNext < iTotalUnits && !bNeighborFlags[iNext]) //!< 找到第1个可用点          {            iNext++;          }          piRef = piAdiLine[iNext*iUnitSize]; //!< 保存该可用点的样点值          // Pad unavailable samples with new value          while (iCurr < iNext) //!< 使用保存下来的第一个可用点的样点值赋值给在其之前被标记为不可用的点          {            for (i=0; i<iUnitSize; i++)            {              piAdiLineTemp[i] = piRef;            }            piAdiLineTemp += iUnitSize;            iCurr++;          }        }        else //!< 当前点不可用且其不是第一个点,则使用该点的前一个可用点的样点值进行赋值        {          piRef = piAdiLine[iCurr*iUnitSize-1];          for (i=0; i<iUnitSize; i++)          {            piAdiLineTemp[i] = piRef;          }          piAdiLineTemp += iUnitSize;          iCurr++;        }      }      else  //!< 当前点可用,继续检查下一点      {        piAdiLineTemp += iUnitSize;        iCurr++;      }    }    // Copy processed samples    piAdiLineTemp = piAdiLine + uiHeight + iUnitSize - 2;  //!< piAdiLineTemp = (piAdiLine + 128) + 3,跳过之前对左上角扩充的3个像素点    for (i=0; i<uiWidth; i++)  //!< 将最终结果拷贝到左上、上、右上边界    {      piAdiTemp[i] = piAdiLineTemp[i];    }    piAdiLineTemp = piAdiLine + uiHeight - 1; //!< uiHeight = uiCUHeight2 + 1    for (i=1; i<uiHeight; i++)  //!< 将最终结果拷贝到左和左下边界    {      piAdiTemp[i*uiWidth] = piAdiLineTemp[-i]; //!< piAdiLineTemp下标为-i是因为赋值方向与实际存储方向是相反的    }  }}


  关于一个PU的相邻点,以及它的相邻点的可用性如何判断的问题,是一个细节问题,并不会影响我们对这个函数实现功能的理解,但是,为了更好地理解这一个过程,这些坐标为什么这么算还是一个值得讨论的问题,限于篇幅,本文不作讨论。我会在接下来的文章中陆续对留下来的问题作更为详尽的讨论。

转自(http://blog.csdn.net/hevc_cjl/article/details/8175721)