HM编码器代码阅读(38)——帧内预测(五)帧内预测之正式的预测操作

时间:2022-06-09 11:35:06

正式的预测操作


    在前面的操作中,我们已经得到了模式候选列表,但是我们的目的是要得到一个最优的模式,因此我们还需要对这个列表中的模式进行遍历,对于每一个模式,进行预测操作,为了计算率失真代价还必须进行变换量化,根据率失真代价来选取最优的预测模式。得到最优的预测模式的同时,我们也得到了变换系数(因为预测之后我们还进行了变换量化操作)。

    执行正式的预测操作的相关代码在estIntraPredQT中,主要的功能是遍历模式候选列表中的所有模式,对每一个模式进行预测(顺带变换量化),选出最优的预测模式

// 遍历候选集中的模式
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



正式预测操作的入口函数

入口函数是xRecurIntraCodingQT,它的流程如下:

一、检测bCheckFull标识是否为true,如果是true,那么进行下面操作:
    1、检查TransformSkip标识是否为true,如果是true(表示将会跳过变换步骤),进行下面操作
        (1)进行两次遍历,每一次遍历都调用xIntraCodingLumaBlk、xIntraCodingChromaBlk对三个分量进行预测、计算残差(没有变换步骤)、量化
        (2)选取两次遍历中更优的模式
    2、如果TransformSkip是false,那么进行下面操作:
        (1)调用xIntraCodingLumaBlk、xIntraCodingChromaBlk对三个分量进行预测、变换、量化
二、检查bCheckSplit表示是否为true,如果为true,那么对子PU进行递归处理

