帧间预测基本知识
对帧间预测有所了解的同学应该都知道,帧间预测操作一般都是由两个最基本也是最核心的操作组成:运动估计(ME)和运动补偿(MC)。
运动估计:负责找到当前帧和参考帧之间的匹配运动矢量(MV)
运动补偿:对MV所指向的参考帧中的匹配参考块,进行插值滤波等操作,得到最终的预测参考块(即用于和当前编码块作差得到残差系数等)
HM中,ME和MC便是在predInterSearch函数中完成的。
备注:想复习补充帧间预测相关知识的同学,可以参见大神的博客:https://blog.csdn.net/NB_vol_1/article/details/55272434
运动补偿为什么要插值
众所周知,现在的视频编码标准MV都支持分数像素,而参考帧中的原始参考块是整数像素级的,为了支持分数像素MV,所以需要对整数像素块进行插值操作,得到分数像素精度的预测参考块。从而进行后续的求残差、变换量化等操作。
函数基本流程
函数原型
Void TEncSearch::predInterSearch( TComDataCU* pcCU, TComYuv* pcOrgYuv, TComYuv* pcPredYuv, TComYuv* pcResiYuv, TComYuv* pcRecoYuv DEBUG_STRING_FN_DECLARE(sDebug), Bool bUseRes, Bool bUseMRG )
参数说明:
pcCU:当前评估的CU模式
pcOrgYuv:CU原始数据
pcPredYuv:预测数据
pcResiYuv:残差数据
pcRecoYuv:重建数据
bUseRes:默认为false,false–函数开头处会清空pcResiYuv。楼主看了下,程序中该变量恒为false,不是很明白该变量的用途。
bUseMRG:默认为false。true–对当前划分模式进行Merge评估,即找到最优的Merge候选项,不需要ME,false–进行ME + MC,计算代价
流程
P帧
对于CU下的每一个PU,遍历参考列表0中的各帧作为参考帧,进行如下操作:
-
AMVP
获得最优MVP,并将其作为ME的搜索起点,对应的函数是xEstimateMvPredAMVP -
ME
以AMVP得到的MVP作为搜索起点,搜索最优MV,对应的函数是xMotionEstimation遍历完各参考帧后,便得到了最优的帧间预测参数:参考帧和对应的MV
-
设置最优的预测模式、参考帧、MV、MVD
-
若划分尺寸不是2N x 2N,则补充进行Merge模式评估,将最优的Merge模式代价和ME代价进行比较,更新最优模式。注:Merge省略了ME过程,直接使用时空域相邻候选项的MV。
B帧
B帧与P帧主要的区别就是:B帧会选用两个参考帧,对应两个MV。对应到预测流程上的区别基本如下:
- 会遍历两个参考列表中的各帧作为参考帧,进行AMVP和ME操作,记录单向预测的预测性能
- 进行双向预测,即从参考列表0和参考列表1中各选择一个参考帧,进行预测。由于需要两个MV,所以在对参考列表X(X取0,1)进行ME之后,需要补充进行MC操作(得到预测参考块X),然后再对列表1-X进行ME操作,从而得到最优的两个MV。
- 将双向预测的性能与单向预测进行比较,得到最终的预测方式并记录详细的预测信息
Merge评估
bUseMRG为true时,只进行Merge模式评估,即只进行步骤4。
关键变量
最核心的变量是bTestNormalMC。bTestNormalMC由传入的形参bUseMRG决定,bUseMRG为true,bTestNormalMC才会被设为false,从而进行Merge模式评估。
作用:true–进行正常的ME和MC操作,false–进行Merge模式评估,省去了ME操作。
其他的重要变量都在源代码中以注释的形式进行了说明。注:注释中还包含了楼主的一些疑问,还希望有大佬能指点迷津或者一起交流探讨。
源代码分析
备注:此代码段是从HM16.20源代码TEncSearch.cpp中完整拷贝过来的,里面加入了个人的阅读注释。楼主对代码的注释可能会存在理解偏差,望读者可以指出来,一起交流探讨~毕竟楼主对于HM源代码研究不多,不少地方也存在疑惑。希望有大佬同仁一起学习交流,共同进步!
//! search of the best candidate for inter prediction
#if AMP_MRG
Void TEncSearch::predInterSearch( TComDataCU* pcCU, TComYuv* pcOrgYuv, TComYuv* pcPredYuv, TComYuv* pcResiYuv, TComYuv* pcRecoYuv DEBUG_STRING_FN_DECLARE(sDebug), Bool bUseRes, Bool bUseMRG )
#else
Void TEncSearch::predInterSearch( TComDataCU* pcCU, TComYuv* pcOrgYuv, TComYuv* pcPredYuv, TComYuv* pcResiYuv, TComYuv* pcRecoYuv, Bool bUseRes )
#endif
{
for(UInt i=0; i<NUM_REF_PIC_LIST_01; i++)
{
m_acYuvPred[i].clear();
}
m_cYuvPredTemp.clear();
pcPredYuv->clear();
if ( !bUseRes )
{
pcResiYuv->clear(); // false--清空残差块
}
pcRecoYuv->clear();
TComMv cMvSrchRngLT;
TComMv cMvSrchRngRB;
TComMv cMvZero;
TComMv TempMv; //kolya
TComMv cMv[2]; // 存储单向预测的MV(分别对应前向和后向)
TComMv cMvBi[2]; // 存储双向预测的两个MV
TComMv cMvTemp[2][33];
Int iNumPart = pcCU->getNumPartitions(); // 获取当前PU划分模式下子块的个数(例如2N x 2N 对应 1, 2N x N 对应 2, N x N 对应 4)
Int iNumPredDir = pcCU->getSlice()->isInterP() ? 1 : 2; // P帧预测方向数为1,B帧为2
TComMv cMvPred[2][33];
TComMv cMvPredBi[2][33];
Int aaiMvpIdxBi[2][33];
Int aaiMvpIdx[2][33]; // *** 存储 AMVP 得到的最优 MVP的 列表索引
Int aaiMvpNum[2][33]; // *** 存储 AMVP 得到的最优 MVP的 列表长度
AMVPInfo aacAMVPInfo[2][33];
Int iRefIdx[2]={
0,0}; //If un-initialized, may cause SEGV in bi-directional prediction iterative stage.
Int iRefIdxBi[2];
UInt uiPartAddr;
Int iRoiWidth, iRoiHeight; // 宽高
UInt uiMbBits[3] = {
1, 1, 0}; // *** 对应的是PU划分模式所需要消耗的编码比特数
UInt uiLastMode = 0; // *** 0--前向预测,1--后向预测,2--双向预测
Int iRefStart, iRefEnd; // 参考帧的范围
PartSize ePartSize = pcCU->getPartitionSize( 0 ); // *** 获取CU的划分尺寸
Int bestBiPRefIdxL1 = 0;
Int bestBiPMvpL1 = 0;
Distortion biPDistTemp = std::numeric_limits<Distortion>::max();
TComMvField cMvFieldNeighbours[MRG_MAX_NUM_CANDS << 1]; // double length for mv of both lists
UChar uhInterDirNeighbours[MRG_MAX_NUM_CANDS]; // *** MRG_MAX_NUM_CANDS 宏值为5,说明AMVP是从5个候选项中进行选择
Int numValidMergeCand = 0 ;
for ( Int iPartIdx = 0; iPartIdx < iNumPart; iPartIdx++ )
{
Distortion uiCost[2] = {
std::numeric_limits<Distortion>::max(), std::numeric_limits<Distortion>::max() };
Distortion uiCostBi = std::numeric_limits<Distortion>::max();
Distortion uiCostTemp;
UInt uiBits[3]; // 对应的是3种预测模式:0-前向预测,1-后向预测,2-双向预测
UInt uiBitsTemp;
Distortion bestBiPDist = std::numeric_limits<Distortion>::max();
Distortion uiCostTempL0[MAX_NUM_REF]; // *** MAX_NUM_REF 该宏的值为16,说明HM中参考帧缓冲最多为16
for (Int iNumRef=0; iNumRef < MAX_NUM_REF; iNumRef++)
{
uiCostTempL0[iNumRef] = std::numeric_limits<Distortion>::max();
}
UInt uiBitsTempL0[MAX_NUM_REF];
TComMv mvValidList1;
Int refIdxValidList1 = 0;
UInt bitsValidList1 = MAX_UINT;
Distortion costValidList1 = std::numeric_limits<Distortion>::max();
xGetBlkBits( ePartSize, pcCU->getSlice()->isInterP(), iPartIdx, uiLastMode, uiMbBits); // 计算当前PU划分模式所需要消耗的编码比特数
pcCU->getPartIndexAndSize( iPartIdx, uiPartAddr, iRoiWidth, iRoiHeight ); // 计算当前PU块的地址 uiPartAddr, 宽度 iRoiWidth, 高度 iRoiHeight
#if AMP_MRG
Bool bTestNormalMC = true; // false--只对Merge模式进行评估选择
if ( bUseMRG && pcCU->getWidth( 0 ) > 8 && iNumPart == 2 ) // *** bUseMRG为true时,才可能会将bTestNormalMC设为false,关键代码段
{
bTestNormalMC = false; // *** bTestNormalMC 设为 false,会跳过对PU的多参考帧运动估计搜索过程,只对Merge模式进行评估选择,注意 bTestNormalMC为true时,也会对Merge模式进行评估
}
if (bTestNormalMC)
{
#endif
// Uni-directional prediction
for ( Int iRefList = 0; iRefList < iNumPredDir; iRefList++ ) // ***
{
RefPicList eRefPicList = ( iRefList ? REF_PIC_LIST_1 : REF_PIC_LIST_0 );
for ( Int iRefIdxTemp = 0; iRefIdxTemp < pcCU->getSlice()->getNumRefIdx(eRefPicList); iRefIdxTemp++ )
{
uiBitsTemp = uiMbBits[iRefList];
if ( pcCU->getSlice()->getNumRefIdx(eRefPicList) > 1 )
{
uiBitsTemp += iRefIdxTemp+1;
if ( iRefIdxTemp == pcCU->getSlice()->getNumRefIdx(eRefPicList)-1 )
{
uiBitsTemp--;
}
}
xEstimateMvPredAMVP( pcCU, pcOrgYuv, iPartIdx, eRefPicList, iRefIdxTemp, cMvPred[iRefList][iRefIdxTemp], false, &biPDistTemp); // 对当前参考帧(iRefIdxTemp) 进行AMVP, 获得最优MVP
aaiMvpIdx[iRefList][iRefIdxTemp] = pcCU->getMVPIdx(eRefPicList, uiPartAddr);
aaiMvpNum[iRefList][iRefIdxTemp] = pcCU->getMVPNum(eRefPicList, uiPartAddr);
if(pcCU->getSlice()->getMvdL1ZeroFlag() && iRefList==1 && biPDistTemp