VVC程式碼 BMS 幀內預測學習之六:Planar、DC及角度模式下預測值的計算
阿新 • • 發佈:2018-12-11
1、Planar模式,函式xPredIntraPlanar()
:
預測畫素是水平、垂直兩個方向上4個參考畫素的平均值。
left, top為預測畫素正左,正上方參考畫素值;
right = leftColumn[height]- left, bottom = topRow[width] - top;
最終預測值:
pred = (( left << log2W + right ) << log2H
+ ( top << log2H + bottom ) << log2W + W*H) //W * H = offset
>> ( 1 + log2W + log2H)
2、DC模式,函式xPredIntraDC()
:
DC模式下,首先通過函式xGetPredValDc()
獲取平均值dcval,為上方畫素與左側畫素和的平均值(不包括左上角畫素),將當前塊的全部畫素賦值為dcval(在BMS中,HEVC中預設的亮度預測塊DC模式下的相關加權處理預設關閉)。
3、角度模式,函式xPredIntraAng()
:
注:
- a、角度模式下,為了便於後續迴圈的統一書寫(寫為width),統一將水平類模式下的寬/高值進行交換,即將其旋轉90度(此說法便於理解)進行預測,預測完成後,再將其旋轉歸位。
- b、在預測實現的過程中,參考畫素是儲存在一維陣列中的。
對於65種角度模式而言,其對應的偏移值表如下:
模式號 | 偏移值 | 模式號 | 偏移值 | 模式號 | 偏移值 | 模式號 | 偏移值 | 模式號 | 偏移值 |
---|---|---|---|---|---|---|---|---|---|
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 |
//注:本文的函式中已經將預設關閉的巨集相關內容刪除。
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中
}
}
//************************** 偏移值 > 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)
{
const Int deltaInt = deltaPos >> 5;//計算當前畫素對應參考畫素在ref中的位置
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
}