HM编码器代码阅读(35)——帧内预测(二)帧内预测总流程

时间:2022-10-15 11:34:46

亮度块的帧内预测


帧内预测的流程

注意:如果没有特别说明,操作的对象都是亮度块(Luma)

1、遍历所有的预测模式,得到每种模式下的残差信号,再对残差信号进行Hadamard变换计算SATD值

2、利用SATD值计算每种预测模式的率失真代价,选取率失真代价最小的几种模式(与PU大小相关)为预测模式集
3、将已编码相邻块的预测模式补充到预测模式集中
4、遍历模式集合中的所有模式,并对残差信号进行正常编码(熵编码),计算率失真代价
5、选取最优的预测模式作为该PU的最优模式

6、当亮度块的模式确定之后,把该模式以及DC、planar、水平方向模式、垂直方向模式作为色度块的候选模式,选取最优的模式即可


亮度块的帧内预测的入口函数

入口函数是estIntraPredQT,流程如下:
1、遍历CU下面的每一个PU,对于每一个PU,进行下面的操作
2、先初始化访问相邻像素块的工具类
3、调用initAdiPattern,对参考像素值进行预处理和滤波
4、首先计算需要进行完整RD率失真优化操作的模式的数量numModesForFullRD,可以根据PU的宽度来得到
5、检测快速搜索标识doFastSearch,通过比较numModesForFullRD和帧内预测模式的总数量(35种)是否相等得到
,如果不相等,那么使用快速搜索模式,否则使用普通模式;(一般来说doFastSearch都是true)
6、如果doFastSearch是true,那么进行下面的操作:
    (1)遍历帧内预测的35种模式,对于每一种模式进行帧内预测,然后使用Hadamard变换计算SATD值,
利用SATD的值计算每种模式的率失真代价
    (2)从35个模式中选取numModesForFullRD个代价比较优的模式,组成模式候选列表
    (3)调用getIntraDirLumaPredictor,根据相邻块的预测模式来对当前块的模式进行预测,若干预测模式
    (4)遍历预测模式,如果它不在候选模式列表中,那么把它添加到候选模式列表中
7、如果doFastSearch是false,那么表示numModesForFullRD的数量是35,那么所有的帧内预测模式都被添加到候选模式列表中
8、用一句话表述步骤6和7,那就是,快速搜索模式下,只选取几种最优可能的模式作为候选模式,普通模式下,所有的帧内预测模式都是候选模式
9、遍历候选模式列表,对于其中的每一模式,进行下面的操作:
    (1)调用xRecurIntraCodingQT,进行预测变换量化,注意该函数倒数第二个参数是true,表示会按照四叉树的方式继续向下划分
    (2)根据率失真代价选取最优的模式
10、经过步骤8,我们已经选取了最优的模式,但是该模式下的编码块是继续向下划分的,因此,我们还要计算该模式下,编码块不向下划分的时候的代价(调用xRecurIntraCodingQT,倒数第二个参数设置为false),通过比较编码块划分和不划分两种情况,得到最优的参数和模式
12、经过了上面的步骤之后,我们已经得到最优模式和变换系数了,此时我们应该重建当前块,因为后面的PU需要使用该重建块作为预测块。

