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

本文深入探讨了H.266/VVC编码标准参考软件VTM中λ参数的设定。在编码过程中,λ起到了平衡失真与码率的关键作用。首先介绍了在initEncSlice()函数中如何预计算不同QP值对应的λ,接着详细分析了在码率控制模式下,如何根据目标bpp动态调整λ以优化编码性能。文章通过代码解析,帮助读者更好地理解VVC编码的内部机制。

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

H.266/VVC专栏传送

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

前言

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便是用于衡量视频流在码率和失真二者间均衡情况的指标。

上篇博客介绍了计算RD cost和 λ \lambda λ的主要函数,可以看出 λ \lambda λ在RD cost的计算过程中具有十分重要的意义。 λ \lambda λ不只是RD cost计算过程中的一个普通参数,而是编码过程中均衡失真与码率的关键因子,读懂VTM中 λ \lambda λ的设定,即可对VVC编码过程从整体上有更清晰的认识与把握。基于此,笔者决定用更多的篇幅介绍代码中的相关内容。

本篇博客将继续聚焦VTM代码中关于 λ \lambda λ设定的部分,对RDO过程中的 λ \lambda λ相关部分代码做出解释说明。

二、代码详解

1.initEncSlice()函数中对 λ \lambda λ的设置

  // 获取double精度的当前帧QP
  dQP = m_pcCfg->getQPForPicture(iGOPid, rpcSlice);
  int iQP;
  double dOrigQP = dQP;
  // pre-compute lambda and QP values for all possible QP candidates
  // 预先为所有可能的候选QP计算λ和QP值
  // getDeltaQpRD()得到DeltaQpRD(在cfg文件中设置,默认为0)
  for ( int iDQpIdx = 0; iDQpIdx < 2 * m_pcCfg->getDeltaQpRD() + 1; iDQpIdx++ )
  {
    // compute QP value
    // 根据picture level的QP和DeltaQpRD计算当前QP值
    dQP = dOrigQP + ((iDQpIdx+1)>>1)*(iDQpIdx%2 ? -1 : 1);
    // compute lambda value
    // 计算λ的值
#if SHARP_LUMA_DELTA_QP  // 默认开启
	// 调用calculateLambda计算λ
    dLambda = calculateLambda (rpcSlice, iGOPid, dQP, dQP, iQP);
#else
    dLambda = initializeLambda (rpcSlice, iGOPid, int (dQP + 0.5), dQP);
    iQP = Clip3 (-rpcSlice->getSPS()->getQpBDOffset (CHANNEL_TYPE_LUMA), MAX_QP, int (dQP + 0.5));
#endif
	// 将计算得到的λ和QP的值存入数组中
    m_vdRdPicLambda[iDQpIdx] = dLambda;
    m_vdRdPicQp    [iDQpIdx] = dQP;
    m_viRdPicQp    [iDQpIdx] = iQP;
  }

上面部分的代码位于initEncSlice函数中,即该部分代码在进入slice level编码环节之前初始化时执行。

for循环条件判断中的getDeltaQpRD()函数返回DeltaQpRD,即slice level delta QP的最大绝对值。下面是代码的基本步骤:

  1. 循环执行时,iDQpIdx取值范围为最小值0,最大值2*DeltaQpRD
  2. 计算当次循环的QP值
  3. 调用calculateLambda函数计算上一步得到QP值对应的λ
  4. 将QP值和λ存入数组

