HEVC帧内编码的原理和实现

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帧内编码的原理和实现(中)


3、像素块的帧内预测

为了有效地预测多种不同种类的内容,HEVC支持多种不同的预测方法。角度预测可以模拟多种不同方向的结构,而平面和DC模式适用于平滑和渐变区域。同样对于任何角度预测模式都不能很好地适应的复杂纹理的像素块,DC和平面模式还可以用于在这种情况下生成一种不带有高频信息的“通用”预测块。此外,对于DC和某些角度预测模式,帧内编码还包括一些滤波后处理操作用于在块的边界提升像素之间的连续性以提高预测的质量。

(1)角度预测

HEVC中的角度预测方法用于高效地对图像和视频的不同方向性结构进行建模的方法。该方法所选择的预测方向经过设计,可以达到对典型视频内容的编码效率和编码复杂度的平衡。算法对不同的预测块大小和预测方向提出了严格的低复杂度要求,因为HEVC帧内预测所支持的预测模式远远超过H.264等前期标准。在HEVC中,HEVC定义了从4×4到32×32这四种不同大小的帧内预测块尺寸,每种尺寸都支持33种不同的预测方形,所以一个解码器必须支持总共132中组合。

(1.1)角度的定义

HEVC定义了33中预测角度的集合,其精度为1/32。根据经验,图像内容中水平和垂直一致通常出现的概率超过其他方向一致。相邻方向之间的角度差越接近这两个方向便越小,越靠近对角线方向便越大,其目的在于在接近水平和垂直模式时可以提供更加精准的预测结果,而在出现机会较低的对角方向减小预测的运算负荷。各个预测方向如下图所示:

下表定义了各个预测模式与方向角度差之间的一一对应关系。角度差用参数A表示,在生成参考像素时会作为获取参考数据的依据。


(1.2)参考像素对负角度方向的扩展

为了简化预测过程,编码器将当前像素块上方的参考像素p[x][-1]和左方的参考像素p[-1][y]置于一个一维数组中。对于正角度(模式26~33和模式2~10),该数组直接拷贝预测方向上的像素值,公式如下:


对于负角度,左侧和上方的参考像素都会被使用到,此时参考数组中的非负索引的元素依旧如前文所述,负索引值所表示的像素通过映射获得,公式如下:


其中B与角度参数A的对应关系如下表:


这部分该如何理解呢?以下两张图分别是正向角度的垂直模式/水平模式的方向示意图:


对于不同的预测方向,预测块所使用的预测数据时不同的。由于预测像素的组织形式是一个一维数组,以顶点像素为中心向两边扩展。在正向角度预测时,只需要或正方向或负方向的预测数据级就可以为预测块进行赋值。而对于负向角度,情况将有所不同。下图表示负向角度下的预测方向示意图:


举例,当mode为2时,预测方向为右上方对角线。处理代码如下:

Void TComPrediction::xPredIntraAng(Int bitDepth, Int* pSrc, Int srcStride, Pel*& rpDst, Int dstStride, UInt width, UInt height, UInt dirMode, Bool blkAboveAvailable, Bool blkLeftAvailable, Bool bFilter )  
{  
   //......  
  // Map the mode index to main prediction direction and angle  
  assert( dirMode > 0 ); //no planar  
  Bool modeDC        = dirMode < 2;//对于mode2,显然不是DC模式  
  Bool modeHor       = !modeDC && (dirMode < 18);//mode2小于18,因此属于水平类  
  Bool modeVer       = !modeDC && !modeHor;//mode11不属于垂直类  
  Int intraPredAngle = modeVer ? (Int)dirMode - VER_IDX/*26*/ : modeHor ? -((Int)dirMode - HOR_IDX/*10*/) : 0;//计算索引差值,值为8  
  Int absAng         = abs(intraPredAngle);//绝对值为8  
  Int signAng        = intraPredAngle < 0 ? -1 : 1;//符号位正  
  //......  
}  

而后,模式的索引差将转换为角度偏移差:


// Set bitshifts and scale the angle parameter to block size  
Int angTable[9]    = {0,    2,    5,   9,  13,  17,  21,  26,  32};  
Int invAngTable[9] = {0, 4096, 1638, 910, 630, 482, 390, 315, 256}; // (256 * 32) / Angle  
Int invAngle       = invAngTable[absAng];  
absAng             = angTable[absAng];//将模式索引差值转换为角度偏移差  
intraPredAngle     = signAng * absAng;  

