原文:https://blog.csdn.net/nb_vol_1/article/details/56036991
率失真優化
原理
一、率失真優化的目標就是:
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;
}