H.266/VVC-VTM代码学习25-VTM中RDcost的计算与λ的设定(一)

本文详细介绍了H.266/VVC视频编码标准参考软件VTM中率失真成本(RDcost)的计算过程,包括最上层函数calcRdCost()的实现、distortion scale (DS)的计算以及λ的计算。通过分析VTM代码,揭示了RDO理论在实际编码中的应用及其调整策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

H.266/VVC专栏传送

上一篇:H.266/VVC-VTM代码学习24-根据当前块位置与尺寸确定隐藏划分模式getImplicitSplit()
下一篇:

前言

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

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

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

一、简介

率失真(rate distortion optimization, RDO)是视频编码的核心理论之一。常规视频编码的目标是使编码后的视频流在带宽占用(用rate表征)和图像质量(用distortion表征)两者间有很好的trade-off。rate distortion (RD) cost便是用于衡量视频流在码率和失真二者间均衡情况的指标。

本文将详细介绍VTM10.0中对RD cost的计算过程。

二、代码详解

1.计算RD cost的最上层函数calcRdCost()

#if WCG_EXT		// 默认开启
// 输入分别为使用比特fracBits,失真distortion,是否使用不调整的λ标志位useUnadjustedLambda【默认使用】
double RdCost::calcRdCost( uint64_t fracBits, Distortion distortion, bool useUnadjustedLambda )
#else
// 不开启WCG_EXT使只输入使用比特和失真两个参数
double RdCost::calcRdCost( uint64_t fracBits, Distortion distortion )
#endif
{
  // 若当前cost模式为无损编码cost,且distortion不为0,且当前cost为无损编码的cost
  if (m_costMode == COST_LOSSLESS_CODING && 0 != distortion && m_isLosslessRDCost)
  {
    // 直接返回最大值作为RD cost
    return MAX_DOUBLE;
  }
#if WCG_EXT
  // 若使用未调整的λ,则返回未调整的DS*D+R
  // 若不使用未调整的λ,则返回调整后的DS*D+R
  return ( useUnadjustedLambda ? m_DistScaleUnadjusted : m_DistScale ) * double( distortion ) + double( fracBits );
#else
  // 不开启WCG_EXT则直接返回调整后的DS*D+R
  return m_DistScale * double( distortion ) + double( fracBits );
#endif
}

此函数位于RdCost.cpp文件中,用于在RDO过程中计算当前配置下得到了使用比特数和对应失真数值后计算RD cost。

上述代码及下文中将distortion scale简写为DS。由上述代码可知,RD cost计算公式如下式所示:
R D c o s t = D S × D + R RD cost=DS\times D+R RDcost=DS×D+R

当宏定义WCG_EXT(默认开启)有效时,式中的DS可以分为m_DistScaleUnadjusted和m_DistScale两类,即可以使用未调整的DS或调整后的DS。useUnadjustedLambda(默认有效)有效时使用未调整的DS,否则使用调整后的DS。

2.m_DistScale的计算

void RdCost::setLambda( double dLambda, const BitDepths &bitDepths )
{
  // 将λ存入成员变量
  m_dLambda             = dLambda;
  // 根据λ计算DS
  m_DistScale           = double(1<<SCALE_BITS) / m_dLambda;
  // 将λ开方作为motion SAD λ
  m_dLambdaMotionSAD    = sqrt(m_dLambda);
}

此函数位于RdCost.cpp文件中,用于将设定的λ存入成员变量并据此计算DS。

由上述代码可知,DS计算公式如下式所示:
D S = 2 15 λ DS = \frac {2^{15}}{\lambda} DS=λ215

RDO理论中的RD cost应为下式所示:
R D c o s t = D + λ R RDcost=D+\lambda R RDcost=D+λR

而VTM中计算的RD cost为下式所示:
R D c o s t = D S × D + R = 2 15 λ × D + R = 2 15 λ ( D + λ 2 15 R ) \begin{aligned} RDcost&=DS\times D+R \\ &=\frac {2^{15}}{\lambda} \times D+R \\ &=\frac {2^{15}}{\lambda} \left( D+\frac {\lambda}{2^{15}} R\right) \end{aligned} RDcost=DS×D+R=λ215×D+R=λ215(D+215λR)

3. λ \lambda λ的计算

EncSlice::calculateLambda() 函数在 EncSlice::initEncSlice() 中被调用,以计算 λ \lambda λ的值。

#if SHARP_LUMA_DELTA_QP || ENABLE_QPA_SUB_CTU
double EncSlice::calculateLambda( const Slice*     slice,  // 当前slice
                                  const int        GOPid, // entry in the GOP table 当前GOPid
                                  const double     refQP, // initial slice-level QP 初始slice级QP
                                  const double     dQP,   // initial double-precision QP 初始双精度QP
                                        int       &iQP )  // returned integer QP. 最终返回的整型QP
{
  // 根据初始slice级QP和初始双精度QP计算λ,initializeLambda() called by calculateLambda() and updateLambda()
  double dLambda = initializeLambda (slice, GOPid, int (refQP + 0.5), dQP);
  // 将初始双精度QP四舍五入后限定在合理区间输出为整型QP
  iQP = Clip3 (-slice->getSPS()->getQpBDOffset (CHANNEL_TYPE_LUMA), MAX_QP, int (dQP + 0.5));
  // 若dependent quantization enabled flag有效(默认使用DQ)
  if (slice->getDepQuantEnabledFlag())
  {
    // 若使用DQ,对于λ有一个轻微调整(乘的因子约为1.0595)
    dLambda *= pow( 2.0, 0.25/3.0 ); // slight lambda adjustment for dependent quantization (due to different slope of quantizer) 
  }
  // NOTE: the lambda modifiers that are sometimes applied later might be best always applied in here.
  // 返回λ
  return dLambda;
}
#endif