/*
** 亮度块的帧内预测的入口函数
*/
Void TEncSearch::estIntraPredQT( TComDataCU* pcCU,
TComYuv* pcOrgYuv,
TComYuv* pcPredYuv,
TComYuv* pcResiYuv,
TComYuv* pcRecoYuv,
UInt& ruiDistC,
Bool bLumaOnly )
{
// 删除无关代码*****

// 候选的cost列表
Double CandCostList[ FAST_UDI_MAX_RDMODE_NUM ];

//===== set QP and clear Cbf =====
// 设置QP参数,清理Cbf
if ( pcCU->getSlice()->getPPS()->getUseDQP() == true)
{
pcCU->setQPSubParts( pcCU->getQP(0), 0, uiDepth );
}
else
{
// 进入此处
pcCU->setQPSubParts( pcCU->getSlice()->getSliceQp(), 0, uiDepth );
}

//===== loop over partitions =====
UInt uiPartOffset = 0;

// 循环处理CU下的每一个预测块PU
for( UInt uiPU = 0; uiPU < uiNumPU; uiPU++, uiPartOffset += uiQNumParts )
{
//===== init pattern for luma prediction =====
Bool bAboveAvail = false; // 上面的块是否有效
Bool bLeftAvail = false; // 左边的块是否有效

// 初始化访问相邻块的工具类
pcCU->getPattern()->initPattern ( pcCU, uiInitTrDepth, uiPartOffset );

// 这个函数很重要:主要是在着帧内预测之前,使用重建后的YUV图像对当前PU的相邻样点进行滤波,为接下来的进行的角度预测
// 提供参考样点值
pcCU->getPattern()->initAdiPattern( pcCU, uiPartOffset, uiInitTrDepth, m_piYuvExt, m_iYuvExtStride, m_iYuvExtHeight, bAboveAvail, bLeftAvail );

//===== determine set of modes to be tested (using prediction signal only) =====
// 35种帧内预测模式
Int numModesAvailable = 35; //total number of Intra modes

// 在原始的YUV中获取获亮度的地址
Pel* piOrg = pcOrgYuv ->getLumaAddr( uiPU, uiWidth );
// 在预测的YUV中获取亮度的地址
Pel* piPred = pcPredYuv->getLumaAddr( uiPU, uiWidth );

// 偏移
UInt uiStride = pcPredYuv->getStride(); // 8

UInt uiRdModeList[FAST_UDI_MAX_RDMODE_NUM];
Int numModesForFullRD = g_aucIntraModeNumFast[ uiWidthBit ];//8

Bool doFastSearch = (numModesForFullRD != numModesAvailable);//true

// 使用快速搜索模式
if (doFastSearch)
{
assert(numModesForFullRD < numModesAvailable);

for( Int i=0; i < numModesForFullRD; i++ )
{
// 用于存储每一种模式的消耗
CandCostList[ i ] = MAX_DOUBLE;
}
CandNum = 0;

// 遍历35种帧内预测模式,选取若干个代价比较小的模式作为后续处理的模式
for( Int modeIdx = 0; modeIdx < numModesAvailable; modeIdx++ ) // 总共有35种模式,numModesAvailable = 35
{
UInt uiMode = modeIdx;

// 对亮度块进行预测
predIntraLumaAng( pcCU->getPattern(), uiMode, piPred, uiStride, uiWidth, uiHeight, bAboveAvail, bLeftAvail );

// use hadamard transform here
// 使用hadamard变换,计算SATD的值
UInt uiSad = m_pcRdCost->calcHAD(g_bitDepthY, piOrg, uiStride, piPred, uiStride, uiWidth, uiHeight );

UInt iModeBits = xModeBitsIntra( pcCU, uiMode, uiPU, uiPartOffset, uiDepth, uiInitTrDepth );

// 计算此种模式的代价
Double cost = (Double)uiSad + (Double)iModeBits * m_pcRdCost->getSqrtLambda();

// 更新候选列表
CandNum += xUpdateCandList( uiMode, cost, numModesForFullRD, uiRdModeList, CandCostList );
}

#if FAST_UDI_USE_MPM
Int uiPreds[3] = {-1, -1, -1};
Int iMode = -1;
// 根据相邻块的预测模式来对当前块的模式进行预测,得到若干模式(称为预测模式),存放在uiPreds中
Int numCand = pcCU->getIntraDirLumaPredictor( uiPartOffset, uiPreds, &iMode );
if( iMode >= 0 )
{
// 将候选列表的索引设置为此模式
numCand = iMode;
}

// 遍历预测的模式,如果它不在模式候选列表中,那么把它添加到模式候选列表中
for( Int j=0; j < numCand; j++)
{
Bool mostProbableModeIncluded = false;
Int mostProbableMode = uiPreds[j];

for( Int i=0; i < numModesForFullRD; i++)
{
mostProbableModeIncluded |= (mostProbableMode == uiRdModeList[i]);
}
if (!mostProbableModeIncluded)
{
uiRdModeList[numModesForFullRD++] = mostProbableMode;
}
}
#endif // FAST_UDI_USE_MPM
}
else
{
for( Int i=0; i < numModesForFullRD; i++)
{
uiRdModeList[i] = i;
}
}

//===== check modes (using r-d costs) =====
#if HHI_RQT_INTRA_SPEEDUP_MOD
UInt uiSecondBestMode = MAX_UINT;
Double dSecondBestPUCost = MAX_DOUBLE;
#endif

UInt uiBestPUMode = 0;
UInt uiBestPUDistY = 0;
UInt uiBestPUDistC = 0;
Double dBestPUCost = MAX_DOUBLE;

// 遍历候选集中的模式
for( UInt uiMode = 0; uiMode < numModesForFullRD; uiMode++ )
{
// set luma prediction mode
UInt uiOrgMode = uiRdModeList[uiMode];

pcCU->setLumaIntraDirSubParts ( uiOrgMode, uiPartOffset, uiDepth + uiInitTrDepth );

// set context models
// 设置上下文模型
m_pcRDGoOnSbacCoder->load( m_pppcRDSbacCoder[uiDepth][CI_CURR_BEST] );

// determine residual for partition
UInt uiPUDistY = 0;
UInt uiPUDistC = 0;
Double dPUCost = 0.0;
#if HHI_RQT_INTRA_SPEEDUP

// 通过多候选模式进行预测、变换、量化等操作来计算代价
// 注意倒数第二个参数bCheckFirst是true,表示会继续按照四叉树的方式向下划分
xRecurIntraCodingQT( pcCU, uiInitTrDepth, uiPartOffset, bLumaOnly, pcOrgYuv, pcPredYuv, pcResiYuv, uiPUDistY, uiPUDistC, true, dPUCost );
// 重要函数end
#else
xRecurIntraCodingQT( pcCU, uiInitTrDepth, uiPartOffset, bLumaOnly, pcOrgYuv, pcPredYuv, pcResiYuv, uiPUDistY, uiPUDistC, dPUCost );
#endif

// check r-d cost
// 从候选列表中选取最优的模式
if( dPUCost < dBestPUCost )
{
#if HHI_RQT_INTRA_SPEEDUP_MOD
uiSecondBestMode = uiBestPUMode;
dSecondBestPUCost = dBestPUCost;
#endif
uiBestPUMode = uiOrgMode;
uiBestPUDistY = uiPUDistY;
uiBestPUDistC = uiPUDistC;
dBestPUCost = dPUCost;

xSetIntraResultQT( pcCU, uiInitTrDepth, uiPartOffset, bLumaOnly, pcRecoYuv );

UInt uiQPartNum = pcCU->getPic()->getNumPartInCU() >> ( ( pcCU->getDepth(0) + uiInitTrDepth ) << 1 );
::memcpy( m_puhQTTempTrIdx, pcCU->getTransformIdx() + uiPartOffset, uiQPartNum * sizeof( UChar ) );
::memcpy( m_puhQTTempCbf[0], pcCU->getCbf( TEXT_LUMA ) + uiPartOffset, uiQPartNum * sizeof( UChar ) );
::memcpy( m_puhQTTempCbf[1], pcCU->getCbf( TEXT_CHROMA_U ) + uiPartOffset, uiQPartNum * sizeof( UChar ) );
::memcpy( m_puhQTTempCbf[2], pcCU->getCbf( TEXT_CHROMA_V ) + uiPartOffset, uiQPartNum * sizeof( UChar ) );
::memcpy( m_puhQTTempTransformSkipFlag[0], pcCU->getTransformSkip(TEXT_LUMA) + uiPartOffset, uiQPartNum * sizeof( UChar ) );
::memcpy( m_puhQTTempTransformSkipFlag[1], pcCU->getTransformSkip(TEXT_CHROMA_U) + uiPartOffset, uiQPartNum * sizeof( UChar ) );
::memcpy( m_puhQTTempTransformSkipFlag[2], pcCU->getTransformSkip(TEXT_CHROMA_V) + uiPartOffset, uiQPartNum * sizeof( UChar ) );
}
#if HHI_RQT_INTRA_SPEEDUP_MOD
else if( dPUCost < dSecondBestPUCost )
{
uiSecondBestMode = uiOrgMode;
dSecondBestPUCost = dPUCost;
}
#endif
} // Mode loop

#if HHI_RQT_INTRA_SPEEDUP
#if HHI_RQT_INTRA_SPEEDUP_MOD
for( UInt ui =0; ui < 2; ++ui )
#endif
{
#if HHI_RQT_INTRA_SPEEDUP_MOD
UInt uiOrgMode = ui ? uiSecondBestMode : uiBestPUMode;
if( uiOrgMode == MAX_UINT )
{
break;
}
#else
UInt uiOrgMode = uiBestPUMode;
#endif

pcCU->setLumaIntraDirSubParts ( uiOrgMode, uiPartOffset, uiDepth + uiInitTrDepth );

// set context models
m_pcRDGoOnSbacCoder->load( m_pppcRDSbacCoder[uiDepth][CI_CURR_BEST] );

// determine residual for partition
UInt uiPUDistY = 0;
UInt uiPUDistC = 0;
Double dPUCost = 0.0;

// 现在已经知道最优模式了,使用最优模式对PU进行预测,然后变换量化等,计算代价
// 注意倒数第二个参数bCheckFirst是false,表示当前PU不再进行划分,即只处理当前深度的PU
xRecurIntraCodingQT( pcCU, uiInitTrDepth, uiPartOffset, bLumaOnly, pcOrgYuv, pcPredYuv, pcResiYuv, uiPUDistY, uiPUDistC, false, dPUCost );

// 检测同一种模式下,bCheckFirst为true和false的情况下,那个的代价更优
if( dPUCost < dBestPUCost )
{
uiBestPUMode = uiOrgMode;
uiBestPUDistY = uiPUDistY;
uiBestPUDistC = uiPUDistC;
dBestPUCost = dPUCost;

xSetIntraResultQT( pcCU, uiInitTrDepth, uiPartOffset, bLumaOnly, pcRecoYuv );

UInt uiQPartNum = pcCU->getPic()->getNumPartInCU() >> ( ( pcCU->getDepth(0) + uiInitTrDepth ) << 1 );
::memcpy( m_puhQTTempTrIdx, pcCU->getTransformIdx() + uiPartOffset, uiQPartNum * sizeof( UChar ) );
::memcpy( m_puhQTTempCbf[0], pcCU->getCbf( TEXT_LUMA ) + uiPartOffset, uiQPartNum * sizeof( UChar ) );
::memcpy( m_puhQTTempCbf[1], pcCU->getCbf( TEXT_CHROMA_U ) + uiPartOffset, uiQPartNum * sizeof( UChar ) );
::memcpy( m_puhQTTempCbf[2], pcCU->getCbf( TEXT_CHROMA_V ) + uiPartOffset, uiQPartNum * sizeof( UChar ) );
::memcpy( m_puhQTTempTransformSkipFlag[0], pcCU->getTransformSkip(TEXT_LUMA) + uiPartOffset, uiQPartNum * sizeof( UChar ) );
::memcpy( m_puhQTTempTransformSkipFlag[1], pcCU->getTransformSkip(TEXT_CHROMA_U) + uiPartOffset, uiQPartNum * sizeof( UChar ) );
::memcpy( m_puhQTTempTransformSkipFlag[2], pcCU->getTransformSkip(TEXT_CHROMA_V) + uiPartOffset, uiQPartNum * sizeof( UChar ) );
}
} // Mode loop
#endif

//--- update overall distortion ---
uiOverallDistY += uiBestPUDistY;
uiOverallDistC += uiBestPUDistC;

//--- update transform index and cbf ---
UInt uiQPartNum = pcCU->getPic()->getNumPartInCU() >> ( ( pcCU->getDepth(0) + uiInitTrDepth ) << 1 );
::memcpy( pcCU->getTransformIdx() + uiPartOffset, m_puhQTTempTrIdx, uiQPartNum * sizeof( UChar ) );
::memcpy( pcCU->getCbf( TEXT_LUMA ) + uiPartOffset, m_puhQTTempCbf[0], uiQPartNum * sizeof( UChar ) );
::memcpy( pcCU->getCbf( TEXT_CHROMA_U ) + uiPartOffset, m_puhQTTempCbf[1], uiQPartNum * sizeof( UChar ) );
::memcpy( pcCU->getCbf( TEXT_CHROMA_V ) + uiPartOffset, m_puhQTTempCbf[2], uiQPartNum * sizeof( UChar ) );
::memcpy( pcCU->getTransformSkip(TEXT_LUMA) + uiPartOffset, m_puhQTTempTransformSkipFlag[0], uiQPartNum * sizeof( UChar ) );
::memcpy( pcCU->getTransformSkip(TEXT_CHROMA_U) + uiPartOffset, m_puhQTTempTransformSkipFlag[1], uiQPartNum * sizeof( UChar ) );
::memcpy( pcCU->getTransformSkip(TEXT_CHROMA_V) + uiPartOffset, m_puhQTTempTransformSkipFlag[2], uiQPartNum * sizeof( UChar ) );

//--- set reconstruction for next intra prediction blocks ---
// 变换量化/反变换反量化都已经处理完成了,那么设置重建块
if( uiPU != uiNumPU - 1 )
{
Bool bSkipChroma = false;
Bool bChromaSame = false;
UInt uiLog2TrSize = g_aucConvertToBit[ pcCU->getSlice()->getSPS()->getMaxCUWidth() >> ( pcCU->getDepth(0) + uiInitTrDepth ) ] + 2;
if( !bLumaOnly && uiLog2TrSize == 2 )
{
assert( uiInitTrDepth > 0 );
bSkipChroma = ( uiPU != 0 );
bChromaSame = true;
}

UInt uiCompWidth = pcCU->getWidth ( 0 ) >> uiInitTrDepth;
UInt uiCompHeight = pcCU->getHeight( 0 ) >> uiInitTrDepth;
UInt uiZOrder = pcCU->getZorderIdxInCU() + uiPartOffset;
Pel* piDes = pcCU->getPic()->getPicYuvRec()->getLumaAddr( pcCU->getAddr(), uiZOrder );
UInt uiDesStride = pcCU->getPic()->getPicYuvRec()->getStride();
Pel* piSrc = pcRecoYuv->getLumaAddr( uiPartOffset );
UInt uiSrcStride = pcRecoYuv->getStride();
for( UInt uiY = 0; uiY < uiCompHeight; uiY++, piSrc += uiSrcStride, piDes += uiDesStride )
{
for( UInt uiX = 0; uiX < uiCompWidth; uiX++ )
{
piDes[ uiX ] = piSrc[ uiX ];
}
}
if( !bLumaOnly && !bSkipChroma )
{
if( !bChromaSame )
{
uiCompWidth >>= 1;
uiCompHeight >>= 1;
}
piDes = pcCU->getPic()->getPicYuvRec()->getCbAddr( pcCU->getAddr(), uiZOrder );
uiDesStride = pcCU->getPic()->getPicYuvRec()->getCStride();
piSrc = pcRecoYuv->getCbAddr( uiPartOffset );
uiSrcStride = pcRecoYuv->getCStride();
for( UInt uiY = 0; uiY < uiCompHeight; uiY++, piSrc += uiSrcStride, piDes += uiDesStride )
{
for( UInt uiX = 0; uiX < uiCompWidth; uiX++ )
{
piDes[ uiX ] = piSrc[ uiX ];
}
}
piDes = pcCU->getPic()->getPicYuvRec()->getCrAddr( pcCU->getAddr(), uiZOrder );
piSrc = pcRecoYuv->getCrAddr( uiPartOffset );
for( UInt uiY = 0; uiY < uiCompHeight; uiY++, piSrc += uiSrcStride, piDes += uiDesStride )
{
for( UInt uiX = 0; uiX < uiCompWidth; uiX++ )
{
piDes[ uiX ] = piSrc[ uiX ];
}
}
}
}

//=== update PU data ====
pcCU->setLumaIntraDirSubParts ( uiBestPUMode, uiPartOffset, uiDepth + uiInitTrDepth );
pcCU->copyToPic ( uiDepth, uiPU, uiInitTrDepth );
} // PU loop


// 设置一些Cbf、重置熵编码上下文之类的操作

// 删除无关代码*****
}