【HEVC学习与研究】39、HEVC帧内编码的原理和实现(上)

简介: 【前面N篇博文都讲了一些HEVC帧内预测的代码结构和简单的方法,但是尚未对整体的算法和实现做一个比较完整的描述。本篇借助参考文献《High Efficiency Video Coding (HEVC) -- Algorithms and Architectures》的相关章节的阅读笔记,对HEVC的帧内预测算法做一个比较完整的阐述。

【前面N篇博文都讲了一些HEVC帧内预测的代码结构和简单的方法,但是尚未对整体的算法和实现做一个比较完整的描述。本篇借助参考文献《High Efficiency Video Coding (HEVC) -- Algorithms and Architectures》的相关章节的阅读笔记,对HEVC的帧内预测算法做一个比较完整的阐述。】

【摘要】HEVC的帧内预测的架构分为三个步骤:①构建参考像素数组;②生成预测像素;③后处理操作。HEVC标准将这三个步骤进行了精密设计,以求达到较高的编码效率,同时降低编码和解码端的运算要求。HEVC标准的多种预定义的预测模式构成一个模式集合,可以对包括视频和静态图像中的多种内容进行预测建模的方法。HEVC的角度预测提供了对包含方向性纹理的物体更高的预测精确度,此外平面和DC模式可以高效地表示图像的平滑区域。

1.引言

HEVC的帧内预测方法可以分为两大类:第一类为角度预测,用于准确预测角度结构;第二类为平面模式和DC模式,用于估计平滑图像区域。HEVC总计支持35种帧内预测的模式,如下表所示:


HEVC所有的35种预测模式使用相邻的重建像素块的信息作为参考像素。由于重建像素块在Transform Block级别上实现,执行帧内预测操作的数据同Transform Block大小相同,从4×4到32×32。HEVC支持各种块尺寸上的各种预测模式,因此各种预测组合模式的数量庞大,HEVC对于各种预测模式进行了特别设计以利于对任意块尺寸/模式进行算法的实现。为了提高获得更准确预测的可能性,HEVC在进行实际的帧内预测过程之前,对参考像素进行滤波预处理操作。某些预测模式还包含后处理操作用于优化像素块边缘的连续性,如DC模式,水平模式(Mode 10)和垂直模式(Mode 26)。

2、生成参考像素

HEVC帧内预测由重建的参考像素根据预测模式推导而得。相比较H.264,HEVC引入了参考像素替换方法,该方法无论相邻像素是否可得都可以获取完整的参考像素集合。另外,HEVC还定义了自适应的滤波过程,可以根据预测模式、块大小和方向性对参考像素进行预滤波处理。

(1)参考像素替换

有时候由于多种原因,帧内预测的时候无法获取部分或全部参考像素。例如,位于图像、slice或ties之外的像素在预测中被认为是不可得的。另外,当constrained intra prediction开启时,来自帧间预测的PU的像素将会被忽略,这是为了防止前面被错误地传输和重建的图像对当前图像造成影响。在H.264中,这些情况都会被当做DC模式进行处理,而在HEVC中,在对无效的参考像素进行替换后可以当做普通的像素块进行处理。

对于极端的情况,即所有参考像素都不可得的情况下,所有参考点都被设置为一个常量(8bit下为128)。

在HM10中的方法:

Void TComPattern::initAdiPattern( TComDataCU* pcCU, UInt uiZorderIdxInPart, UInt uiPartDepth, Int* piAdiBuf, Int iOrgBufStride, Int iOrgBufHeight, Bool& bAbove, Bool& bLeft, Bool bLMmode )
{
    //.......
    //计算有效相邻点的个数
    bNeighborFlags[iNumUnitsInCu*2] = isAboveLeftAvailable( pcCU, uiPartIdxLT );
  iNumIntraNeighbor  += (Int)(bNeighborFlags[iNumUnitsInCu*2]);
  iNumIntraNeighbor  += isAboveAvailable     ( pcCU, uiPartIdxLT, uiPartIdxRT, bNeighborFlags+(iNumUnitsInCu*2)+1 );
  iNumIntraNeighbor  += isAboveRightAvailable( pcCU, uiPartIdxLT, uiPartIdxRT, bNeighborFlags+(iNumUnitsInCu*3)+1 );
  iNumIntraNeighbor  += isLeftAvailable      ( pcCU, uiPartIdxLT, uiPartIdxLB, bNeighborFlags+(iNumUnitsInCu*2)-1 );
  iNumIntraNeighbor  += isBelowLeftAvailable ( pcCU, uiPartIdxLT, uiPartIdxLB, bNeighborFlags+ iNumUnitsInCu   -1 );
    //......
    fillReferenceSamples (g_bitDepthY, piRoiOrigin, piAdiTemp, bNeighborFlags, iNumIntraNeighbor, iUnitSize, iNumUnitsInCu, iTotalUnits, uiCuWidth, uiCuHeight, uiWidth, uiHeight, iPicStride, bLMmode);
    //......
}

其中fillReferenceSamples的实现如下:

Void TComPattern::fillReferenceSamples(Int bitDepth, 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;
  Int  i, j;
  Int  iDCValue = 1 << (bitDepth - 1);

  if (iNumIntraNeighbor == 0)
  {
    // Fill border with DC value
    for (i=0; i<uiWidth; i++)
    {
      piAdiTemp[i] = iDCValue;
    }
    for (i=1; i<uiHeight; i++)
    {
      piAdiTemp[i*uiWidth] = iDCValue;
    }
  }
  //......
}
当左侧和上方的相邻像素均可得时,参考像素集合直接拷贝这些像素。在HM中也在 fillReferenceSamples中实现:

Void TComPattern::fillReferenceSamples(Int bitDepth, 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 )
{
  //......
  else if (iNumIntraNeighbor == iTotalUnits)
  {
    // 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)
    {
      piRoiTemp --; // move to the second left column
    }

    for (i=0; i<uiCuHeight; i++)
    {
      piAdiTemp[(1+i)*uiWidth] = piRoiTemp[0];
      piRoiTemp += iPicStride;
    }

    // Fill below left border with rec. samples
    for (i=0; i<uiCuHeight; i++)
    {
      piAdiTemp[(1+uiCuHeight+i)*uiWidth] = piRoiTemp[0];
      piRoiTemp += iPicStride;
    }

    // Fill top border with rec. samples
    piRoiTemp = piRoiOrigin - iPicStride;
    for (i=0; i<uiCuWidth; i++)
    {
      piAdiTemp[1+i] = piRoiTemp[i];
    }
    
    // Fill top right border with rec. samples
    piRoiTemp = piRoiOrigin - iPicStride + uiCuWidth;
    for (i=0; i<uiCuWidth; i++)
    {
      piAdiTemp[1+uiCuWidth+i] = piRoiTemp[i];
    }
  }
  //......
}

如果至少一个像素为可用数据,其他不可得的像素都将使用该像素按顺时针的顺序进行替换。

①当p[-1][2N-1]不可得时,则沿着p[-1][2N-2],p[-1][2N-3].....p[-1][-1],p[0][-1],p[1][-1]......p[2N-1][-1],即先自下而上,后从左向右查找,由找到的第一个有效的点替换。

②所有的无效的点p[-1][y], y=2N-2~1,都由p[-1][y+1]替换,即对于垂直方向的参考像素,无效点都由下方的点替换。

③所有的无效的点p[x][-1], x=0~2N-1,都由p[x-1][-1]替换,即对于水平方向的参考像素,无效点都由左侧的点替换。

fillReferenceSamples函数的最后一个选择结构中实现的便是该方法:

Void TComPattern::fillReferenceSamples(Int bitDepth, 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 )
{
  //......
  else // reference samples are partially available
  {
    Int  iNumUnits2 = iNumUnitsInCu<<1;
    Int  iTotalSamples = iTotalUnits*iUnitSize;
    Pel  piAdiLine[5 * MAX_CU_SIZE];
    Pel  *piAdiLineTemp; 
    Bool *pbNeighborFlags;
    Int  iNext, iCurr;
    Pel  piRef = 0;

    // Initialize
    for (i=0; i<iTotalSamples; i++)
    {
      piAdiLine[i] = iDCValue;
    }
    
    // Fill top-left sample
    piRoiTemp = piRoiOrigin - iPicStride - 1;
    piAdiLineTemp = piAdiLine + (iNumUnits2*iUnitSize);
    pbNeighborFlags = bNeighborFlags + iNumUnits2;
    if (*pbNeighborFlags)
    {
      piAdiLineTemp[0] = piRoiTemp[0];
      for (i=1; i<iUnitSize; i++)
      {
        piAdiLineTemp[i] = piAdiLineTemp[0];
      }
    }

    // Fill left & below-left samples
    piRoiTemp += iPicStride;
    if (bLMmode)
    {
      piRoiTemp --; // move the second left column
    }
    piAdiLineTemp--;
    pbNeighborFlags--;
    for (j=0; j<iNumUnits2; j++)
    {
      if (*pbNeighborFlags)
      {
        for (i=0; i<iUnitSize; i++)
        {
          piAdiLineTemp[-i] = piRoiTemp[i*iPicStride];
        }
      }
      piRoiTemp += iUnitSize*iPicStride;
      piAdiLineTemp -= iUnitSize;
      pbNeighborFlags--;
    }

    // Fill above & above-right samples
    piRoiTemp = piRoiOrigin - iPicStride;
    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];
        }
      }
      piRoiTemp += iUnitSize;
      piAdiLineTemp += iUnitSize;
      pbNeighborFlags++;
    }

    // Pad reference samples when necessary
    iCurr = 0;
    iNext = 1;
    piAdiLineTemp = piAdiLine;
    while (iCurr < iTotalUnits)
    {
      if (!bNeighborFlags[iCurr])
      {
        if(iCurr == 0)
        {
          while (iNext < iTotalUnits && !bNeighborFlags[iNext])
          {
            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;
    for (i=0; i<uiWidth; i++)
    {
      piAdiTemp[i] = piAdiLineTemp[i];
    }
    piAdiLineTemp = piAdiLine + uiHeight - 1;
    for (i=1; i<uiHeight; i++)
    {
      piAdiTemp[i*uiWidth] = piAdiLineTemp[-i];
    }
  }
}


(2)参考像素的滤波

同H264的8×8帧内预测类似,HEVC的帧内预测参考像素都会依据条件进行平滑滤波处理。滤波的目的在于提升帧内预测的像素块的视觉效果,减小边缘可能产生的突变感。是否对参考像素进行滤波取决于帧内预测模式和预测像素块的大小。

当采用DC模式,或预测像素块为4×4大小时,不需要进行滤波操作。对于其他情况,块的大小和预测的方向决定了是否要进行滤波操作。对于8×8大小的编码像素块,仅仅针对对角线方向的预测模式(即模式2,18,34)进行滤波。对于16×16的像素块,大部分模式都会进行参考点滤波,除了几个近似水平和垂直模式(即模式9,10,11,25,27)。对于32×32的像素块,除了垂直和水平模式(即模式10和26)外所有的模式都要进行滤波。

对参考像素进行滤波时,共有两种滤波方法可供选择。默认情况下,滤波器采用一种[1,2,1]一维滤波器对参考像素进行处理。另一种方法,如果像素块的大小为32×32,且参考像素的走势足够平坦,那么参考像素将依据起点、终点和拐点三个特殊点(p[-1][63],p[-1][-1],p[63][-1])进行线性插值的方法获得。关于是否“足够平坦”这一问题,则依以下两个准则确定(b取表示一个颜色分量的bit位数):

|p[-1][-1]+p[2N-1][-1]-2p[N-1][-1]| < (1<<(b-5))

|p[-1][-1]+p[-1][2N-1]-2p[-1][N-1]| < (1<<(b-5))

HM中的实现方法如:

Void TComPattern::initAdiPattern( TComDataCU* pcCU, UInt uiZorderIdxInPart, UInt uiPartDepth, Int* piAdiBuf, Int iOrgBufStride, Int iOrgBufHeight, Bool& bAbove, Bool& bLeft, Bool bLMmode )  
{  
//......  
if (pcCU->getSlice()->getSPS()->getUseStrongIntraSmoothing())  
  {  
    Int blkSize = 32;  
    Int bottomLeft = piFilterBuf[0];  
    Int topLeft = piFilterBuf[uiCuHeight2];  
    Int topRight = piFilterBuf[iBufSize-1];  
    Int threshold = 1 << (g_bitDepthY - 5);  
    Bool bilinearLeft = abs(bottomLeft+topLeft-2*piFilterBuf[uiCuHeight]) < threshold;  
    Bool bilinearAbove  = abs(topLeft+topRight-2*piFilterBuf[uiCuHeight2+uiCuHeight]) < threshold;  
    
    if (uiCuWidth>=blkSize && (bilinearLeft && bilinearAbove))  
    {  
      Int shift = g_aucConvertToBit[uiCuWidth] + 3;  // log2(uiCuHeight2)  
      piFilterBufN[0] = piFilterBuf[0];  
      piFilterBufN[uiCuHeight2] = piFilterBuf[uiCuHeight2];  
      piFilterBufN[iBufSize - 1] = piFilterBuf[iBufSize - 1];  
      for (i = 1; i < uiCuHeight2; i++)  
      {  
        piFilterBufN[i] = ((uiCuHeight2-i)*bottomLeft + i*topLeft + uiCuHeight) >> shift;  
      }  
    
      for (i = 1; i < uiCuWidth2; i++)  
      {  
        piFilterBufN[uiCuHeight2 + i] = ((uiCuWidth2-i)*topLeft + i*topRight + uiCuWidth) >> shift;  
      }  
    }  
    else   
    {  
      // 1. filtering with [1 2 1]  
      piFilterBufN[0] = piFilterBuf[0];  
      piFilterBufN[iBufSize - 1] = piFilterBuf[iBufSize - 1];  
      for (i = 1; i < iBufSize - 1; i++)  
      {  
        piFilterBufN[i] = (piFilterBuf[i - 1] + 2 * piFilterBuf[i]+piFilterBuf[i + 1] + 2) >> 2;  
      }  
    }  
  }  
  else   
  {  
    // 1. filtering with [1 2 1]  
    piFilterBufN[0] = piFilterBuf[0];  
    piFilterBufN[iBufSize - 1] = piFilterBuf[iBufSize - 1];  
    for (i = 1; i < iBufSize - 1; i++)  
    {  
      piFilterBufN[i] = (piFilterBuf[i - 1] + 2 * piFilterBuf[i]+piFilterBuf[i + 1] + 2) >> 2;  
    }  
  }  
//......  
}  



目录
相关文章
|
编解码
HEVC学习之琐事(一):HEVC编码结构分析
<p><span style="font-size:14px"><span style="white-space:pre"></span><span style="white-space:pre"></span><span style="white-space:pre"></span>在H.264中,编码的基本单元是宏块,对于抽样格式为4:2:0的宏块,它包含一个16x16的亮度样本块和两
4637 0
|
编解码 流计算
研究音频编解码要看什么书
前言。。。。。。 最近总是有人问研究音频编解码要看什么书 其实这是一个很难回答的问题,原因有很多。 首先,做工程首先一个问题就是和课本学习不同,不是看书能解决的。 其次,音频编解码技术在国内研究的人很少包括总体的音频技术国内相对国外都研究的不多。
2211 0
|
编解码
【H.264/AVC视频编解码技术详解】十五、H.264的变换编码(二):H.264整数变换和量化的实现
《H.264/AVC视频编解码技术详解》视频教程已经在“CSDN学院”上线,视频中详述了H.264的背景、标准协议和实现,并通过一个实战工程的形式对H.
2366 0
|
编解码
【H.264/AVC视频编解码技术详解】十四、H.264的变换编码(一)——矩阵运算与正交变换基本概念
《H.264/AVC视频编解码技术详解》视频教程已经在“CSDN学院”上线,视频中详述了H.264的背景、标准协议和实现,并通过一个实战工程的形式对H.
1526 0
|
编解码 算法 索引
【H.264/AVC视频编解码技术详解】十三、熵编码算法(4):H.264使用CAVLC解析宏块的残差数据
《H.264/AVC视频编解码技术详解》视频教程已经在“CSDN学院”上线,视频中详述了H.264的背景、标准协议和实现,并通过一个实战工程的形式对H.
1790 0
|
编解码 算法
【H.264/AVC视频编解码技术详解】十三、熵编码算法(3):CAVLC原理
《H.264/AVC视频编解码技术详解》视频教程已经在“CSDN学院”上线,视频中详述了H.264的背景、标准协议和实现,并通过一个实战工程的形式对H.
1654 0
|
编解码
【H.264/AVC视频编解码技术详解】十二、解析H.264码流的宏块结构(下):H.264帧内编码宏块的预测结构
《H.264/AVC视频编解码技术详解》视频教程已经在“CSDN学院”上线,视频中详述了H.264的背景、标准协议和实现,并通过一个实战工程的形式对H.
1653 0