躲避疫情,在家也學點東西吧,解鎖一波新地圖——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
未完待續。。