/*
** 正式的预测操作(顺便进行了变换量化)
** 为了清晰易懂,把不太相关的代码删除,只保留了清晰的架构
*/
Void TEncSearch::xRecurIntraCodingQT( TComDataCU* pcCU,
UInt uiTrDepth,
UInt uiAbsPartIdx,
Bool bLumaOnly,
TComYuv* pcOrgYuv,
TComYuv* pcPredYuv,
TComYuv* pcResiYuv,
UInt& ruiDistY,
UInt& ruiDistC,
#if HHI_RQT_INTRA_SPEEDUP
Bool bCheckFirst,
#endif
Double& dRDCost )
{

// 删除不太重要的代码******

Bool bCheckFull = ( uiLog2TrSize <= pcCU->getSlice()->getSPS()->getQuadtreeTULog2MaxSize() );
Bool bCheckSplit = ( uiLog2TrSize > pcCU->getQuadtreeTULog2MinSizeInCU(uiAbsPartIdx) );


// 删除不太重要的代码******

Bool noSplitIntraMaxTuSize = bCheckFull;
if(m_pcEncCfg->getRDpenalty() && ! isIntraSlice)
{
// in addition don't check split if TU size is less or equal to 16x16 TU size for non-intra slice
noSplitIntraMaxTuSize = ( uiLog2TrSize <= min(maxTuSize,4) );

// if maximum RD-penalty don't check TU size 32x32
if(m_pcEncCfg->getRDpenalty()==2)
{
bCheckFull = ( uiLog2TrSize <= min(maxTuSize,4));
}
}
if( bCheckFirst && noSplitIntraMaxTuSize )
{
bCheckSplit = false;
}

// 删除不太重要的代码******

Bool checkTransformSkip = pcCU->getSlice()->getPPS()->getUseTransformSkip();
UInt widthTransformSkip = pcCU->getWidth ( 0 ) >> uiTrDepth;
UInt heightTransformSkip = pcCU->getHeight( 0 ) >> uiTrDepth;

// 删除不太重要的代码******

checkTransformSkip &= (widthTransformSkip == 4 && heightTransformSkip == 4);
checkTransformSkip &= (!pcCU->getCUTransquantBypass(0));
if ( m_pcEncCfg->getUseTransformSkipFast() )
{
checkTransformSkip &= (pcCU->getPartitionSize(uiAbsPartIdx)==SIZE_NxN);
}

if( bCheckFull )
{
// skip模式为真
if(checkTransformSkip == true)
{
// 删除不太重要的代码******

// 遍历两次也是为了选取最优的模式,modeId能够决定xIntraCodingLumaBlk的最后一个参数,该参数控制了预测像素如何生成
for(Int modeId = firstCheckId; modeId < 2; modeId ++)
{
// 删除不太重要的代码******

// 亮度块的预测、变换和量化
xIntraCodingLumaBlk( pcCU, uiTrDepth, uiAbsPartIdx, pcOrgYuv, pcPredYuv, pcResiYuv, singleDistYTmp,default0Save1Load2);

singleCbfYTmp = pcCU->getCbf( uiAbsPartIdx, TEXT_LUMA, uiTrDepth );
//----- code chroma blocks with given intra prediction mode and store Cbf-----
if( !bLumaOnly )
{
// 删除不太重要的代码******

// 色度块的预测、变换和量化
xIntraCodingChromaBlk ( pcCU, uiTrDepth, uiAbsPartIdx, pcOrgYuv, pcPredYuv, pcResiYuv, singleDistCTmp, 0, default0Save1Load2);
xIntraCodingChromaBlk ( pcCU, uiTrDepth, uiAbsPartIdx, pcOrgYuv, pcPredYuv, pcResiYuv, singleDistCTmp, 1, default0Save1Load2);

// 删除不太重要的代码******
}

// 删除不太重要的代码******

// 代价更新
if(singleCostTmp < dSingleCost)
{
dSingleCost = singleCostTmp;
uiSingleDistY = singleDistYTmp;
uiSingleDistC = singleDistCTmp;
uiSingleCbfY = singleCbfYTmp;
uiSingleCbfU = singleCbfUTmp;
uiSingleCbfV = singleCbfVTmp;
bestModeId = modeId;
if(bestModeId == firstCheckId)
{
xStoreIntraResultQT(pcCU, uiTrDepth, uiAbsPartIdx,bLumaOnly );
m_pcRDGoOnSbacCoder->store( m_pppcRDSbacCoder[ uiFullDepth ][ CI_TEMP_BEST ] );
}
}

// 删除不太重要的代码******
}

// 删除不太重要的代码******
}
else
{
// 删除不太重要的代码******

xIntraCodingLumaBlk( pcCU, uiTrDepth, uiAbsPartIdx, pcOrgYuv, pcPredYuv, pcResiYuv, uiSingleDistY );

// 删除不太重要的代码******

if( !bLumaOnly )
{
// 删除不太重要的代码******

xIntraCodingChromaBlk ( pcCU, uiTrDepth, uiAbsPartIdx, pcOrgYuv, pcPredYuv, pcResiYuv, uiSingleDistC, 0 );
xIntraCodingChromaBlk ( pcCU, uiTrDepth, uiAbsPartIdx, pcOrgYuv, pcPredYuv, pcResiYuv, uiSingleDistC, 1 );

// 删除不太重要的代码******
}

// 删除不太重要的代码******
}
}

// 当前块是否向下继续划分为4个子块,如果是,那么就递归处理
if( bCheckSplit )
{
// 删除不太重要的代码******

for( UInt uiPart = 0; uiPart < 4; uiPart++, uiAbsPartIdxSub += uiQPartsDiv )
{
#if HHI_RQT_INTRA_SPEEDUP
// 递归调用
xRecurIntraCodingQT( pcCU, uiTrDepth + 1, uiAbsPartIdxSub, bLumaOnly, pcOrgYuv, pcPredYuv, pcResiYuv, uiSplitDistY, uiSplitDistC, bCheckFirst, dSplitCost );
#else
xRecurIntraCodingQT( pcCU, uiTrDepth + 1, uiAbsPartIdxSub, bLumaOnly, pcOrgYuv, pcPredYuv, pcResiYuv, uiSplitDistY, uiSplitDistC, dSplitCost );
#endif

// 删除不太重要的代码******
}

// 删除不太重要的代码******

//----- determine rate and r-d cost -----
UInt uiSplitBits = xGetIntraBitsQT( pcCU, uiTrDepth, uiAbsPartIdx, true, !bLumaOnly, false );
dSplitCost = m_pcRdCost->calcRdCost( uiSplitBits, uiSplitDistY + uiSplitDistC );

//===== compare and set best =====
if( dSplitCost < dSingleCost )
{
//--- update cost ---
ruiDistY += uiSplitDistY;
ruiDistC += uiSplitDistC;
dRDCost += dSplitCost;
return;
}

// 删除不太重要的代码******

//--- set reconstruction for next intra prediction blocks ---

// 到这里是像素块的重建操作,这里把相关的代码删了
}
ruiDistY += uiSingleDistY;
ruiDistC += uiSingleDistC;
dRDCost += dSingleCost;
}