ntraPredAngle这个变量中就保存了当前模式同水平/垂直模式映射到边界上的偏移值,精度为1/32像素。如果该参数为正,那么将当前预测块的上方和左方预测像素复制到两个数组中,并依据当前模式的方向分类确定哪一个作为主要参考哪一个作为辅助参考:


// Initialise the Main and Left reference array.  
if (intraPredAngle < 0)  
{  
  // 角度差为负    
  //......        
}  
else  
{  
  // 角度差为正  
  for (k=0;k<2*blkSize+1;k++)  
  {  
    refAbove[k] = pSrc[k-srcStride-1];//复制上方参考像素  
  }  
  for (k=0;k<2*blkSize+1;k++)  
  {  
    refLeft[k] = pSrc[(k-1)*srcStride-1];//复制左侧参考像素  
  }  
  refMain = modeVer ? refAbove : refLeft;//mode2属于水平类,因此refMain为refLeft,refSide为refAbove。  
  refSide = modeVer ? refLeft  : refAbove;  
}  
当对于mode11时,情况将有所不同。


Bool modeDC        = dirMode < 2;//mode11非DC模式  
Bool modeHor       = !modeDC && (dirMode < 18);//mode11属于水平类  
Bool modeVer       = !modeDC && !modeHor;  
Int intraPredAngle = modeVer ? (Int)dirMode - VER_IDX/*26*/ : modeHor ? -((Int)dirMode - HOR_IDX/*10*/) : 0;//模式索引差为-1  
Int absAng         = abs(intraPredAngle);  
Int signAng        = intraPredAngle < 0 ? -1 : 1;  
  
// Set bitshifts and scale the angle parameter to block size  
Int angTable[9]    = {0,    2,    5,   9,  13,  17,  21,  26,  32};  
Int invAngTable[9] = {0, 4096, 1638, 910, 630, 482, 390, 315, 256}; // (256 * 32) / Angle  
Int invAngle       = invAngTable[absAng];  
absAng             = angTable[absAng];//将模式索引差值转换为角度偏移差  
intraPredAngle     = signAng * absAng;//最终计算得到的角度差为-2  

mode11的预测方向为水平向右,并略带向右下方倾斜,其参考的像素大部分为左侧像素,同时也会用到几个上方的像素。具体所需的像素个数为块尺寸×角度差参数A的绝对值÷32,对于64×64的mode11就是64×2÷32=4。这几个值从refSide中每隔M个像素取一个,M的取值为invAngle的取值除以256。实现方法如下:


if (intraPredAngle < 0)  
{  
  for (k=0;k<blkSize+1;k++)  
  {  
    refAbove[k+blkSize-1] = pSrc[k-srcStride-1];//赋值上方的参考像素到refAbove[blkSize-1]~refAbove[2*blkSize-1],只复制前半部分  
  }  
  for (k=0;k<blkSize+1;k++)  
  {  
    refLeft[k+blkSize-1] = pSrc[(k-1)*srcStride-1];//赋值左方的参考像素到refLeft[blkSize-1]~refLeft[2*blkSize-1],只复制前半部分  
  }  
  refMain = (modeVer ? refAbove : refLeft) + (blkSize-1);//refMain和refSide指向拷贝的参考点的起始位置  
  refSide = (modeVer ? refLeft : refAbove) + (blkSize-1);  
  
  // Extend the Main reference to the left.  
  Int invAngleSum    = 128;       // rounding for (shift by 8)用于四舍五入  
  for (k=-1; k>blkSize*intraPredAngle>>5; k--)  
  {  
    invAngleSum += invAngle;  
    refMain[k] = refSide[invAngleSum>>8];//挑选某几个参考像素进行拷贝  
  }  
}  

(1.3)角度预测模式的像素值预测

预测像素的值p[x][y]由pel[x][y]的当前位置按照模式规定的方向向参考像素数组上进行映射获取,并按照1/32像素的精度进行差值。差值由最接近的两个像素点按照线性关系生成。对于水平和垂直模式生成预测数据的公式如下:
水平模式:

垂直模式:

公式中的i表示对于垂直模式在y列和水平模式在x行的偏移值的整数部分,按照如下方式计算:

