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的计算与λ的设定(二)