率失真优化
原理
一、率失真优化的目标就是:
1、在一定的码率(码率也表现为数据压缩的程度,码率越低,数据压缩的越厉害)限制下,减少视频的失真(减少失真就会提高视频的主观质量,看的人就会喜欢o(*≧▽≦)ツ)!
2、在允许一定的失真下,把视频压缩到最小!
二、编码器的率失真优化的工作主要是按照某种策略选取最优的编码参数,以实现最优的编码性能,某个模式下的率失真代价,是通过该模式下编码的失真和占用的比特比特数来计算的
三、率失真函数RD 是在假定信源在给定的情况下,在用户可以容忍的失真度内再现数据消息所必需获得的最小平均互信信息,直白一点说,就是在允许的失真内,数据可以压缩的极限!我们对数据的压缩不能超过这个极限,否则,数据在解码端就不能再现了!因此我们的工作就是,在不超过这个极限的前提下,尽量使数据压缩得更小!
更多的细节参考 HEVC/H.265理论知识(10)——率失真优化
代码实现
某个模式下的率失真代价,是通过该模式下编码的失真和占用的比特比特数来计算的
TComDataCU中有三个成员以及函数和率失真相关:
UInt& getTotalDistortion() { return m_uiTotalDistortion; } //总的失真,某一种模式下总的失真
Double& getTotalCost() { return m_dTotalCost; } //总的代价,某一种模式下总的代价
UInt& getTotalBits() { return m_uiTotalBits; } //总得比特数,按照某一种模式进行编码之后的总比特数
失真的计算
一个像素块的总的失真就是该像素块中三个分量的失真之和。失真是通过TComRdCost::getDistPart函数进行计算的,DistParam是失真参数结构体,用于存放计算失真的参数,以及处理计算过程的函数指针,至于使用哪个具体的率失真计算函数,可以通过DFunc枚举来指定,默认使用DF_SSE(即xGetSSE,平方误差和)。
UInt TComRdCost::getDistPart(Int bitDepth, Pel* piCur, Int iCurStride, Pel* piOrg, Int iOrgStride, UInt uiBlkWidth, UInt uiBlkHeight, TextType eText, DFunc eDFunc)
{
// DistParam是失真参数结构体,用于存放计算失真的参数,以及处理计算过程的函数指针
DistParam cDtParam;
// eDFunc指定了使用哪个失真计算函数(默认使用SSE的方式计算)
setDistParam( uiBlkWidth, uiBlkHeight, eDFunc, cDtParam );
cDtParam.pOrg = piOrg;
cDtParam.pCur = piCur;
cDtParam.iStrideOrg = iOrgStride;
cDtParam.iStrideCur = iCurStride;
cDtParam.iStep = 1;
cDtParam.bApplyWeight = false;
cDtParam.uiComp = 255; // just for assert: to be sure it was set before use, since only values 0,1 or 2 are allowed.
cDtParam.bitDepth = bitDepth;
if (eText == TEXT_CHROMA_U)
{
return ((Int) (m_cbDistortionWeight * cDtParam.DistFunc( &cDtParam )));
}
else if (eText == TEXT_CHROMA_V)
{
return ((Int) (m_crDistortionWeight * cDtParam.DistFunc( &cDtParam )));
}
else
{
return cDtParam.DistFunc( &cDtParam );
}
}
/*
** 默认的失真计算函数(使用SSE的方法)
*/
UInt TComRdCost::xGetSSE( DistParam* pcDtParam )
{
if ( pcDtParam->bApplyWeight )
{
return xGetSSEw( pcDtParam );
}
Pel* piOrg = pcDtParam->pOrg;
Pel* piCur = pcDtParam->pCur;
Int iRows = pcDtParam->iRows;
Int iCols = pcDtParam->iCols;
Int iStrideOrg = pcDtParam->iStrideOrg;
Int iStrideCur = pcDtParam->iStrideCur;
UInt uiSum = 0;
UInt uiShift = DISTORTION_PRECISION_ADJUSTMENT((pcDtParam->bitDepth-8) << 1);
Int iTemp;
for( ; iRows != 0; iRows-- )
{
for (Int n = 0; n < iCols; n++ )
{
iTemp = piOrg[n ] - piCur[n ];
uiSum += ( iTemp * iTemp ) >> uiShift;
}
piOrg += iStrideOrg;
piCur += iStrideCur;
}
return ( uiSum );
}
比特总数的计算
这个就比较简单了,熵编码之后统计比特数就可以了,具体的就是调用TEncEntropy::getNumberOfWrittenBits()函数得到比特数
率失真代价的计算
计算完成失真与比特数之后,就可以利用失真与比特数来计算代价了,计算代价是通过TComRdCost::calcRdCost函数来进行的
Double TComRdCost::calcRdCost( UInt uiBits, UInt uiDistortion, Bool bFlag, DFunc eDFunc )
{
Double dRdCost = 0.0;
Double dLambda = 0.0;
// 根据率失真计算函数的类型来确定lambda参数
switch ( eDFunc )
{
case DF_SSE:
assert(0);
break;
case DF_SAD:
dLambda = (Double)m_uiLambdaMotionSAD;
break;
case DF_DEFAULT:
dLambda = m_dLambda;
break;
case DF_SSE_FRAME:
dLambda = m_dFrameLambda;
break;
default:
assert (0);
break;
}
// 根据失真和比特数来计算代价
// 是否选用某种模式,要根据代价来决定,代价要在失真和比特数之间达到平衡
// 既要让失真小,也要让比特数少
if (bFlag)
{
// Intra8x8, Intra4x4 Block only...
#if SEQUENCE_LEVEL_LOSSLESS
dRdCost = (Double)(uiBits);
#else
dRdCost = (((Double)uiDistortion) + ((Double)uiBits * dLambda));
#endif
}
else
{
if (eDFunc == DF_SAD)
{
dRdCost = ((Double)uiDistortion + (Double)((Int)(uiBits * dLambda+.5)>>16));
dRdCost = (Double)(UInt)floor(dRdCost);
}
else
{
#if SEQUENCE_LEVEL_LOSSLESS
dRdCost = (Double)(uiBits);
#else
dRdCost = ((Double)uiDistortion + (Double)((Int)(uiBits * dLambda+.5)));
dRdCost = (Double)(UInt)floor(dRdCost);
#endif
}
}
return dRdCost;
}