公式中的f表示偏移的小数部分,计算方式如下:

i和f这两个常量适用于预测一组数据(垂直模式的一行和水平模式的一列),只有线性插值操作需要对每个像素值进行操作。如果f值为0,那么不进行线性插值,直接将参考数据用作预测数据。
具体实现如以下代码所示:


if (intraPredAngle == 0)  
{//水平或者垂直模式  
  for (k=0;k<blkSize;k++)  
  {  
    for (l=0;l<blkSize;l++)  
    {  
      pDst[k*dstStride+l] = refMain[l+1];//直接复制参考数据中的值  
    }  
  }  
  
  //....  
}  
else  
{//预测模式存在角度差  
  Int deltaPos=0;  
  Int deltaInt;  
  Int deltaFract;  
  Int refMainIndex;  
  
  for (k=0;k<blkSize;k++)  
  {  
    deltaPos += intraPredAngle;  
    deltaInt   = deltaPos >> 5;  
    deltaFract = deltaPos & (32 - 1);  
  
    if (deltaFract)  
    {  
      // Do linear filtering  
      for (l=0;l<blkSize;l++)  
      {  
        refMainIndex        = l+deltaInt+1;  
        pDst[k*dstStride+l] = (Pel) ( ((32-deltaFract)*refMain[refMainIndex]+deltaFract*refMain[refMainIndex+1]+16) >> 5 );//亚像素预测,从两个相邻像素中获取加权均值  
      }  
    }  
    else  
    {  
      // Just copy the integer samples  
      for (l=0;l<blkSize;l++)  
      {  
        pDst[k*dstStride+l] = refMain[l+deltaInt+1];  
      }  
    }  
  }  
}  

(2)、DC预测模式

对于DC预测模式,所有的预测数据采用同一数值,该数值由左方和上方参考数据的平均值生成。对于16×16或更小的DC预测块还需要一个滤波过程来优化左边和上方的边界效果,具体方法第四节详述。


(3)、平面预测模式

角度预测可以对方向性结构的像素块进行较为精确的预测,但在光滑的图像区也会产生一些肉眼可见的边界轮廓。类似的是,DC预测模式在中低码率下也可能会产生一些块效应。HEVC定义了平面模式用于处理类似的问题,可以生成一个在边界上没有不连续断层的预测平面。其方法为依据水平和垂直的线性预测方法,公式如下:


水平方向的预测结果Ph[x][y]和垂直方向上的预测结果Pv[x][y]按照以下方法生成:


(4)、像素预测值的后处理

有些预测模式在预测像素块的边界处可能产生不连续的像素值断层,对DC模式和角度预测中的水平和垂直模式尤为明显。在DC模式下,顶部和左侧边界都会产生不连续效应,因为整个预测像素值都由同一个平均值替换。对于垂直模式,左侧边界可能产生不连续边界,因为最左边一列的预测像素值复制了块上方最左侧的参考像素。对于水平模式的最顶行也存在类似的问题。
为了降低块边界的这种不连续性,预测块内部的边界像素需要考虑块边界的斜率做一次滤波并用结果进行替换。这一步仅仅针对DC、水平和垂直模式,而且预测块的尺寸小于32×32时进行。事实证明这种设置可以在编码效率和运算复杂度上取得一个较好的平衡。另外,由于亮度分量有更为均衡的特性,预测块边界滤波操作仅限于亮度分量。
如果预测模式为垂直模式,预测像素p[0][y](y∈[0,N-1])由以下公式的结果进行替换:


对于水平模式,操作类似。
对于DC模式,需要根据原预测像素的位置分为三种情况:




HEVC帧内编码的原理和实现(下)


4、编码帧内预测模式

大量增加可选择的预测模式可以提供更高的编码效率,同时要求编码预测模式有更加高效的方法降低更多模式带来的负担。与H.264采用一个最可能模式不同的是,对于亮度分量,三个最可能模式用于预测实际的帧内预测模式。同时,也考虑了三个最可能模式中的冗余信息,重复的模式会使用其他模式进行替换。对于亮度分量,HEVC采用了同亮度分量相同的预测模式。在编码亮度和色度帧内预测模式时,各个语法元素的设计也体现了亮度分量更多的最可能候选模式以及亮度分量的统计特性。

