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的最大绝对值。下面是代码的基本步骤:
- 循环执行时,iDQpIdx取值范围为最小值0,最大值2*DeltaQpRD
- 计算当次循环的QP值
- 调用calculateLambda函数计算上一步得到QP值对应的λ
- 将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中编码器主函数逻辑