H.266/VVC代碼學習:MIP技術相關代碼之predIntraMip函數

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矩陣的模式號,A_{k}表示模式k對應的MIP矩陣,通過調用getMatrixData函數獲得。b_{k}對應於偏移向量,計算方法如下,其中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;
  }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章