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 角度模式預測值計算的理論:
假設預測塊上一個預測位置的預測值爲,設偏移值爲(個人理解:偏移值最大應該爲正負1,在函數處理中以32爲單位進行),當前點在預測過程中實際投影到參考像素的座標,相對於當前點座標,位移爲(假設投影爲x方向)。
滿足相似三角形的特性:
所以可以知道:
( 爲函數中後續循環的deltaInt )
( 去掉大於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 注意事項:
- a、角度模式下,爲了便於後續循環的統一書寫(寫爲width),統一將水平類模式下的寬/高值進行交換,即將其旋轉90度(此說法便於理解)進行預測,預測完成後,再將其旋轉歸位。
- b、在預測實現的過程中,參考像素是存儲在一維數組refMain中的。
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
}