predIntraMip函數是進行MIP預測的入口函數,主要功能是進行矩陣乘法運算,再通過上採樣獲得整個塊的預測像素,實現步驟如下圖所示:
predIntraMip代碼及註釋如下
#if JVET_R0350_MIP_CHROMA_444_SINGLETREE
void MatrixIntraPrediction::predBlock(int *const result, const int modeIdx, const bool transpose, const int bitDepth,
const ComponentID compId)
{
CHECK(m_component != compId, "Boundary has not been prepared for this component.");
#else
void MatrixIntraPrediction::predBlock(int* const result, const int modeIdx, const bool transpose, const int bitDepth)
{
#endif
//是否需要上採樣
const bool needUpsampling = ( m_upsmpFactorHor > 1 ) || ( m_upsmpFactorVer > 1 );
//根據mipSizeId獲取MIP矩陣
const uint8_t* matrix = getMatrixData(modeIdx);
//存儲縮減預測像素
static_vector<int, MIP_MAX_REDUCED_OUTPUT_SAMPLES> bufReducedPred( m_reducedPredSize * m_reducedPredSize );
int* const reducedPred = needUpsampling ? bufReducedPred.data() : result;
//根據是否轉置獲得縮減邊界像素向量
const int* const reducedBoundary = transpose ? m_reducedBoundaryTransposed.data() : m_reducedBoundary.data();
//進行矩陣乘法計算縮減預測像素
computeReducedPred(reducedPred, reducedBoundary, matrix, transpose, bitDepth);
//如果需要進行上採樣
if( needUpsampling )
{
//上採樣函數,利用縮減預測像素獲得整個塊的預測像素
predictionUpsampling( result, reducedPred );
}
}
塊尺寸 |
下采樣後的邊界長度 m_reducedBdrySize |
矩陣乘法輸出邊界長度 m_reducedPredSize |
MIP矩陣數目 | MIP矩陣維度 | |
mipSizeId = 0 | 4x4 | 2 | 4 | 16 | 16x4 |
mipSizeId = 1 | 4xN、Nx4、8x8 | 4 | 4 | 8 | 16x8 |
mipSizeId = 2 | 其餘塊 | 4 | 8 | 6 | 64x8 |
1、獲取MIP矩陣
MIP矩陣的獲取和mipSizeId有關,如上表所示,不同mipSizeId對應的MIP矩陣數目、維度不同
2、計算預測像素
computeReducedPred函數是通過進行矩陣乘法計算下采樣後的預測像素,如下圖所示。mode k表示MIP矩陣的模式號,表示模式k對應的MIP矩陣,通過調用getMatrixData函數獲得。對應於偏移向量,計算方法如下,其中p[i]表示矩陣乘法輸入向量,inSize表示 2 * m_reducedBdrySize.
MIP矩陣乘法公式如下所示,其中mWeight表示MIP矩陣,p表示MIP矩陣乘法輸入向量。
void MatrixIntraPrediction::computeReducedPred( int*const result, const int* const input,
const uint8_t* matrix,
const bool transpose, const int bitDepth )
{
const int inputSize = 2 * m_reducedBdrySize; // 4 or 8
// use local buffer for transposed result 對轉置結果使用本地緩衝區
static_vector<int, MIP_MAX_REDUCED_OUTPUT_SAMPLES> resBufTransposed( m_reducedPredSize * m_reducedPredSize );
int*const resPtr = (transpose) ? resBufTransposed.data() : result;
int sum = 0;
for( int i = 0; i < inputSize; i++ ) { sum += input[i]; }
// MIP_SHIFT_MATRIX 移位因子sW固定爲6
// MIP_OFFSET_MATRIX 偏移因子fO固定爲32
// 計算偏移量Bias
const int offset = (1 << (MIP_SHIFT_MATRIX - 1)) - MIP_OFFSET_MATRIX * sum;
CHECK( inputSize != 4 * (inputSize >> 2), "Error, input size not divisible by four" );
const uint8_t *weight = matrix; //權重矩陣
// 獲取input[0],即m_reducedBoundary[0]
const int inputOffset = transpose ? m_inputOffsetTransp : m_inputOffset;
const bool redSize = (m_sizeId == 2);
int posRes = 0;
for( int y = 0; y < m_reducedPredSize; y++ )
{
for( int x = 0; x < m_reducedPredSize; x++ )
{
if( redSize ) weight -= 1;
int tmp0 = redSize ? 0 : (input[0] * weight[0]);
int tmp1 = input[1] * weight[1];
int tmp2 = input[2] * weight[2];
int tmp3 = input[3] * weight[3];
for (int i = 4; i < inputSize; i += 4)
{
tmp0 += input[i] * weight[i];
tmp1 += input[i + 1] * weight[i + 1];
tmp2 += input[i + 2] * weight[i + 2];
tmp3 += input[i + 3] * weight[i + 3];
}
//對矩陣乘法輸出採樣鉗位
resPtr[posRes++] = ClipBD<int>(((tmp0 + tmp1 + tmp2 + tmp3 + offset) >> MIP_SHIFT_MATRIX) + inputOffset, bitDepth);
weight += inputSize;
}
}
if( transpose )
{
// 將矩陣乘法結果進行轉置
for( int y = 0; y < m_reducedPredSize; y++ )
{
for( int x = 0; x < m_reducedPredSize; x++ )
{
result[ y * m_reducedPredSize + x ] = resPtr[ x * m_reducedPredSize + y ];
}
}
}
}
3、上採樣
插值順序是固定的,如果需要水平插值的話,則先進行水平插值,後垂直插值,如下圖所示(以8x8的塊爲例)。上採樣的過程其實就是一種線性加權的過程,在相應位置處通過對參考像素和預測像素線性加權,即可求得空白處的像素值(權重和位置有關)。
相關代碼及註釋如下所示:
// dst 上採樣結果
// src 矩陣乘法輸出結果
void MatrixIntraPrediction::predictionUpsampling( int* const dst, const int* const src ) const
{
const int* verSrc = src;
SizeType verSrcStep = m_blockSize.width;
//插值過程固定,先水平後垂直
if( m_upsmpFactorHor > 1 ) //如果需要進行水平插值
{
int* const horDst = dst + (m_upsmpFactorVer - 1) * m_blockSize.width;
verSrc = horDst;
verSrcStep *= m_upsmpFactorVer;
predictionUpsampling1D( horDst, src, m_refSamplesLeft.data(),
m_reducedPredSize, m_reducedPredSize,
1, m_reducedPredSize, 1, verSrcStep,
m_upsmpFactorVer, m_upsmpFactorHor );
}
if( m_upsmpFactorVer > 1 )
{
predictionUpsampling1D( dst, verSrc, m_refSamplesTop.data(),
m_reducedPredSize, m_blockSize.width,
verSrcStep, 1, m_blockSize.width, 1,
1, m_upsmpFactorVer );
}
}
插值代碼具體實現(以8x8爲例):
水平插值:代碼中水平插值的順序是從上往下,即插值順序爲第2、4、6、8行
垂直插值:代碼中垂直插值的順序是從左到右,即插值順序爲第1 2 3 4 5 6 7 8列。
代碼中,是在插值的過程中,將預測像素放到結果塊中的。
/*
- dst:上採樣結果
- srt:矩陣乘法輸入結果或者水平插值結果
- bndry:邊界參考像素
- bndryStep:插值時參考邊界像素的間隔(有時候不一定會參考全部的邊界像素)
- srcSizeUpsmpDim: m_reducedPredSize(4/8)
- srcSizeOrthDim:當前插值方向需要插值的次數
- upsmpFactor:採樣因子
*/
void MatrixIntraPrediction::predictionUpsampling1D(int* const dst, const int* const src, const int* const bndry,
const SizeType srcSizeUpsmpDim, const SizeType srcSizeOrthDim,
const SizeType srcStep, const SizeType srcStride,
const SizeType dstStep, const SizeType dstStride,
const SizeType bndryStep,
const unsigned int upsmpFactor )
{
const int log2UpsmpFactor = floorLog2( upsmpFactor );
CHECKD( upsmpFactor <= 1, "Upsampling factor must be at least 2." );
const int roundingOffset = 1 << (log2UpsmpFactor - 1);
SizeType idxOrthDim = 0;
const int* srcLine = src;//矩陣乘法輸出或水平插值結果
int* dstLine = dst;
const int* bndryLine = bndry + bndryStep - 1;//邊界參考像素
while( idxOrthDim < srcSizeOrthDim )
{
SizeType idxUpsmpDim = 0;
const int* before = bndryLine;//前一個參考像素
const int* behind = srcLine;//後一個參考像素
int* currDst = dstLine;
while( idxUpsmpDim < srcSizeUpsmpDim )
{
SizeType pos = 1;//控制當前插值的位置,將插值結果和矩陣乘法結果放到各自相應的位置上
int scaledBefore = ( *before ) << log2UpsmpFactor;
int scaledBehind = 0;
while( pos <= upsmpFactor )
{
//通過+-操作可以控制插值時參考像素的權重
scaledBefore -= *before;
scaledBehind += *behind;
*currDst = (scaledBefore + scaledBehind + roundingOffset) >> log2UpsmpFactor;
pos++;
currDst += dstStep;
}
idxUpsmpDim++;
before = behind;//移動前一個參考像素
behind += srcStep;//移動後一個參考像素
}
idxOrthDim++;
srcLine += srcStride;
dstLine += dstStride;
bndryLine += bndryStep;
}
}