其中,第二步的计算如下式所示:
∣ Δ Q P ∣ = ( i D Q p I d x + 1 ) > > 1 |\Delta QP| = (iDQpIdx+1)>>1 ΔQP=(iDQpIdx+1)>>1
Δ Q P = { ∣ Δ Q P ∣ , i D Q p I d x % 2 = 0 − ∣ Δ Q P ∣ , i D Q p I d x % 2 = 1 \Delta QP=\begin{cases} |\Delta QP| &, iDQpIdx\% 2=0 \\ -|\Delta QP| &, iDQpIdx\% 2=1 \end{cases} ΔQP={ΔQPΔQP,iDQpIdx%2=0,iDQpIdx%2=1
Q P = Q P p i c + Δ Q P QP = QP_{pic}+\Delta QP QP=QPpic+ΔQP

即在循环执行过程中 Δ Q P \Delta QP ΔQP取值绝对值从小到大,符号正负交替。

2.使用码率控制时 λ \lambda λ调整的代码

该部分代码位于EncSlice.cpp文件的encodeCtus函数中,且位于遍历处理slice中各个CTU的循环内部。根据当前QP调整 λ \lambda λ的取值。

需要注意的是,这部分代码只在设定使用码率控制(Rate Control, RC)的情况下执行。若只关注VTM主体框架的读者可以跳过这一部分。

    // 存储原λ的值
    const double oldLambda = pRdCost->getLambda();
    // 若使用码率控制
    if ( pCfg->getUseRateCtrl() )
    {
      // 将sliceQP设置为estQP
      int estQP        = pcSlice->getSliceQp();
      // 将estLambda和bpp初始化为-1
      double estLambda = -1.0;
      double bpp       = -1.0;

      // 直接使用sliceQP的情况
      if( ( pcPic->slices[0]->isIRAP() && pCfg->getForceIntraQP() ) || !pCfg->getLCULevelRC() )
      {
        estQP = pcSlice->getSliceQp();
      }
      // 其他情况下
      else
      {
        // 先计算出LCU的目标bpp
        bpp = pRateCtrl->getRCPic()->getLCUTargetBpp(pcSlice->isIRAP());
        // 若当前帧为I帧
        if ( pcPic->slices[0]->isIntra())
        {
          // 根据bpp计算当前slice的QP和λ
          estLambda = pRateCtrl->getRCPic()->getLCUEstLambdaAndQP(bpp, pcSlice->getSliceQp(), &estQP);
        }
        // 若当前帧不为I帧
        else
        {
          // 根据LCU的目标bpp计算λ
          estLambda = pRateCtrl->getRCPic()->getLCUEstLambda( bpp );
          // 根据λ得到QP
          estQP     = pRateCtrl->getRCPic()->getLCUEstQP    ( estLambda, pcSlice->getSliceQp() );
        }

        // 将QP规范化到固定区间
        estQP     = Clip3( -pcSlice->getSPS()->getQpBDOffset(CHANNEL_TYPE_LUMA), MAX_QP, estQP );

        // 设置λ
        pRdCost->setLambda(estLambda, pcSlice->getSPS()->getBitDepths());
#if WCG_EXT
        // 将得到的λ保存为未调整的λ
        pRdCost->saveUnadjustedLambda();
#endif
        // 对各个分量进行处理
        for (uint32_t compIdx = 1; compIdx < MAX_NUM_COMPONENT; compIdx++)
        {
          const ComponentID compID = ComponentID(compIdx);
          // 得到色度分量QP的偏移量
          int chromaQPOffset = pcSlice->getPPS()->getQpOffset(compID) + pcSlice->getSliceChromaQpDelta(compID);
          // 得到色度分量的QP值
          int qpc = pcSlice->getSPS()->getMappedChromaQpValue(compID, estQP) + chromaQPOffset;
          
          // 计算权重
          double tmpWeight = pow(2.0, (estQP - qpc) / 3.0);  // takes into account of the chroma qp mapping and chroma qp Offset
          if (m_pcCfg->getDepQuantEnabledFlag())
          {
            // 若使用Dependent quantization,则调整权重
            tmpWeight *= (m_pcCfg->getGOPSize() >= 8 ? pow(2.0, 0.1 / 3.0) : pow(2.0, 0.2 / 3.0));  // increase chroma weight for dependent quantization (in order to reduce bit rate shift from chroma to luma)
          }
          // 设定D的权重
          m_pcRdCost->setDistortionWeight(compID, tmpWeight);
        }
#if RDOQ_CHROMA_LAMBDA
        // 存储记录λ和QP
        const double lambdaArray[MAX_NUM_COMPONENT] = {estLambda / m_pcRdCost->getDistortionWeight (COMPONENT_Y),
                                                       estLambda / m_pcRdCost->getDistortionWeight (COMPONENT_Cb),
                                                       estLambda / m_pcRdCost->getDistortionWeight (COMPONENT_Cr)};
        pTrQuant->setLambdas( lambdaArray );
#else
        pTrQuant->setLambda( estLambda );
#endif
      }

      pRateCtrl->setRCQP( estQP );
    }

上一篇:H.266/VVC-VTM代码学习25-VTM中RDcost的计算与λ的设定(一)
下一篇:H.266/VVC-VTM代码学习27-VTM中编码器主函数逻辑

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值