C/C++教程

H.266/VVC技术学习之帧内模式编码

本文主要是介绍H.266/VVC技术学习之帧内模式编码,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

在HEVC中,支持33种角度模式、DC模式和Planar模式,为了减少编码比特,使用长度为3的最可能模式列表。在VVC中,引入了ISP模式、MRL模式、MIP模式等,帧内模式编码时需要先对这些模式的flag进行编码。VVC将角度模式扩展到了65种角度模式,因此,将MPM列表相应地扩展到了长度6。这里,Planar模式总是在MPM列表中,且有单独的flag表示。

1. 帧内亮度模式编码

帧内亮度模式编码流程如上图所示,其中包括MIP模式、MRL模式、ISP模式以及MPM相关的语法元素。

首先编码mip_flag,如果mip_flag为1则进一步编码mip_transpose_flag和mip_mode,mip_transpose_flag表示是否需要将MIP矩阵转置,mip_mode表示所选择的MIP模式,编码完mip_transpose_flag和mip_mode之后就会退出,不再对其余模式进行编码。

如果mip_flag为0,则下一步编码mrl_idx,则多参考行的索引,如果mrl_idx大于0,则下一步需要编码MPM_index(使用多参考行时,帧内预测模式只能是MPM列表中的除Planar以外的模式)。如果mrl_idx为0,表示不使用多参考行模式,下一步继续编码isp_flag,isp_flag表示是否使用ISP模式,如果isp_flag为1,则需要进一步编码isp_split_flag,表示ISP是垂直划分还是水平划分方式。之后,需要编码mpm_flag,当mpm_flag为1时,表示预测模式使用的是MPM列表中的模式,需要进一步编码planar_flag,如果planar_flag为1表示应用Planar模式,否则需要编码mpm_idx以确定应用MPM中的五个非Planar中的哪一个模式;如果mpm_flag为0,则需要对非MPM模式进行编码。

MPM模式列表是通过相邻PU构建的,具体构建过程参考:H.266/VVC代码学习:MPM列表建立(getIntraMPMs函数)

各语法元素的编码方式如下表所示:(其中FL表示定长码,TB表示截断二进制码,TR表示截断莱斯码,各编码方式的细节参考:H.266/VVC熵编码之二进制化)

intra_mip_flag

FL

cMax = 1

intra_mip_transposed_flag[ ][ ]

FL

cMax = 1

intra_mip_mode[ ][ ]

TB

cMax = ( cbWidth  = =  4  &&  cbHeight  = =  4 ) ? 15 : ( ( ( cbWidth  = =  4  | |  cbHeight  = =  4 )  | | ( cbWidth  = =  8  &&  cbHeight  = =  8 ) ) ? 7 : 5 )

intra_luma_ref_idx

TR

cMax = 2, cRiceParam = 0

intra_subpartitions_mode_flag

FL

cMax = 1

intra_subpartitions_split_flag

FL

cMax = 1

intra_luma_mpm_flag[ ][ ]

FL

cMax = 1

intra_luma_not_planar_flag[ ][ ]

FL

cMax = 1

intra_luma_mpm_idx[ ][ ]

TR

cMax = 4, cRiceParam = 0

intra_luma_mpm_remainder[ ][ ]

TB

cMax = 60