对某个分量进行预测(顺带变换量化)

流程很简单:

1、先进行预测,得到预测像素

2、计算残差

3、对残差进行变换、量化

4、进行反变换、反量化

5、重建像素块

/*
** 帧内预测的一整套流程:预测+变换量化+反变换反量化+重建像素
** 被xRecurIntraCodingQT调用
*/
Void TEncSearch::xIntraCodingLumaBlk( TComDataCU* pcCU,
UInt uiTrDepth,
UInt uiAbsPartIdx,
TComYuv* pcOrgYuv,
TComYuv* pcPredYuv,
TComYuv* pcResiYuv,
UInt& ruiDist,
Int default0Save1Load2 )
{
// 获取亮度块的预测模式
UInt uiLumaPredMode = pcCU ->getLumaIntraDir ( uiAbsPartIdx );
// 获取深度
UInt uiFullDepth = pcCU ->getDepth ( 0 ) + uiTrDepth;
// 获取宽高
UInt uiWidth = pcCU ->getWidth ( 0 ) >> uiTrDepth;
UInt uiHeight = pcCU ->getHeight ( 0 ) >> uiTrDepth;
// 获取偏移
UInt uiStride = pcOrgYuv ->getStride ();
// 原始的像素地址
Pel* piOrg = pcOrgYuv ->getLumaAddr( uiAbsPartIdx );
// 预测的像素地址
Pel* piPred = pcPredYuv->getLumaAddr( uiAbsPartIdx );
// 残差的像素地址
Pel* piResi = pcResiYuv->getLumaAddr( uiAbsPartIdx );
// 重建的像素地址
Pel* piReco = pcPredYuv->getLumaAddr( uiAbsPartIdx );

UInt uiLog2TrSize = g_aucConvertToBit[ pcCU->getSlice()->getSPS()->getMaxCUWidth() >> uiFullDepth ] + 2;
UInt uiQTLayer = pcCU->getSlice()->getSPS()->getQuadtreeTULog2MaxSize() - uiLog2TrSize;
UInt uiNumCoeffPerInc = pcCU->getSlice()->getSPS()->getMaxCUWidth() * pcCU->getSlice()->getSPS()->getMaxCUHeight() >> ( pcCU->getSlice()->getSPS()->getMaxCUDepth() << 1 );
// 系数(实际是一个int数组)
TCoeff* pcCoeff = m_ppcQTTempCoeffY[ uiQTLayer ] + uiNumCoeffPerInc * uiAbsPartIdx;

#if ADAPTIVE_QP_SELECTION
Int* pcArlCoeff = m_ppcQTTempArlCoeffY[ uiQTLayer ] + uiNumCoeffPerInc * uiAbsPartIdx;
#endif

Pel* piRecQt = m_pcQTTempTComYuv[ uiQTLayer ].getLumaAddr( uiAbsPartIdx );
UInt uiRecQtStride = m_pcQTTempTComYuv[ uiQTLayer ].getStride ();

// Z扫描的顺序
UInt uiZOrder = pcCU->getZorderIdxInCU() + uiAbsPartIdx;
Pel* piRecIPred = pcCU->getPic()->getPicYuvRec()->getLumaAddr( pcCU->getAddr(), uiZOrder );
UInt uiRecIPredStride = pcCU->getPic()->getPicYuvRec()->getStride ();
Bool useTransformSkip = pcCU->getTransformSkip(uiAbsPartIdx, TEXT_LUMA);
//===== init availability pattern =====

// 上方是否有效
Bool bAboveAvail = false;
// 左侧是否有效
Bool bLeftAvail = false;

// default0Save1Load2参数控制了预测像素的生成方式
if(default0Save1Load2 != 2)
{
pcCU->getPattern()->initPattern ( pcCU, uiTrDepth, uiAbsPartIdx );

pcCU->getPattern()->initAdiPattern( pcCU, uiAbsPartIdx, uiTrDepth, m_piYuvExt, m_iYuvExtStride, m_iYuvExtHeight, bAboveAvail, bLeftAvail );

// 预测操作
predIntraLumaAng( pcCU->getPattern(), uiLumaPredMode, piPred, uiStride, uiWidth, uiHeight, bAboveAvail, bLeftAvail );
// save prediction
// 保存预测信息
if(default0Save1Load2 == 1)
{
Pel* pPred = piPred;
Pel* pPredBuf = m_pSharedPredTransformSkip[0];
Int k = 0;
for( UInt uiY = 0; uiY < uiHeight; uiY++ )
{
for( UInt uiX = 0; uiX < uiWidth; uiX++ )
{
pPredBuf[ k ++ ] = pPred[ uiX ];
}
pPred += uiStride;
}
}
}
else
{
// load prediction

// 直接计算预测值!
Pel* pPred = piPred;
Pel* pPredBuf = m_pSharedPredTransformSkip[0];
Int k = 0;
for( UInt uiY = 0; uiY < uiHeight; uiY++ )
{
for( UInt uiX = 0; uiX < uiWidth; uiX++ )
{
pPred[ uiX ] = pPredBuf[ k ++ ];
}
pPred += uiStride;
}
}


//===== get residual signal =====
{
// get residual

// 计算残差
Pel* pOrg = piOrg;
Pel* pPred = piPred;
Pel* pResi = piResi;
for( UInt uiY = 0; uiY < uiHeight; uiY++ )
{
for( UInt uiX = 0; uiX < uiWidth; uiX++ )
{
// 此处计算残差数据
pResi[ uiX ] = pOrg[ uiX ] - pPred[ uiX ];
}
pOrg += uiStride;
pResi += uiStride;
pPred += uiStride;
}
}

// 变换和量化
//===== transform and quantization =====
//--- init rate estimation arrays for RDOQ ---
// 是否跳过变换操作
if( useTransformSkip? m_pcEncCfg->getUseRDOQTS():m_pcEncCfg->getUseRDOQ())
{
// 比特数估计
m_pcEntropyCoder->estimateBit( m_pcTrQuant->m_pcEstBitsSbac, uiWidth, uiWidth, TEXT_LUMA );
}

//--- transform and quantization ---
UInt uiAbsSum = 0;
pcCU ->setTrIdxSubParts ( uiTrDepth, uiAbsPartIdx, uiFullDepth );

m_pcTrQuant->setQPforQuant ( pcCU->getQP( 0 ), TEXT_LUMA, pcCU->getSlice()->getSPS()->getQpBDOffsetY(), 0 );

#if RDOQ_CHROMA_LAMBDA
m_pcTrQuant->selectLambda (TEXT_LUMA);
#endif

// 变换(连同量化一起)
m_pcTrQuant->transformNxN ( pcCU, piResi, uiStride, pcCoeff,
#if ADAPTIVE_QP_SELECTION
pcArlCoeff,
#endif
uiWidth, uiHeight, uiAbsSum, TEXT_LUMA, uiAbsPartIdx,useTransformSkip );

//--- set coded block flag ---
pcCU->setCbfSubParts ( ( uiAbsSum ? 1 : 0 ) << uiTrDepth, TEXT_LUMA, uiAbsPartIdx, uiFullDepth );
//--- inverse transform ---

// uiAbsSum表示变换系数的绝对值只和
if( uiAbsSum )
{
Int scalingListType = 0 + g_eTTable[(Int)TEXT_LUMA];
assert(scalingListType < SCALING_LIST_NUM);

// 反变换
m_pcTrQuant->invtransformNxN( pcCU->getCUTransquantBypass(uiAbsPartIdx), TEXT_LUMA,pcCU->getLumaIntraDir( uiAbsPartIdx ), piResi, uiStride, pcCoeff, uiWidth, uiHeight, scalingListType, useTransformSkip );
}
else
{
Pel* pResi = piResi;
memset( pcCoeff, 0, sizeof( TCoeff ) * uiWidth * uiHeight );
for( UInt uiY = 0; uiY < uiHeight; uiY++ )
{
memset( pResi, 0, sizeof( Pel ) * uiWidth );
pResi += uiStride;
}
}

// 图像重建
//===== reconstruction =====
{
Pel* pPred = piPred;
Pel* pResi = piResi;
Pel* pReco = piReco;
Pel* pRecQt = piRecQt;
Pel* pRecIPred = piRecIPred;
for( UInt uiY = 0; uiY < uiHeight; uiY++ )
{
for( UInt uiX = 0; uiX < uiWidth; uiX++ )
{
pReco [ uiX ] = ClipY( pPred[ uiX ] + pResi[ uiX ] );
pRecQt [ uiX ] = pReco[ uiX ];
pRecIPred[ uiX ] = pReco[ uiX ];
}
pPred += uiStride;
pResi += uiStride;
pReco += uiStride;
pRecQt += uiRecQtStride;
pRecIPred += uiRecIPredStride;
}
}

//===== update distortion =====
// 失真代价更新!
ruiDist += m_pcRdCost->getDistPart(g_bitDepthY, piReco, uiStride, piOrg, uiStride, uiWidth, uiHeight );
}