由以上代码可知,计算 λ \lambda λ的主要过程在initializeLambda()函数中。

EncSlice::initializeLambda()函数在calculateLambda() 和 updateLambda()中被调用。

double EncSlice::initializeLambda(const Slice* slice, const int GOPid, const int refQP, const double dQP)
{
  // 得到亮度位深
  const int   bitDepthLuma  = slice->getSPS()->getBitDepth(CHANNEL_TYPE_LUMA);
  // 比特深度偏移=6*(bitdepth-8)-12,默认处理位深10时,该值为0
  const int   bitDepthShift = 6 * (bitDepthLuma - 8 - DISTORTION_PRECISION_ADJUSTMENT(bitDepthLuma)) - 12;
  // B帧数量
  const int   numberBFrames = m_pcCfg->getGOPSize() - 1;
  // 当前帧的类型:
  //B_SLICE               = 0,
  //P_SLICE               = 1,
  //I_SLICE               = 2,
  const SliceType sliceType = slice->getSliceType();
#if X0038_LAMBDA_FROM_QP_CAPABILITY
  // 当前帧level
  const int      temporalId = m_pcCfg->getGOPEntry(GOPid).m_temporalId;
  // 帧内λ修改器
  const std::vector<double> &intraLambdaModifiers = m_pcCfg->getIntraLambdaModifier();
#endif
  // 第一种情况:当前帧为I帧或P帧(关键帧)
  // case #1: I or P slices (key-frame)
  // 获取QPF
  double dQPFactor = m_pcCfg->getGOPEntry(GOPid).m_QPFactor;
  double dLambda, lambdaModifier;

  // 若当前帧为I帧
  if (sliceType == I_SLICE)
  { 
    // 若intraQPF>=0且GOP的入口帧不为I帧
    if ((m_pcCfg->getIntraQpFactor() >= 0.0) && (m_pcCfg->getGOPEntry(GOPid).m_sliceType != I_SLICE))
    {
      // QPF设置为intraQPF
      dQPFactor = m_pcCfg->getIntraQpFactor();
    }
    // 若intraQP<0或GOP入口帧为I帧
    else
    {
#if X0038_LAMBDA_FROM_QP_CAPABILITY
	  // 若可以从QP获得λ
      if (m_pcCfg->getLambdaFromQPEnable())
      {
        // 将QPF设置为0.57
        dQPFactor = 0.57;
      }
      // 若不能从QP获得λ
      else
#endif
	  // QPF设置为在0.57基础上根据B帧数量进行调整得到的一个值
      dQPFactor = 0.57 * (1.0 - Clip3(0.0, 0.5, 0.05 * double (slice->getPic()->fieldPic ? numberBFrames >> 1 : numberBFrames)));
    }
  }
#if X0038_LAMBDA_FROM_QP_CAPABILITY
  // 若当前帧不为I帧,但可以从QP获得λ
  else if (m_pcCfg->getLambdaFromQPEnable())
  {
    // 将QPF设置为0.57
    dQPFactor = 0.57;
  }
#endif

  // λ=QPF*2^(QP/3)
  dLambda = dQPFactor * pow(2.0, (dQP + bitDepthShift) / 3.0);

#if X0038_LAMBDA_FROM_QP_CAPABILITY
  // 若λ不是从QP得到的
  if (slice->getDepth() > 0 && !m_pcCfg->getLambdaFromQPEnable())
#else
  if (slice->getDepth() > 0)
#endif
  {
    // 在之前计算的λ基础上乘2-4之间的一个值(与初始slice级QP有关)
    dLambda *= Clip3(2.0, 4.0, ((refQP + bitDepthShift) / 6.0));
  }
  // if Hadamard is used in motion estimation process
  // 若当前帧不是I帧且不使用哈达玛变换
  if (!m_pcCfg->getUseHADME() && (sliceType != I_SLICE))
  {
    // 在之前计算的λ基础上乘0.95
    dLambda *= 0.95;
  }
#if X0038_LAMBDA_FROM_QP_CAPABILITY
  // 若当前帧不为I帧或帧内λ调整器为空
  if ((sliceType != I_SLICE) || intraLambdaModifiers.empty())
  {
    // 获取λ调整器
    lambdaModifier = m_pcCfg->getLambdaModifier(temporalId);
  }
  // 若当前帧为I帧,且λ调整器不为空
  else
  {
    // 获取λ调整器
    lambdaModifier = intraLambdaModifiers[temporalId < intraLambdaModifiers.size() ? temporalId : intraLambdaModifiers.size() - 1];
  }
  // 根据λ调整器调整之前计算得到的λ
  dLambda *= lambdaModifier;
#endif
  // 返回计算得到的λ
  return dLambda;
}

上一篇:H.266/VVC-VTM代码学习24-根据当前块位置与尺寸确定隐藏划分模式getImplicitSplit()
下一篇:H.266/VVC-VTM代码学习26-VTM中RDcost的计算与λ的设定(二)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值