(1)亮度帧内预测模式的预测

HEVC对每种PU大小共支持总计33种角度模式,外加上DC和平面模式。由于支持的模式更多,类似于H.264的单一最可能模式在HEVC中效率不高。HEVC针对每种PU大小定义了三个最可能模式,其选择依赖于相邻PU所选的模式。同时,由于最可能模式之外的模式概率分布接近平均,这样也可以由一个不经过CABAC的定长编码表示的结构来从剩余32个模式中指定一个模式。
最可能模式的选择根据左侧和上方的相邻块的模式确定。如果某一个相邻块不是帧内编码或者编码模式为PCM模式,或左边PU处于当前CTU之外,那么该块的模式在这个过程中等同于DC模式。设左侧和上方PU的模式分别为A和B,如果A≠B,那么这两个模式分别设为MPM[0]和MPM[1]。对于MPM[2]的确定需要遵循以下原则:①如果A或B都不是平面模式,那么MPM[2]设为平面模式;②否则,若A或B都不是DC模式,那么MPM[2]设为DC模式;③否则(即AB中有一个平面一个DC模式),MPM[2]设为模式26,即垂直模式。如果A=B,那么确定MPM的方法如下:①如果A和B都不是角度预测模式,那么MPM分别设定为平面、DC和垂直模式;②否则,MPM[0]设为A/B,MPM[1]和MPM[2]选择A的相邻模式,公式如下


三个最可能模式确定后,会按照模式序号进行排序。如果当前预测模式等同于三个最可能模式之一,那么只有该模式在MPM的索引值会传递给解码器,否则将会传递一个5位且不经过CABAC的语法元素表示在MPM之外所选择的帧内预测模式。

该部分的实现如下:


/** Get most probable intra modes 
*\param   uiAbsPartIdx 
*\param   uiIntraDirPred  pointer to the array for MPM storage 
*\param   piMode          it is set with MPM mode in case both MPM are equal. It is used to restrict RD search at encode side. 
*\returns Number of MPM 
*/  
Int TComDataCU::getIntraDirLumaPredictor( UInt uiAbsPartIdx, Int* uiIntraDirPred, Int* piMode  )  
{  
  TComDataCU* pcTempCU;  
  UInt        uiTempPartIdx;  
  Int         iLeftIntraDir, iAboveIntraDir;  
  Int         uiPredNum = 0;  
    
  // Get intra direction of left PU  
  pcTempCU = getPULeft( uiTempPartIdx, m_uiAbsIdxInLCU + uiAbsPartIdx );//获取左方PU  
    
  iLeftIntraDir  = pcTempCU ? ( pcTempCU->isIntra( uiTempPartIdx ) ? pcTempCU->getLumaIntraDir( uiTempPartIdx ) : DC_IDX ) : DC_IDX;//左方PU为空或非Intra,左方的PU模式则设置为DC,否则设置为左方PU的模式  
    
  // Get intra direction of above PU  
  pcTempCU = getPUAbove( uiTempPartIdx, m_uiAbsIdxInLCU + uiAbsPartIdx, true, true );//获取上方PU  
    
  iAboveIntraDir = pcTempCU ? ( pcTempCU->isIntra( uiTempPartIdx ) ? pcTempCU->getLumaIntraDir( uiTempPartIdx ) : DC_IDX ) : DC_IDX;//上方PU为空或非Intra,上方的PU模式则设置为DC,否则设置为上方PU的模式  
    
  uiPredNum = 3;  
  if(iLeftIntraDir == iAboveIntraDir)  
  {  
      //模式A和模式B相等的情况  
    if( piMode )  
    {  
      *piMode = 1;  
    }  
      
    if (iLeftIntraDir > 1) // angular modes  
    {  
        //如果二者都是角度预测,那么MPM[0]设置为该角度模式,MPM[1]和MPM[2]设置为该模式的相邻模式  
      uiIntraDirPred[0] = iLeftIntraDir;  
      uiIntraDirPred[1] = ((iLeftIntraDir + 29) % 32) + 2;  
      uiIntraDirPred[2] = ((iLeftIntraDir - 1 ) % 32) + 2;  
    }  
    else //non-angular  
    {  
        //如果二者都不是角度预测,那么三种模式分别设置为平面、DC和垂直模式  
      uiIntraDirPred[0] = PLANAR_IDX;  
      uiIntraDirPred[1] = DC_IDX;  
      uiIntraDirPred[2] = VER_IDX;   
    }  
  }  
  else  
  {  
      //模式A与模式B不等  
    if( piMode )  
    {  
      *piMode = 2;  
    }  
    uiIntraDirPred[0] = iLeftIntraDir;//MPM[0]和MPM[1]分别设置为这两种模式  
    uiIntraDirPred[1] = iAboveIntraDir;  
      
    if (iLeftIntraDir && iAboveIntraDir ) //both modes are non-planar  
    {  
        //当两个模式都不是平面模式时,MPM[2]设置为平面模式  
      uiIntraDirPred[2] = PLANAR_IDX;  
    }  
    else  
    {  
        //至少有一个是平面模式时,如果另一个是DC模式,那么MPM[2]设置为垂直模式;如果另一个不是DC模式,那么MPM[2]设置为DC模式  
      uiIntraDirPred[2] =  (iLeftIntraDir+iAboveIntraDir)<2? VER_IDX : DC_IDX;  
    }  
  }  
    
  return uiPredNum;  
}  

