躲避疫情,在家也学点东西吧,解锁一波新地图——VVC的RDO。
所谓率失真优化:在给定编码比特率的情况下,如何使失真最小:J(λ) = min[ D + λR ]。其中:
率:信息数量的多少(比特率R);
失真:接收短信号与源信号的差异程度(失真D);
λ:拉格朗日参数,只与量化参数有关;
几何意义:一条斜率为λ的直线,与率失真曲线的切点即最优解。
目的:得到性价比最高的编码参数,如划分方式、预测模式、变换方式。
如需了解更多具体的理论方面的知识,可参考:率失真优化理论。
文章目录
1 lambda的确定及走向
之前的学习见H.266/VVC代码学习6:VTM4.0中lambda与QP的关系。
直接看VTM7.0中的代码:
重要成员变量含义
double m_dLambda;//最初始的lambda
double m_DistScaleUnadjusted;//lambda的倒数,同时有移位(左移了15位)
double m_DistScale;//lambda的倒数,同时有移位(左移了15位),后面可能有调整
double m_dLambdaMotionSAD[2 /* 运动信息的lambda 0=standard, 1=for transquant bypass when mixed-lossless cost evaluation enabled*/];
重要相关函数含义
double RdCost::calcRdCost(
uint64_t fracBits, //比特数R
Distortion distortion, //失真D
bool useUnadjustedLambda//是否调整lambda
)
{
if( m_costMode == COST_LOSSLESS_CODING && 0 != distortion )//如果无损编码情况下有失真,就返回最大值
{
return MAX_DOUBLE;
}
return ( useUnadjustedLambda ? m_DistScaleUnadjusted : m_DistScale ) * double( distortion ) + double( fracBits );//率失真计算
}
template<typename T, size_t N>
uint32_t updateCandList
(
T uiMode, //新的要参与对比的模式
double uiCost, //新的要参与对比的模式的代价
static_vector<T, N>& candModeList, //之前留下的模式
static_vector<double, N>& candCostList //之前留下的模式的代价
, size_t uiFastCandNum = N, //最后留下几个模式(长度)
int* iserttPos = nullptr
)
{
CHECK( std::min( uiFastCandNum, candModeList.size() ) != std::min( uiFastCandNum, candCostList.size() ), "Sizes do not match!" );
CHECK( uiFastCandNum > candModeList.capacity(), "The vector is to small to hold all the candidates!" );
size_t i;
size_t shift = 0;
size_t currSize = std::min( uiFastCandNum, candCostList.size() );//确定最后有多少个模式
while( shift < uiFastCandNum && shift < currSize && uiCost < candCostList[currSize - 1 - shift] )//找到当前模式排在倒数第几个索引
{
shift++;//从大的往小的找,排在倒数第几个位置
}
if( candModeList.size() >= uiFastCandNum && shift != 0 )//更新列表的条件:超出总列表范围(需要往外剔除),并且,新加入的干掉了其中的一部分
{
for( i = 1; i < shift; i++ )//被干掉的都往后移一位
{
candModeList[currSize - i] = candModeList[currSize - 1 - i];
candCostList[currSize - i] = candCostList[currSize - 1 - i];
}
candModeList[currSize - shift] = uiMode;//把当前这个插进入
candCostList[currSize - shift] = uiCost;
if (iserttPos != nullptr)
{
*iserttPos = int(currSize - shift);
}
return 1;//列表有所更新,就返回1
}
else if( currSize < uiFastCandNum )//否则(列表没填满):直接插入
{
candModeList.insert( candModeList.end() - shift, uiMode );
candCostList.insert( candCostList.end() - shift, uiCost );
if (iserttPos != nullptr)
{
*iserttPos = int(candModeList.size() - shift - 1);
}
return 1;//列表有所更新,就返回1
}
if (iserttPos != nullptr)
{
*iserttPos = -1;
}
return 0;
}
#endif
1.1 updataLambda
void EncCu::updateLambda (Slice* slice, const int dQP,
#if WCG_EXT && ER_CHROMA_QP_WCG_PPS
const bool useWCGChromaControl,
#endif
const bool updateRdCostLambda)
{
#if WCG_EXT && ER_CHROMA_QP_WCG_PPS
if (useWCGChromaControl)//如果使用了WCG,比较容易,计算后返回即可
{
const double lambda = m_pcSliceEncoder->initializeLambda (slice, m_pcSliceEncoder->getGopId(), slice->getSliceQp(), (double)dQP);//获得lambda
const int clippedQP = Clip3 (-slice->getSPS()->getQpBDOffset (CHANNEL_TYPE_LUMA), MAX_QP, dQP);
m_pcSliceEncoder->setUpLambda (slice, lambda, clippedQP);//加载lambda
return;
}
#endif
int iQP = dQP;
const double oldQP = (double)slice->getSliceQpBase();
#if ENABLE_QPA_SUB_CTU
const double oldLambda = (m_pcEncCfg->getUsePerceptQPA() && !m_pcEncCfg->getUseRateCtrl() && slice->getPPS()->getUseDQP()) ? slice->getLambdas()[0] :
m_pcSliceEncoder->calculateLambda (slice, m_pcSliceEncoder->getGopId(), oldQP, oldQP, iQP);//计算
#else
const double oldLambda = m_pcSliceEncoder->calculateLambda (slice, m_pcSliceEncoder->getGopId(), oldQP, oldQP, iQP);
#endif
const double newLambda = oldLambda * pow (2.0, ((double)dQP - oldQP) / 3.0);//新的lambda是根据QP的变化改动的
#if RDOQ_CHROMA_LAMBDA
const double chromaLambda = newLambda / m_pcRdCost->getChromaWeight();
const double lambdaArray[MAX_NUM_COMPONENT] = {
newLambda, chromaLambda, chromaLambda};//根据新计算的lambda更新三个分量
m_pcTrQuant->setLambdas (lambdaArray);//设置进去三个分量的lambda
#else
m_pcTrQuant->setLambda (newLambda);
#endif
if (updateRdCostLambda)//如果要更新RDO的lambda,则重新设置lambda
{
m_pcRdCost->setLambda (newLambda, slice->getSPS()->getBitDepths());
}
}
#endif // SHARP_LUMA_DELTA_QP || ENABLE_QPA_SUB_CTU
1.2 calculateLambda
double EncSlice::calculateLambda( const Slice* slice, // 要处理的帧
const int GOPid, // entry in the GOP table 第几个GOP
const double refQP, // initial slice-level QP 帧的QP
const double dQP, // initial double-precision QP 浮点型QP
int &iQP ) // returned integer QP. 整型QP
{
double dLambda = initializeLambda (slice, GOPid, int (refQP + 0.5), dQP);//得到lambda
iQP = Clip3 (-slice->getSPS()->getQpBDOffset (CHANNEL_TYPE_LUMA), MAX_QP, int (dQP + 0.5));//得到QP
if( m_pcCfg->getDepQuantEnabledFlag() )//如果应用DQ,要有少量的改变
{
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;
}
1.3 initializeLambda
double EncSlice::initializeLambda(const Slice* slice, const int GOPid, const int refQP, const double dQP)
{
/*************************** 1.初始化过程 *************************/
const int bitDepthLuma = slice->getSPS()->getBitDepth(CHANNEL_TYPE_LUMA);//亮度的比特深度
const int bitDepthShift = 6 * (bitDepthLuma - 8 - DISTORTION_PRECISION_ADJUSTMENT(bitDepthLuma)) - 12;//比特深度偏移,VVC基本都是0
const int numberBFrames = m_pcCfg->getGOPSize() - 1;//B帧数量
const SliceType sliceType = slice->getSliceType();//获取该帧类型:I or B or P
#if X0038_LAMBDA_FROM_QP_CAPABILITY
const int temporalId = m_pcCfg->getGOPEntry(GOPid).m_temporalId;//当前帧的层级
const std::vector<double> &intraLambdaModifiers = m_pcCfg->getIntraLambdaModifier();//?????
#endif
double dQPFactor = m_pcCfg->getGOPEntry(GOPid).m_QPFactor;//获取QP
double dLambda, lambdaModifier;//dlambda就是要返回的计算出的lambda的值
/*************** 2.对I帧的操作,设出一个值为0.57 ***************/
if (sliceType == I_SLICE)
{
if ((m_pcCfg->getIntraQpFactor() >= 0.0) && (m_pcCfg->getGOPEntry(GOPid).m_sliceType != I_SLICE))
{
dQPFactor = m_pcCfg->getIntraQpFactor();
}
else
{
#if X0038_LAMBDA_FROM_QP_CAPABILITY
if (m_pcCfg->getLambdaFromQPEnable())//如果lambda从QP得出,则固定为0.57
{
dQPFactor = 0.57;
}
else//否则比较麻烦:0.57减去一个和B帧数量相关的一个值,但范围在0.28到0.57之间
#endif
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
else if (m_pcCfg->getLambdaFromQPEnable())
{
dQPFactor = 0.57;
}
#endif
/***************** 3.先通过0.57计算得到一个dlambda = 0.57 * 2 ^ (QP/3) *********************/
dLambda = dQPFactor * pow(2.0, (dQP + bitDepthShift) / 3.0);//0.57 * 2 ^ (QP/3)
/***************** 4.根据不同的情况调整lambda的数值 *********************/
#if X0038_LAMBDA_FROM_QP_CAPABILITY
if (slice->getDepth() > 0 && !m_pcCfg->getLambdaFromQPEnable())//1.如果lambda不是从QP得到的,需要再乘上一个2~4之间的值
#else
if (slice->getDepth() > 0)
#endif
{
dLambda *= Clip3(2.0, 4.0, ((refQP + bitDepthShift) / 6.0));
}
// if Hadamard is used in motion estimation process
if (!m_pcCfg->getUseHADME() && (sliceType != I_SLICE))//2.如果不是I帧并且可以使用哈达玛变换,需要再乘上一个0.95
{
dLambda *= 0.95;
}
#if X0038_LAMBDA_FROM_QP_CAPABILITY
if ((sliceType != I_SLICE) || intraLambdaModifiers.empty())
{
lambdaModifier = m_pcCfg->getLambdaModifier(temporalId);
}
else
{
lambdaModifier = intraLambdaModifiers[temporalId < intraLambdaModifiers.size() ? temporalId : intraLambdaModifiers.size() - 1];
}
dLambda *= lambdaModifier;//3.乘上一个调整值
#endif
return dLambda;//返回这一个计算出的lambda
}
1.4 setUpLambda
void EncSlice::setUpLambda( Slice* slice, const double dLambda, int iQP)
{
// store lambda
m_pcRdCost ->setLambda( dLambda, slice->getSPS()->getBitDepths() );
/******************************************* 1.对于率失真优化 ************************************************/
//在RdCost中,只有一个lambda,因为亮度和色度位没有分开,而是对色度的失真进行加权。
double dLambdas[MAX_NUM_COMPONENT] = {
dLambda };//设置了一个临时变量,默认值为dLambda,这个临时变量将在处理后应用于RDOQ和SAO
/* 亮度分量直接确定为dLambda,色度分量将在下面的for循环中进行处理 */
for( uint32_t compIdx = 1; compIdx < MAX_NUM_COMPONENT; compIdx++ )
{
const ComponentID compID = ComponentID( compIdx );//确定是Cb还是Cr分量
int chromaQPOffset = slice->getPPS()->getQpOffset( compID ) + slice->getSliceChromaQpDelta( compID );//获取色度QP偏移
int qpc = slice->getSPS()->getMappedChromaQpValue(compID, iQP) + chromaQPOffset;//将偏移值应用到默认QP,获取色度实际采用的QP
double tmpWeight = pow( 2.0, ( iQP - qpc ) / 3.0 ); // 考虑了色度qp映射和色度qp偏移,得到权重,这个权重大于1
if( m_pcCfg->getDepQuantEnabledFlag() && !( m_pcCfg->getLFNST() ) )//如果使用了DQ并且不使用LFNST
{
//增加色度权重以进行相关量化(以减少从色度到亮度的比特率偏移)
tmpWeight *= ( m_pcCfg->getGOPSize() >= 8 ? pow( 2.0, 0.1/3.0 ) : pow( 2.0, 0.2/3.0 ) );
}
m_pcRdCost->setDistortionWeight( compID, tmpWeight );
dLambdas[compIdx] = dLambda / tmpWeight;//确定色度分量的lambda,比亮度的小一点
}
/***************************** 2.根据率失真优化得到的lambda,直接作用于RDOQ和SAO ******************************/
// for RDOQ
m_pcTrQuant->setLambdas( dLambdas );
// for SAO
slice->setLambdas( dLambdas );
}
1.5 setLambda
void RdCost::setLambda( double dLambda, const BitDepths &bitDepths )
{
m_dLambda = dLambda;//把参数中的lambda值赋值给RdCost类的成员变量m_dLambda
m_DistScale = double(1<<SCALE_BITS) / m_dLambda;//m_DistScale这个是lambda的倒数,还有移位,J(λ) = min[ D + λR ] 转化为 1/J(λ) = min[ (1/λ) D + R ]
m_dLambdaMotionSAD[0] = sqrt(m_dLambda);//常规帧间运动信息的SAD的lambda是lambda的开方
dLambda = 0.57
* pow(2.0, ((LOSSLESS_AND_MIXED_LOSSLESS_RD_COST_TEST_QP_PRIME - 12
+ 6
* ((bitDepths.recon[CHANNEL_TYPE_LUMA] - 8)
- DISTORTION_PRECISION_ADJUSTMENT(bitDepths.recon[CHANNEL_TYPE_LUMA])))
/ 3.0));
m_dLambdaMotionSAD[1] = sqrt(dLambda);//TS模式下帧间运动信息的SAD的lambda是上面新算得lambda的开方
}
2 率失真优化所在位置
2.1 TPM
用于TPM模式的粗选:从40种划分确定索引方式中,根据SAD找到3种最优,后续这3种模式会进入SATD筛选。
double cost = (double)uiSad + (double)uiBitsCand * sqrtLambdaForFirstPass;
updateCandList( mergeCand, cost, triangleRdModeList, tianglecandCostList, triangleNumMrgSATDCand );
2.2 Affine merge
用于Affine merge模式的粗选,根据SAD找到2种最优,后续这2种模式会进入SATD筛选。
double cost = (double)uiSad + (double)uiBitsCand * sqrtLambdaForFirstPass;
updateCandList( uiMergeCand, cost, RdModeList, candCostList, uiNumMrgSATDCand );
2.3 SAO
1)确定最佳边带补偿阶段:对比28种边带,找到最佳边带BO。
2)CTU级:确定最佳SAO模式阶段:对比以下6种:4个边界补偿方式EO,1个前面找到的最佳BO,1个不采用SAO,找到最佳SAO方式
3)CTU级:确定是否使用参数融合:对比以下3种:刚找到的最佳SAO方式,上块SAO merge,左块SAO merge,得到SAO全过程的最佳
2.4 ALF
1)CTU级:确定是否使用ALF(根据前面计算出的协方差D)
2)确定亮度是否合并滤波器分类,对比固定滤波器和计算得出的滤波器系数,得到最佳亮度滤波器系数,保存进APS
3)确定色度用哪个滤波器,得到最佳色度滤波器系数,保存进APS
4)遍历APS序列(即对比当前系数和历史系数哪个更好),得到最佳ALF系数
2.5 Intra
1、亮度预测对35种模式第一轮粗选出2或3种模式:D应用SAD和HAD中较小的
2、亮度预测对2或3种旁边的模式第二轮粗选出2或3种模式:D应用SAD和HAD中较小的
3、亮度预测对MPM列表中的进行多参考行的筛选找到最佳参考行:D应用SAD和HAD中较小的
4、亮度MIP预测的筛选得出一种最佳MIP模式,放在前面2到3种模式后面成为3或4种模式:D应用SAD和HAD中较小的
5、亮度预测选出最佳模式:D应用HAD
6、色度预测用到lambda计算的有6种,6种中选出最佳:D应用HAD
2.6 Merge
merge、mmvd、CIIP的失真计算共同粗选出5种(CIIP选出1种,merge和mmvd共同选出4种):D均应用SAD
未完待续。。