void CABACWriter::intra_luma_pred_modes( const CodingUnit& cu )
{
  if( !cu.Y().valid() )
  {
    return;
  }

  if( cu.bdpcmMode )
  {
    cu.firstPU->intraDir[0] = cu.bdpcmMode == 2? VER_IDX : HOR_IDX;
    return;
  }

  mip_flag(cu);  //编码MIP标志
  if (cu.mipFlag)
  {
    // 如果当前模式是MIP,则编码MIP转置标志和MIP模式号
    mip_pred_modes(cu);
    return;
  }
  extend_ref_line( cu ); // 编码MRL索引

  
  isp_mode( cu ); // 编码ISP模式

  const int numMPMs   = NUM_MOST_PROBABLE_MODES; // MPM模式数
  const int numBlocks = CU::getNumPUs( cu );
  unsigned  mpm_preds   [4][numMPMs]; // 4表示CU中最多包含四个PU,表示每个PU中有numMPMs个MPM模式
  unsigned  mpm_idxs    [4];
  unsigned  ipred_modes [4];

  const PredictionUnit* pu = cu.firstPU;

  // prev_intra_luma_pred_flag 遍历CU中全部PU
  for( int k = 0; k < numBlocks; k++ )
  {
    unsigned*  mpm_pred   = mpm_preds[k];
    unsigned&  mpm_idx    = mpm_idxs[k];
    unsigned&  ipred_mode = ipred_modes[k];

    PU::getIntraMPMs( *pu, mpm_pred ); // 获取MPM列表

    ipred_mode = pu->intraDir[0]; // 当前PU的模式
    mpm_idx    = numMPMs; // 将mpm_idx初始化为6
    // 遍历MPM列表,如果当前模式在MPM列表中,则将对应的索引赋给mpm_idx
    for( unsigned idx = 0; idx < numMPMs; idx++ )
    {
      if( ipred_mode == mpm_pred[idx] )
      {
        mpm_idx = idx;
        break;
      }
    }
    if ( pu->multiRefIdx )
    {
      CHECK(mpm_idx >= numMPMs, "use of non-MPM");
    }
    else
    {
      m_BinEncoder.encodeBin(mpm_idx < numMPMs, Ctx::IntraLumaMpmFlag()); // 编码MPM标志,如果mpm_idx小于6即表示当前模式在MPM列表中,编码其在MPM中的索引即可
    }

    pu = pu->next;
  }

  pu = cu.firstPU;

  // mpm_idx / rem_intra_luma_pred_mode
  for( int k = 0; k < numBlocks; k++ ) // 遍历全部PU
  {
    const unsigned& mpm_idx = mpm_idxs[k];// 每个PU对应的mpm_idx
    if( mpm_idx < numMPMs ) // 如果mpm_idx小于6,说明该PU的预测模式在MP列表中,仅编码MPM索引即可
    {
      {
        unsigned ctx = (pu->cu->ispMode == NOT_INTRA_SUBPARTITIONS ? 1 : 0);
        if (pu->multiRefIdx == 0) // 当参考行索引大于0时,不可能是Planar模式
          m_BinEncoder.encodeBin(mpm_idx > 0, Ctx::IntraLumaPlanarFlag(ctx)); // Planar flag
        if( mpm_idx )
        {
          m_BinEncoder.encodeBinEP( mpm_idx > 1 );
        }
        if (mpm_idx > 1)
        {
          m_BinEncoder.encodeBinEP(mpm_idx > 2);
        }
        if (mpm_idx > 2)
        {
          m_BinEncoder.encodeBinEP(mpm_idx > 3);
        }
        if (mpm_idx > 3)
        {
          m_BinEncoder.encodeBinEP(mpm_idx > 4);
        }
      }
    }
    else
    {
      unsigned* mpm_pred   = mpm_preds[k]; //PU的MPM模式
      unsigned  ipred_mode = ipred_modes[k]; //PU的预测模式

      // sorting of MPMs
      std::sort( mpm_pred, mpm_pred + numMPMs ); // 排序

      {
        for (int idx = numMPMs - 1; idx >= 0; idx--)
        {
          if (ipred_mode > mpm_pred[idx])
          {
            ipred_mode--;
          }
        }
        CHECK(ipred_mode >= 64, "Incorrect mode");
        // 截断二进制编码剩余的模式
        xWriteTruncBinCode(ipred_mode, NUM_LUMA_MODE - NUM_MOST_PROBABLE_MODES);  // Remaining mode is truncated binary coded
      }
    }

    DTRACE( g_trace_ctx, D_SYNTAX, "intra_luma_pred_modes() idx=%d pos=(%d,%d) mode=%d\n", k, pu->lumaPos().x, pu->lumaPos().y, pu->intraDir[0] );
    pu = pu->next;
  }
}

2. 帧内色度模式编码

VVC中总共包括8种帧内色度模式:Planar、垂直、水平、DC、DM以及CCLM的三种模式。

在帧内色度模式编码时,首先编码一位bit(cclm_mode_flag)来表示当前模式是三种CCLM模式还是非CCLM模式。如果当前模式是CCLM模式,则需要进一步编码当前模式CCLM模式之中的哪一个模式。如果当前模式是非CCLM模式,则编码索引为0-4的intra_chroma_pred_mode,当当前模式是DM模式时,则仅需要编码一位bit来指示DM模式;否则,使用三位定长码指示其是四种非DM模式中的哪一种。

各语法元素的编码方式如下所示:

cclm_mode_flag

FL

cMax = 1

cclm_mode_idx

TR

cMax = 2, cRiceParam = 0

intra_chroma_pred_mode

9.3.3.8

-

Value of intra_chroma_pred_mode

Bin string

0

100

1

101

2

110

3

111

4

0

可以得到帧内色度各预测模式的编码比特如下表所示:

色度模式编号

码流

4(DM模式)

00

0(Planar模式)

0100

1(垂直模式)

0101

2(水平模式)

0110

3(DC模式)

0111

5(LM模式)

10

6(LM_L模式)

110

7(LM_T模式)

111

帧内色度预测模式编码相关代码:

void CABACWriter::intra_chroma_pred_mode(const PredictionUnit& pu)
{
  const unsigned intraDir = pu.intraDir[1];
  if (pu.cu->colorTransform) // ACT模式下使用DM模式,不对色度模式编码
  {
    CHECK(pu.intraDir[CHANNEL_TYPE_CHROMA] != DM_CHROMA_IDX, "chroma should use DM for adaptive color transform");
    return;
  }
  if (pu.cs->sps->getUseLMChroma() && pu.cu->checkCCLMAllowed())
  {
    m_BinEncoder.encodeBin(PU::isLMCMode(intraDir) ? 1 : 0, Ctx::CclmModeFlag(0)); // 如果是CCLM模式,编码1,否则编码0
    if (PU::isLMCMode(intraDir))
    {
      intra_chroma_lmc_mode(pu); // CCLM模式编码
      return;
    }
  }

  const bool     isDerivedMode = intraDir == DM_CHROMA_IDX;
  m_BinEncoder.encodeBin(isDerivedMode ? 0 : 1, Ctx::IntraChromaPredMode(0)); //DM模式
  if (isDerivedMode)
  {
    return;
  }

  // chroma candidate index
  unsigned chromaCandModes[NUM_CHROMA_MODE];
  PU::getIntraChromaCandModes(pu, chromaCandModes); // 获取色度候选模式列表

  int candId = 0;
  for (; candId < NUM_CHROMA_MODE; candId++) // 遍历模式列表,找到当前的模式
  {
    if (intraDir == chromaCandModes[candId])
    {
      break;
    }
  }

  CHECK(candId >= NUM_CHROMA_MODE, "Chroma prediction mode index out of bounds");
  CHECK(chromaCandModes[candId] == DM_CHROMA_IDX, "The intra dir cannot be DM_CHROMA for this path");
  {
    m_BinEncoder.encodeBinsEP(candId, 2); // 使用两位bit编码
  }
}

CCLM模式相关语法元素的编码 :

void CABACWriter::intra_chroma_lmc_mode(const PredictionUnit& pu)
{
  const unsigned intraDir = pu.intraDir[1];
  int lmModeList[10];
  PU::getLMSymbolList(pu, lmModeList); // LM模式列表LM LM_L LM_T
  int symbol = -1;
  for (int k = 0; k < LM_SYMBOL_NUM; k++)
  {
    if (lmModeList[k] == intraDir)
    {
      symbol = k;
      break;
    }
  }
  CHECK(symbol < 0, "invalid symbol found");


  m_BinEncoder.encodeBin(symbol == 0 ? 0 : 1, Ctx::CclmModeIdx(0)); // 编码一位表示是否是LM模式
  if (symbol > 0)
  {
    CHECK(symbol > 2, "invalid symbol for MMLM");
    unsigned int symbol_minus_1 = symbol - 1;
    m_BinEncoder.encodeBinEP(symbol_minus_1);
  }
}

这篇关于H.266/VVC技术学习之帧内模式编码的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!