1、Planar模式,函数xPredIntraPlanar()
:
预测像素是水平、垂直两个方向上4个参考像素的平均值。
获取左侧及上方的参考像素,随后给右侧及下方参考像素赋值:
BL = leftColumn[height], TR = topRow[width]
rightColumn[y] = TR - leftColumn[y]
bottomRow[x] = BL - topRow[x]
预测值首先分为垂直,水平两部分:
verPred[x] = topRow[x] * h + bottomRow[x] * ( y + 1)
= topRow[x] * h + ( BL - topRow[x] ) * ( y + 1)
= topRow[x] * ( h - y -1 ) + BL * ( y + 1)
horPred[y] = leftColumn[y] * w + rightColumn[y] * ( x + 1)
= leftColumn[y] * w + ( TR - leftColumn[y] ) * ( x + 1)
= leftColumn[y] * ( w - x -1 ) + TR* ( x + 1)
pred[x][y] = (horPred[y] * h + verPred[x] * w + w * h) >> ( 1 + log2w + log2h)
2、DC模式,函数xPredIntraDC()
:
DC模式下,首先通过函数xGetPredValDc()
获取平均值dcval,为上方像素与左侧像素和的平均值(不包括左上角像素),将当前块的全部像素赋值为dcval(在BMS中,HEVC中默认的亮度预测块DC模式下的相关加权处理默认关闭)。
3、角度模式,函数xPredIntraAng()
:
3.1 角度模式预测值计算的理论:
假设预测块上一个预测位置 ( x , y ) (x,y) (x,y)的预测值为 p ( x , y ) p(x,y) p(x,y),设偏移值为 d ∈ [ − 32 , 32 ] d\in[-32, 32] d∈[−32,32](个人理解:偏移值最大应该为正负1,在函数处理中以32为单位进行),当前点在预测过程中实际投影到参考像素的坐标,相对于当前点坐标,位移为 C x Cx Cx(假设投影为x方向)。
满足相似三角形的特性:
C x Cx Cx / y = d / 32 /y = d/32 /y=d/32
所以可以知道:
C x = ( y ∗ d ) / 32 Cx = (y*d)/32 Cx=(y∗d)/32
( 为函数中后续循环的deltaInt )
d e l t a F r a c t = ( y ∗ d ) & 31 deltaFract = (y*d)\&31 deltaFract=(y∗d)&31
( 去掉大于32的部分,即为当前像素投影至参考像素的投影位置,到左侧相邻的整像素点的位置,同时作为后续求预测值的权重 )
对于65种角度模式而言,其对应的偏移值d表如下:
模式号 | 偏移值 | 模式号 | 偏移值 | 模式号 | 偏移值 | 模式号 | 偏移值 | 模式号 | 偏移值 |
---|---|---|---|---|---|---|---|---|---|
2 | 32 | 17 | 1 | 32 | -26 | 47 | -3 | 62 | 21 |
3 | 29 | 18 | 0 | 33 | -29 | 48 | -2 | 63 | 23 |
4 | 26 | 19 | -1 | 34 | -32 | 49 | -1 | 64 | 26 |
5 | 23 | 20 | -2 | 35 | -29 | 50 | 0 | 65 | 29 |
6 | 21 | 21 | -3 | 36 | -26 | 51 | 1 | 66 | 32 |
7 | 19 | 22 | -5 | 37 | -23 | 52 | 2 | ||
8 | 17 | 23 | -7 | 38 | -21 | 53 | 3 | ||
9 | 15 | 24 | -9 | 39 | -19 | 54 | 5 | ||
10 | 13 | 25 | -11 | 40 | -17 | 55 | 7 | ||
11 | 11 | 26 | -13 | 41 | -15 | 56 | 9 | ||
12 | 9 | 27 | -15 | 42 | -13 | 57 | 11 | ||
13 | 7 | 28 | -17 | 43 | -11 | 58 | 13 | ||
14 | 5 | 29 | -19 | 44 | -9 | 59 | 15 | ||
15 | 3 | 30 | -21 | 45 | -7 | 60 | 17 | ||
16 | 2 | 31 | -23 | 46 | -5 | 61 | 19 |
反角度参数用来消除计算预测值过程中的除法运算,用查表来代替。偏移值的正值与反角度参数的值对应关系如下表所示,负值对应的参数取负:
偏移值的正值 | 反角度参数 | 偏移值的正值 | 反角度参数 | 偏移值的正值 | 反角度参数 |
---|---|---|---|---|---|
0 | 0 | 9 | 910 | 21 | 390 |
1 | 8192 | 11 | 745 | 23 | 356 |
2 | 4096 | 13 | 630 | 26 | 315 |
3 | 2731 | 15 | 546 | 29 | 282 |
5 | 1638 | 17 | 482 | 32 | 256 |
7 | 1170 | 19 | 431 |
3.2 注意事项:
3.3 代码解析
//注:本文的函数中已经将默认关闭的宏相关内容删除。
Void IntraPrediction::xPredIntraAng( const CPelBuf &pSrc, PelBuf &pDst, const ChannelType channelType, const UInt dirMode, const ClpRng& clpRng, const SPS& sps, const bool enableBoundaryFilter )
{
Int width =Int(pDst.width);
Int height=Int(pDst.height);
CHECK( !( dirMode > DC_IDX && dirMode < NUM_LUMA_MODE ), "Invalid intra dir" );
const Bool bIsModeVer = (dirMode >= DIA_IDX);
const Int intraPredAngleMode = (bIsModeVer) ? (Int)dirMode - VER_IDX : -((Int)dirMode - HOR_IDX);
const Int absAngMode = abs(intraPredAngleMode);
const Int signAng = intraPredAngleMode < 0 ? -1 : 1;
// Set bitshifts and scale the angle parameter to block size
static const Int angTable[17] = { 0, 1, 2, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 26, 29, 32 };
static const Int invAngTable[17] = { 0, 8192, 4096, 2731, 1638, 1170, 910, 745, 630, 546, 482, 431, 390, 356, 315, 282, 256 }; // (256 * 32) / Angle
Int invAngle = invAngTable[absAngMode];
Int absAng = angTable [absAngMode];
Int intraPredAngle = signAng * absAng;//上文表中的偏移值
Pel* refMain;//主参考
Pel* refSide;//副参考
Pel refAbove[2 * MAX_CU_SIZE + 1];
Pel refLeft [2 * MAX_CU_SIZE + 1];
// Initialize the Main and Left reference array.
//****************************************** refMain[*]获取过程 ************************************************
//************************** 第一个表中模式对应的偏移值 < 0 ********************************
if (intraPredAngle < 0)//即模式[19,49],此时需要进行“投影像素”法
{
for( Int x = 0; x < width + 1; x++ )
{
refAbove[x + height - 1] = pSrc.at( x, 0 );//获取上方参考像素,置于refAbove[height - 1]开始及之后,共width+1个
}
for( Int y = 0; y < height + 1; y++ )
{
refLeft[y + width - 1] = pSrc.at( 0, y );//获取上方参考像素,置于refLeft[width - 1]开始及之后,共height +1个
}
refMain = (bIsModeVer ? refAbove + height : refLeft + width ) - 1;//refMain指向水平类/垂直类对应ref数据开始的部分。若为垂直类,refAbove 为主参考,refMain指向refAbove数据开始的部分。
refSide = (bIsModeVer ? refLeft + width : refAbove + height) - 1;//refSide 指向水平类/垂直类对应ref数据开始的部分。若为垂直类,refLeft 为副参考。
// Extend the Main reference to the left.即“投影像素”过程,将副参考值对应给refMain空缺的部分
Int invAngleSum = 128; // rounding for (shift by 8)
const Int refMainOffsetPreScale = bIsModeVer ? height : width;
for( Int k = -1; k > (refMainOffsetPreScale * intraPredAngle) >> 5; k-- )
{
invAngleSum += invAngle;
refMain[k] = refSide[invAngleSum>>8];//所有相关的参考像素值均放置在refMain中
//refMain[k] = refSide[((k*invAngle)+128)>>8], k = -1……,H或者W
//此操作中利用反角度参数invAngle避免除法运算
}
}
//************************** 偏移值 > 0********************************
else
{
for( Int x = 0; x < width + height + 1; x++ )
{
refAbove[x] = pSrc.at(x, 0);//refAbove[*]为参考样本对应位置的值,共width + height + 1个
refLeft[x] = pSrc.at(0, x);
}
refMain = bIsModeVer ? refAbove : refLeft ;//水平类、垂直类refMain不同
refSide = bIsModeVer ? refLeft : refAbove;
}
// swap width/height if we are doing a horizontal mode:
//为了简化后续步骤进行的操作,否则需要判断是根据高还是宽进行循环
Pel tempArray[MAX_CU_SIZE*MAX_CU_SIZE];
const Int dstStride = bIsModeVer ? pDst.stride : MAX_CU_SIZE;
Pel *pDstBuf = bIsModeVer ? pDst.buf : tempArray;
if (!bIsModeVer)
{
std::swap(width, height);
}
//****************************************** 预测值赋值过程 ************************************************
if( intraPredAngle == 0 ) // pure vertical or pure horizontal,纯水平或垂直模式,即模式18/50
{
for( Int y = 0; y < height; y++ )
{
for( Int x = 0; x < width; x++ )
{
pDstBuf[y*dstStride + x] = refMain[x + 1];//refMain[1] ~ refMain[width] 赋值给pDstBuf的每一行
}
}
}
else//非纯水平/垂直模式
{
Pel *pDsty=pDstBuf;//指向预测缓存的指针
for (Int y=0, deltaPos=intraPredAngle; y<height; y++, deltaPos+=intraPredAngle, pDsty+=dstStride)
{
//deltaPos = (y+1)*intraPredAngle(即理论阐述中的偏移值d)
const Int deltaInt = deltaPos >> 5;//计算当前像素对应参考像素在ref中的位置,此操作获取的是Cx,在后续计算中还需要+x+1(+1是由于循环从0开始,根据您后续代码自行理解)
const Int deltaFract = deltaPos & (32 - 1);//计算当前像素对应参考像素的加权因子
if( deltaFract )//加权因子是否为1,即判断是否需要进行插值,为1表示投影点在非整像素位置,需要插值。
{
#if JEM_TOOLS
//4抽头滤波在BMS中目前默认关闭,因为是新技术,故在本文的代码中保留
if( sps.getSpsNext().getUseIntra4Tap() )//采用4抽头插值滤波器
{
Int p[4];
const Bool useCubicFilter = (width <= 8);
const Int *f = (useCubicFilter) ? g_intraCubicFilter[deltaFract] : g_intraGaussFilter[deltaFract];//根据宽是否小于等于8,判断使用立方体滤波器还是高斯滤波器,获取滤波系数
Int refMainIndex = deltaInt + 1;
for( Int x = 0; x < width; x++, refMainIndex++ )
{
p[1] = refMain[refMainIndex];//获取refMain中对应位置的参考像素值
p[2] = refMain[refMainIndex + 1];
p[0] = x == 0 ? p[1] : refMain[refMainIndex - 1];//边界处理
p[3] = x == (width - 1) ? p[2] : refMain[refMainIndex + 2];//边界处理
pDstBuf[y*dstStride + x] = (Pel)((f[0] * p[0] + f[1] * p[1] + f[2] * p[2] + f[3] * p[3] + 128) >> 8);//四抽头滤波操作
if( useCubicFilter ) // only cubic filter has negative coefficients and requires clipping
{
pDstBuf[y*dstStride + x] = ClipPel( pDstBuf[y*dstStride + x], clpRng );
}
}
}
else//采用线性插值
#endif
{
// Do linear filtering
const Pel *pRM = refMain + deltaInt + 1;
Int lastRefMainPel = *pRM++;//左侧相邻整像素
for( Int x = 0; x < width; pRM++, x++ )
{
Int thisRefMainPel = *pRM;//右侧相邻整像素
//计算预测值
pDsty[x + 0] = ( Pel ) ( ( ( 32 - deltaFract )*lastRefMainPel + deltaFract*thisRefMainPel + 16 ) >> 5 );
lastRefMainPel = thisRefMainPel;//左侧指向右侧,再循环时右侧指向下一个
}
}
}
else//无需插值
{
// Just copy the integer samples
for( Int x = 0; x < width; x++ )
{
pDsty[x] = refMain[x + deltaInt + 1];
}
}
}
}
// Flip the block if this is the horizontal mode,水平模式下旋转当前块
if( !bIsModeVer )
{
for( Int y = 0; y < height; y++ )
{
for( Int x = 0; x < width; x++ )
{
pDst.at( y, x ) = pDstBuf[x];
}
pDstBuf += dstStride;
}
}
#if JEM_TOOLS && JEM_USE_INTRA_BOUNDARY //JEM_USE_INTRA_BOUNDARY 默认 0,不过因为是边界值平滑滤波这一新技术,所以放到这里,具体参见上一篇文章
if( sps.getSpsNext().getUseIntraBoundaryFilter() && enableBoundaryFilter && isLuma( channelType ) && width > 2 && height > 2 )
{
if( dirMode == VDIA_IDX )
{
xIntraPredFilteringMode34( pSrc, pDst );
}
else if( dirMode == 2 )
{
xIntraPredFilteringMode02( pSrc, pDst );
}
else if( ( dirMode <= 10 && dirMode > 2 ) || ( dirMode >= ( VDIA_IDX - 8 ) && dirMode < VDIA_IDX ) )
{
xIntraPredFilteringModeDGL( pSrc, pDst, dirMode );
}
}
#endif
}