(2)色度帧内预测模式的导出

为了在增加了预测模式的数量的同时降低传输负载,HEVC定义了INTER_DERIVED模式表示该色度PU使用亮度PU相同的预测模式。对于一个PU,只有平面、垂直、水平、DC和导出模式可供选择。如果一个色度PU选择了导出模式,那么帧内预测编码按照对应的亮度PU的模式进行。角度预测模式34用于在推导模式为前四种模式时做替换之用,替换原则如下表:


(3)帧内预测模式编码的语法结构

经过排序的三个最可能模式分别为SMPM[0]、SMPM[1]和SMPM[2]。prev_intra_luma_pred_flag表示亮度帧内预测模式等于三者之一,若相等,则定义mpm_idx表示SMPM[mpm_idx]为选定的帧内预测模式;否则,定义语法元素rem_intra_luma_pred_mode直接指定选定的帧内预测模式取值为[0,31]。亮度模式导出方法如下:①设L=rem_intra_luma_pred_mode;②对于i=0,1,2,若L≥SMPM[i],则L++。
对于亮度帧内预测模式,导出模式出现的概率最高,其余四种的概率基本一致。语法元素intra_chroma_pred_mode二值化的方法如下表:


5、编码算法

由于HEVC定义的模式数量较多,在大多数场合下对所有模式进行率失真计算是不现实的。在HEVC参考代码中,SATD用于在进行率失真优化前筛选候选的亮度帧内预测模式。进入Full RDO的预测模式的数量根据对应PU的大小确定:4×4和8×8时8个,其他尺寸时3个。对于这些模式以及MPM中的候选模式,进行预测和变换操作获取所需编码数据量和对应失真。最后率失真最低的模式被选中。对于色度帧内编码,由于模式数量较少,所有可能的预测模式都会基于率失真代价进行评估。
学术界对HEVC提出了多种快速帧内预测的算法。其中之一就是减少块尺寸的种类,如根据空间相邻块之前的判决的快速CU尺寸和模式判定算法、根据贝叶斯判决准则的CU筛选算法。另一种是减少候选帧内模式的方法,如在率失真计算阶段减少候选模式数量并强制使用最可能模式的方法、根据源图像方向性和相邻块进行模式预筛选的方法等。

6、HEVC与H.264帧内编码的主要区别

HEVC和H.264帧内编码都是基于空间图像预测的方法,预测后都会进行变换编码,而HEVC的算法更加复杂。
第一,为了同HEVC的块分割结构一致,并且更适合于平滑图像区域的编码,HEVC支持的预测块大小最大可到32×32;
第二,预测的方向从H.264的8种增加到了33种用于提升对方向性结构的预测效果。HEVC可选的所有预测方向适用于亮度和色度,而H.264适用于色度的方向只有水平和垂直两种。并且HEVC针对垂直、水平和DC模式在边界像素上进行了优化;
第三,HEVC在参考数据部分缺失的情况下可以对其进行补全,并在这种情况下可以使用所有预定的预测模式;
第四,预测模式编码次用了同H.264不同的方法,使用三个最可能模式进行预测,且选中的模式始终作为色度分量的有效候选模式之一;
以上区别总结如下表:


  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值