预测函数

流程也很简单:

1、判断模式属于哪一类:planar模式、水平类型的模式、垂直类型的模式、DC模式

2、根据模式来调用不同的函数,进行预测

// 亮度块的帧内预测
Void TComPrediction::predIntraLumaAng(TComPattern* pcTComPattern, UInt uiDirMode, Pel* piPred, UInt uiStride, Int iWidth, Int iHeight, Bool bAbove, Bool bLeft )
{
Pel *pDst = piPred;
Int *ptrSrc;

assert( g_aucConvertToBit[ iWidth ] >= 0 ); // 4x 4
assert( g_aucConvertToBit[ iWidth ] <= 5 ); // 128x128
assert( iWidth == iHeight );

// 获取数据的指针
ptrSrc = pcTComPattern->getPredictorPtr( uiDirMode, g_aucConvertToBit[ iWidth ] + 2, m_piYuvExt );

// get starting pixel in block
// 获取块中的开始像素
Int sw = 2 * iWidth + 1; // 9

// Create the prediction
// 如果指定了planar模式
if ( uiDirMode == PLANAR_IDX )
{
// planar预测模式
xPredIntraPlanar( ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight );
}
else
{
// 没有指定planar模式
// 区分水平或者垂直模式进行预测
if ( (iWidth > 16) || (iHeight > 16) )
{
xPredIntraAng(g_bitDepthY, ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight, uiDirMode, bAbove, bLeft, false );
}
else
{
xPredIntraAng(g_bitDepthY, ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight, uiDirMode, bAbove, bLeft, true );

// 观察是否为DC模式
if( (uiDirMode == DC_IDX ) && bAbove && bLeft )
{
// 对DC模式进行滤波
xDCPredFiltering( ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight);
}
}
}
}


预测的实现细节

下面这些函数基本都是某个公式的实现,详细信息请参考标准

planar模式:xPredIntraPlanar

DC模式和其他模式:xPredIntraAng


变换和量化

处理变换和量化的函数是transformNxN,这个函数等讲解变换量化的时候再细讲