HEVC中一共定义了35中帧内编码预测模式,编号分别以0-34定义。其中模式0定义为平面模式(INTRA_PLANAR),模式1定义为均值模式(INTRA_DC),模式2~34定义为角度预测模式(INTRA_ANGULAR2~INTRA_ANGULAR34),分别代表了不同的角度。具体的示意图如标准文档的图8-1所示:
这三大类的预测方法均有实现的代码。首先看最简单的Intra_DC模式,该模式同角度预测模式实现在同一个函数Void TComPrediction::xPredIntraAng(...)中:
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 )
{
//......
// Do the DC prediction
if (modeDC)
{
Pel dcval = predIntraGetPredValDC(pSrc, srcStride, width, height, blkAboveAvailable, blkLeftAvailable);
for (k=0;k<blkSize;k++)
{
for (l=0;l<blkSize;l++)
{
pDst[k*dstStride+l] = dcval;
}
}
}
//......
}
在这个函数中可以看到,Intra_DC模式中所有预测块的像素值都是同一个值dcval,这个值是由一个函数predIntraGetPredValDC计算得到:
Pel TComPrediction::predIntraGetPredValDC( Int* pSrc, Int iSrcStride, UInt iWidth, UInt iHeight, Bool bAbove, Bool bLeft )
{
Int iInd, iSum = 0;
Pel pDcVal;
if (bAbove)
{
for (iInd = 0;iInd < iWidth;iInd++)
{
iSum += pSrc[iInd-iSrcStride];
}
}
if (bLeft)
{
for (iInd = 0;iInd < iHeight;iInd++)
{
iSum += pSrc[iInd*iSrcStride-1];
}
}
if (bAbove && bLeft)
{
pDcVal = (iSum + iWidth) / (iWidth + iHeight);
}
else if (bAbove)
{
pDcVal = (iSum + iWidth/2) / iWidth;
}
else if (bLeft)
{
pDcVal = (iSum + iHeight/2) / iHeight;
}
else
{
pDcVal = pSrc[-1]; // Default DC value already calculated and placed in the prediction array if no neighbors are available
}
return pDcVal;
}
在该函数中,编码器通过判断上方和左方参考像素是否有效而选择将相应的数据(指针pSrc指向的数据)累加到iSum中,并对这些参考数据取平均返回。所以,在DC模式下,所有预测像素值都是同一个值,也即参考数据的均值,这也是DC模式命名的由来。
第二种预测模式时平面模式,该模式定义在xPredIntraPlanar函数中。
Void TComPrediction::xPredIntraPlanar( Int* pSrc, Int srcStride, Pel* rpDst, Int dstStride, UInt width, UInt height )
{
assert(width == height);
Int k, l, bottomLeft, topRight;
Int horPred;
Int leftColumn[MAX_CU_SIZE], topRow[MAX_CU_SIZE], bottomRow[MAX_CU_SIZE], rightColumn[MAX_CU_SIZE];
UInt blkSize = width;
UInt offset2D = width;
UInt shift1D = g_aucConvertToBit[ width ] + 2;
UInt shift2D = shift1D + 1;
// Get left and above reference column and row
for(k=0;k<blkSize+1;k++)
{
topRow[k] = pSrc[k-srcStride];
leftColumn[k] = pSrc[k*srcStride-1];
}
// Prepare intermediate variables used in interpolation
bottomLeft = leftColumn[blkSize];
topRight = topRow[blkSize];
for (k=0;k<blkSize;k++)
{
bottomRow[k] = bottomLeft - topRow[k];
rightColumn[k] = topRight - leftColumn[k];
topRow[k] <<= shift1D;
leftColumn[k] <<= shift1D;
}
// Generate prediction signal
for (k=0;k<blkSize;k++)
{
horPred = leftColumn[k] + offset2D;
for (l=0;l<blkSize;l++)
{
horPred += rightColumn[k];
topRow[l] += bottomRow[l];
rpDst[k*dstStride+l] = ( (horPred + topRow[l]) >> shift2D );
}
}
}
首先从参考数据中获取的是顶行和左列的数据,并记录一下左下角和右上角的两个像素值。然后计算底行和右列的数据,方法是用左下角的像素减去
顶行相应位置的像素得到底行,右上角的像素减去左列相应位置的像素得到右列。预测块中每个像素的数据,就是对应的四个边的像素值的平均。
第三种预测模式,即mode=2~34时采用角度预测模式。实现的方式在xPredIntraAng中:
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 )
{
Int k,l;
Int blkSize = width;
Pel* pDst = rpDst;
// Map the mode index to main prediction direction and angle
assert( dirMode > 0 ); //no planar
Bool modeDC = dirMode < 2;
Bool modeHor = !modeDC && (dirMode < 18);
Bool modeVer = !modeDC && !modeHor;
Int intraPredAngle = modeVer ? (Int)dirMode - VER_IDX : modeHor ? -((Int)dirMode - HOR_IDX) : 0;//计算当前模式同水平/垂直模式之间的角度差
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;
// ......
// Do angular predictions
else
{
Pel* refMain;
Pel* refSide;
Pel refAbove[2*MAX_CU_SIZE+1];
Pel refLeft[2*MAX_CU_SIZE+1];
// Initialise the Main and Left reference array.
if (intraPredAngle < 0)
{
for (k=0;k<blkSize+1;k++)
{
refAbove[k+blkSize-1] = pSrc[k-srcStride-1];
}
for (k=0;k<blkSize+1;k++)
{
refLeft[k+blkSize-1] = pSrc[(k-1)*srcStride-1];
}
refMain = (modeVer ? refAbove : refLeft) + (blkSize-1);
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];
}
}
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;
refSide = modeVer ? refLeft : refAbove;
}
if (intraPredAngle == 0)
{
for (k=0;k<blkSize;k++)
{
for (l=0;l<blkSize;l++)
{
pDst[k*dstStride+l] = refMain[l+1];
}
}
if ( bFilter )
{
for (k=0;k<blkSize;k++)
{
pDst[k*dstStride] = Clip3(0, (1<<bitDepth)-1, pDst[k*dstStride] + (( refSide[k+1] - refSide[0] ) >> 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];
}
}
}
}
// Flip the block if this is the horizontal mode
if (modeHor)
{
Pel tmp;
for (k=0;k<blkSize-1;k++)
{
for (l=k+1;l<blkSize;l++)
{
tmp = pDst[k*dstStride+l];
pDst[k*dstStride+l] = pDst[l*dstStride+k];
pDst[l*dstStride+k] = tmp;
}
}
}
}
}
在图8.1中可以看出,模式18的预测方向相当于对角线预测。所以以模式18为分界线,2~17分为水平模式(modeHor),18~33分为垂直模式(modeVer),这样区分有利于减少代码的冗余。另外,从该图中也可以看出,模式10和26即相当于水平模式和垂直模式,在代码中也定义了两个宏HOR_IDX和VER_IDX表示,然后计算当前模式同水平/垂直模式之间的角度差,用intraPredAngle表示。intraPredAngle不同的取值对应的预测方向可以参考图8-2:
除此之外,这个函数还实现了对小于16×16尺寸块实现滤波操作,以及水平模式时将预测矩阵进行转置操作。
大致上Intra预测块的生成方法就这样了,下一个问题在于,参考像素是如何来的?pSrc指针指向的数据又是如何获取的?且听下回。