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;
intraPredAngle这个变量中就保存了当前模式同水平/垂直模式映射到边界上的偏移值,精度为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;//最终计算得到的角度差为-2mode11的预测方向为水平向右,并略带向右下方倾斜,其参考的像素大部分为左侧像素,同时也会用到几个上方的像素。具体所需的像素个数为块尺寸×角度差参数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模式,需要根据原预测像素的位置分为三种情况: