H.266/VVC-VTM代码学习23-编码块RDO选择模式(Intra与Inter)initCULevel()

H.266/VVC专栏传送

上一篇:H.266/VVC-VTM代码学习22-partitioner类判断划分模式是否可用函数partitioner.canSplit
下一篇:

前言

VTM是H.266/VVC视频编码标准的参考软件,研究VTM代码给研究人员解释了VVC编码标准的详细标准规范与细节。

本文是笔者对VTM代码的一点学习记录,成文于笔者刚开始接触VVC期间,期间很多概念和理论框架还很不成熟,若文中存在错误欢迎批评指正,也欢迎广大视频编码学习者沟通交流、共同进步。

VTM代码的下载及编译请参考博文:
【视频编码学习】H.266/VVC参考软件VTM配置运行(VTM-6.0版本)

一、简介

本文介绍 VTM10.0 进行 RDO 过程时,选择块模式(Intra 或 Inter)部分的代码,是 initCULevel 函数中将 Intra 及 Inter 根据具体情况加入 RDO 列表的代码。

initCULevel 函数的完整注释详解请参考:
H.266/VVC-VTM代码学习19-CU层确定测试模式函数initCULevel
xCompressCU 函数的完整注释详解请参考:
H.266/VVC-VTM代码学习20-CU层进行RDO函数xCompressCU

二、代码详解

1. 将 Intra/Inter 加入 RDO 列表

initCULevel 函数(xCompressCU 函数调用 initCULevel)代码片段:

① Intra
    // add intra modes
    // 若尝试 Intra RDO
    if( tryIntraRdo )
    {
    if (cs.slice->getSPS()->getPLTMode() && (partitioner.treeType != TREE_D || cs.slice->isIntra() || (cs.area.lwidth() == 4 && cs.area.lheight() == 4)) && getPltEnc())
    {
      // 加入 palette mode(利用CU内部的样本只是有少数几种典型的颜色组成的特性进行编码) RDO
      m_ComprCUCtxList.back().testModes.push_back({ ETM_PALETTE, ETO_STANDARD, qp });
    }
    // 加入 Intra RDO
    m_ComprCUCtxList.back().testModes.push_back( { ETM_INTRA, ETO_STANDARD, qp } );
    if (cs.slice->getSPS()->getPLTMode() && partitioner.treeType == TREE_D && !cs.slice->isIntra() && !(cs.area.lwidth() == 4 && cs.area.lheight() == 4) && getPltEnc())
    {
      // 加入 palette mode RDO
      m_ComprCUCtxList.back().testModes.push_back({ ETM_PALETTE,  ETO_STANDARD, qp });
    }
    }

由这部分代码可见,将 Intra 加入 RDO 列表的条件是 tryIntraRdo 为 True。

② Inter
  if ( !m_slice->isIntra() && !( cs.area.lwidth() == 4 && cs.area.lheight() == 4 ) && tryInterRdo )
  {
    for( int qpLoop = maxQP; qpLoop >= minQP; qpLoop-- )
    {
      const int  qp       = std::max( qpLoop, lowestQP );
      if (m_pcEncCfg->getIMV())
      {
        m_ComprCUCtxList.back().testModes.push_back({ ETM_INTER_ME,  EncTestModeOpts( 4 << ETO_IMV_SHIFT ), qp });
      }
      if( m_pcEncCfg->getIMV() || m_pcEncCfg->getUseAffineAmvr() )
      {
        int imv = m_pcEncCfg->getIMV4PelFast() ? 3 : 2;
        m_ComprCUCtxList.back().testModes.push_back( { ETM_INTER_ME, EncTestModeOpts( imv << ETO_IMV_SHIFT ), qp } );
        m_ComprCUCtxList.back().testModes.push_back( { ETM_INTER_ME, EncTestModeOpts( 1 << ETO_IMV_SHIFT ), qp } );
      }
      // add inter modes
      if( m_pcEncCfg->getUseEarlySkipDetection() )
      {
        if( cs.sps->getUseGeo() && cs.slice->isInterB() )
        {
          m_ComprCUCtxList.back().testModes.push_back( { ETM_MERGE_GEO, ETO_STANDARD, qp } );
        }
        m_ComprCUCtxList.back().testModes.push_back( { ETM_MERGE_SKIP,  ETO_STANDARD, qp } );
        if (cs.sps->getUseAffine() || cs.sps->getSbTMVPEnabledFlag())
        {
          m_ComprCUCtxList.back().testModes.push_back( { ETM_AFFINE,    ETO_STANDARD, qp } );
        }
        m_ComprCUCtxList.back().testModes.push_back( { ETM_INTER_ME,    ETO_STANDARD, qp } );
      }
      else
      {
        m_ComprCUCtxList.back().testModes.push_back( { ETM_INTER_ME,    ETO_STANDARD, qp } );
        if( cs.sps->getUseGeo() && cs.slice->isInterB() )
        {
          m_ComprCUCtxList.back().testModes.push_back( { ETM_MERGE_GEO, ETO_STANDARD, qp } );
        }
        m_ComprCUCtxList.back().testModes.push_back( { ETM_MERGE_SKIP,  ETO_STANDARD, qp } );
        if (cs.sps->getUseAffine() || cs.sps->getSbTMVPEnabledFlag())
        {
          m_ComprCUCtxList.back().testModes.push_back( { ETM_AFFINE,    ETO_STANDARD, qp } );
        }
      }
      // 加入 Hash_Inter RDO
      if (m_pcEncCfg->getUseHashME())
      {
        int minSize = min(cs.area.lwidth(), cs.area.lheight());
        if (minSize < 128 && minSize >= 4)
        {
          m_ComprCUCtxList.back().testModes.push_back({ ETM_HASH_INTER, ETO_STANDARD, qp });
        }
      }
    }
  }

由这部分代码可见,将 Inter 加入 RDO 列表的条件是当前帧不为 intra, tryIntraRdo 为 True 且当前区域宽高不同时为4 且 tryInterRdo 为 True。

2. tryIntraRdo 和 tryInterRdo 两变量的确定

下面对该变量进行具体分析,以下仍是 initCULevel 函数代码片段。

  // 是否尝试 Intra RDO
  bool tryIntraRdo = true;
  // 是否尝试 Inter RDO
  bool tryInterRdo = true;
  // 是否尝试 IBC(Intra block copy) RDO
  bool tryIBCRdo   = true;
  // 若指定 isConsIntra,则不尝试 Inter RDO
  if( partitioner.isConsIntra() )
  {
    tryInterRdo = false;
  }
  // 若指定 isConsInter,则不尝试 IBC RDO
  else if( partitioner.isConsInter() )
  {
    tryIntraRdo = tryIBCRdo = false;
  }

由这部分代码可见:

  • tryIntraRdo 和 tryInterRdo 初始化为 True
  • 仅当 partitioner.isConsInter() 为 True 时,tryIntraRdo 为 false
  • 仅当 partitioner.isConsIntra() 为 True 时,tryInterRdo 为 false

而 partitioner.isConsIntra() 和 partitioner.isConsInter() 函数直接获取 partitioner.modeType,这一变量的值由 xCompressCU 函数中对分块模式的处理部分代码确定,以下是这部分代码:

      // 获得当前信号模式值(LDT_MODE_TYPE_INHERIT、LDT_MODE_TYPE_INFER、LDT_MODE_TYPE_SIGNAL)
      int signalModeConsVal = tempCS->signalModeCons( getPartSplit( currTestMode ), partitioner, modeTypeParent );
      // 若当前信号模式值为 LDT_MODE_TYPE_SIGNAL(Need to signal mode_constraint_flag, and the modeType of the region is determined by the flag),则 RDO 轮次为2,否则为1
      int numRoundRdo = signalModeConsVal == LDT_MODE_TYPE_SIGNAL ? 2 : 1;
      bool skipInterPass = false;
      // 遍历 RDO 轮次
      for( int i = 0; i < numRoundRdo; i++ )
      {
        //change cons modes
        // 若当前信号模式值为 LDT_MODE_TYPE_SIGNAL
        // 则当前测试模式类型在第一轮设置为 INTER,在第二轮设置为 INTRA
        if( signalModeConsVal == LDT_MODE_TYPE_SIGNAL )
        {
          CHECK( numRoundRdo != 2, "numRoundRdo shall be 2 - [LDT_MODE_TYPE_SIGNAL]" );
          tempCS->modeType = partitioner.modeType = (i == 0) ? MODE_TYPE_INTER : MODE_TYPE_INTRA;
        }
        // 若当前信号模式值为 LDT_MODE_TYPE_INFER
        // 则当前测试模式类型为 INTRA
        else if( signalModeConsVal == LDT_MODE_TYPE_INFER )
        {
          CHECK( numRoundRdo != 1, "numRoundRdo shall be 1 - [LDT_MODE_TYPE_INFER]" );
          tempCS->modeType = partitioner.modeType = MODE_TYPE_INTRA;
        }
        // 若当前信号模式值为 LDT_MODE_TYPE_INHERIT
        // 则当前测试模式类型为父结点模式类型
        else if( signalModeConsVal == LDT_MODE_TYPE_INHERIT )
        {
          CHECK( numRoundRdo != 1, "numRoundRdo shall be 1 - [LDT_MODE_TYPE_INHERIT]" );
          tempCS->modeType = partitioner.modeType = modeTypeParent;
        }
        ...

由此代码可见,当前块是否测试 Intra 即在当前块的父结点测试 split 模式时决定 partitioner.modeType,若该变量设置为 True 时,子块可进行 Intra RDO,否则不进行 Intra RDO。

由上部分代码可见,partitioner.modeType 的取值有以下几种情况:

  • signalModeConsVal == LDT_MODE_TYPE_SIGNAL 时:子块 RDO 先测试 Inter 后测试 Intra;
  • signalModeConsVal == LDT_MODE_TYPE_INFER 时:子块 RDO 仅测试 Intra;
  • signalModeConsVal == LDT_MODE_TYPE_INHERIT 时:子块 RDO 测试模式(intra or inter)与父结点相同。

signalModeConsVal 的取值则在上部分代码中调用 signalModeCons 函数确定,以下是确定该取值的具体代码:

const int CodingStructure::signalModeCons( const PartSplit split, Partitioner &partitioner, const ModeType modeTypeParent ) const
{
  // 若当前为 DualTree,或父结点模式类型不为inter与intra均尝试,或当前块色度格式为444,或当前块色度格式为400
  // 则子块 RDO 时模式继承父结点
  if (CS::isDualITree(*this) || modeTypeParent != MODE_TYPE_ALL || partitioner.currArea().chromaFormat == CHROMA_444 || partitioner.currArea().chromaFormat == CHROMA_400 )
  {
    return LDT_MODE_TYPE_INHERIT;
  }
  int minLumaArea = partitioner.currArea().lumaSize().area();
  // 若划分模式为 QT 或 TH 或 TV
  // 则最小亮度区域为当前区域/4
  if (split == CU_QUAD_SPLIT || split == CU_TRIH_SPLIT || split == CU_TRIV_SPLIT) // the area is split into 3 or 4 parts
  {
    minLumaArea = minLumaArea >> 2;
  }
  // 若划分模式为 BV 或 BH
  // 则最小亮度区域为当前区域/2
  else if (split == CU_VERT_SPLIT || split == CU_HORZ_SPLIT) // the area is split into 2 parts
  {
    minLumaArea = minLumaArea >> 1;
  }
  // 最小色度块 = 最小亮度区域 >> scale
  int minChromaBlock = minLumaArea >> (getChannelTypeScaleX(CHANNEL_TYPE_CHROMA, partitioner.currArea().chromaFormat) + getChannelTypeScaleY(CHANNEL_TYPE_CHROMA, partitioner.currArea().chromaFormat));
  // (当前色度区域宽度为4 且 划分模式为 BV) 或 (当前色度区域宽度为8 且 划分模式为 TV)时,色度块尺寸为 2*N
  bool is2xNChroma = (partitioner.currArea().chromaSize().width == 4 && split == CU_VERT_SPLIT) || (partitioner.currArea().chromaSize().width == 8 && split == CU_TRIV_SPLIT);
  // 若最小色度块 >= 16 且 色度块尺寸不是 2*N 时,子块 RDO 模式继承父结点,否则在最小亮度块<32 或 当前帧为 INTRA 时子块 RDO 模式为 Intra,否则先子块先测试 Inter 再测试 Intra
  return minChromaBlock >= 16 && !is2xNChroma ? LDT_MODE_TYPE_INHERIT : ((minLumaArea < 32) || slice->isIntra()) ? LDT_MODE_TYPE_INFER : LDT_MODE_TYPE_SIGNAL;
}
}

由以上代码可以得到以下结论:

  1. 为 DualTree,或父结点模式类型不为inter与intra均尝试,或当前块色度格式为444,或当前块色度格式为400 时:子块 RDO 的模式(intra/inter)继承父结点
  2. 划分后最小色度块 >= 16 且 色度块尺寸不是 2*N 时:子块 RDO 模式继承父结点
  3. 不满足第2点,且最小亮度块<32 或 当前帧为 intra 时:子块 RDO 模式为 intra
  4. 不满足第2点,也不满足第3点时:子块先测试 Inter 再测试 Intra

三、总结

RDO 测试模式(intra/inter)确定的代码在 initCULevel 函数中,其中 intra 模式是否测试直接由 tryIntraRdo 变量决定inter 模式是否测试由当前区域尺寸和 tryInterRdo 变量共同决定

tryIntraRdo 和 tryInterRdo 变量的确定取决于 partitioner.modeType 的取值,而 partitioner.modeType 的取值受 signalModeConsVal 取值约束

由 signalModeCons 函数对 signalModeConsVal 变量取值的确定可以总结出以下规则

  1. 为 DualTree,或父结点模式类型不为inter与intra均尝试,或当前块色度格式为444,或当前块色度格式为400 时:子块 RDO 的模式(intra/inter)继承父结点
  2. 划分后最小色度块 >= 16 且 色度块尺寸不是 2*N 时:子块 RDO 模式继承父结点
  3. 不满足第2点,且最小亮度块<32 或 当前帧为 intra 时:子块 RDO 模式为 intra
  4. 不满足第2点,也不满足第3点时:子块先测试 Inter 再测试 Intra

上一篇:H.266/VVC-VTM代码学习22-partitioner类判断划分模式是否可用函数partitioner.canSplit
下一篇:H.266/VVC-VTM代码学习24-根据当前块位置与尺寸确定隐藏划分模式getImplicitSplit()

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值