xIntraCodingTUBlock()函数是帧内预测函数的一个关键函数,里面进行亮度预测模式的具体实现,以及亮度和色度的残差的变换以及量化,该函数是亮度预测和色度预测都要用的统一函数,今天抽空讲一下该函数的代码细节。
该函数的主要流程如下:
一、 初始化各种参数,定义一些变量。
二、 如果是亮度分量,则进行亮度分量的预测编码(MIp模式或者传统的角度预测)
三、 预测结束之后计算残差(非联合模式下,Cb和Cr分量预测块分别进行残差计算;若为联合模式,首先只对Cb分量预测块进行残差计算),这里残差的计算需要注意一下,首先定义一个piResi,将原始数据复制到piResi中,copyFrom( piOrg );以便后面减去预测值计算残差用。然后再减去预测值得到残差值,subtract(tmpPred)。
四、 对残差进行变换和量化,大致步骤如下:
- 根据不同的颜色分量选择合适的QP和Lambda,
- 如果当前颜色分量的残差是单独编码,则对该残差调用scaleSignal()函数进行适当的微调;
- 针对联合色度模式,再为Cr分量预测块单独进行一些初始化的操作;
- 如果联合模式启用,首先获取Cr块的残差,然后用之前求好的Cb的残差减去Cr的残差求平均:jointResi = (cbResi - crResi)/2,此处的操作是通过下面的函数实现的,piResi.subtractAndHalve( crResi );这里有关联合色度残差编码模式的具体技术细节我早之前的博客讲过了,链接如下:H.266/VVC相关技术学习笔记:色度残差联合编码技术
五、 如果是联合模式,则为了确保联合模式能够保留单独模式在两个分量的色度块具有相同的残差,要将Lambda设置为更加宽松一些,就是设置Lambda更小一些;若不是联合模式,Lambda的值应该设置大一些
六、 调用m_pcTrQuant->transformNxN()函数对残差信号进行变换和量化,该函数是残差信号的变换以及量化的主函数入口。
七、 然后调用m_pcTrQuant->invTransformNxN()函数对变换量化编码之后的残差信号进行反量化反变换得到一个解码的残差。最后调用piReco.reconstruct()函数计算当前块的重建值,用于计算失真,在外层函数计算RDcost用。这里需要注意的是若为传统模式,则在外层有两次Cb和Cr的循环,计算各自分量块的重建值;若为联合模式,则在外层函数只对Cb预测块进行调用,piResi为联合残差,保存在Cb块内,只计算Cb块的重建值,Cr块的重建值需要在后面单独计算。
八、 这里判断是否是是联合色度模式,如果是,则Cr预测块的解码残差和重建值需要单独计算,Cr的残差等于Cb残差的负值(crResi.copyAndNegate( piResi )),因为这里已经是联合色度模式了,所以piResi里存的就是联合残差。然后再调用crReco.reconstruct(crPred, crResi, cs.slice->clpRng( COMPONENT_Cr ))函数计算Cr块的重建值。
九、 最后更新当前预测块的色度分量的失真,用于外层计算RDcost。
以下是该函数的所有代码,重要的地方我都有详细的备注:
//这是帧内预测函数的一个关键函数,里面进行亮度预测模式的具体实现,以及亮度和色度的残差的变换以及量化。亮度预测和色度预测都要用的统一函数
void IntraSearch::xIntraCodingTUBlock(TransformUnit &tu, const ComponentID &compID, const bool &checkCrossCPrediction, Distortion& ruiDist, const int &default0Save1Load2, uint32_t* numSig, std::vector<TrMode>* trModes, const bool loadTr)
{
if (!tu.blocks[compID].valid())
{
return;
}
CodingStructure &cs = *tu.cs;
#if JVET_N0671_RDCOST_FIX
m_pcRdCost->setChromaFormat(cs.sps->getChromaFormatIdc());
#endif
const CompArea &area = tu.blocks[compID];
const SPS &sps = *cs.sps;
const PPS &pps = *cs.pps;
const ChannelType chType = toChannelType(compID);
const int bitDepth = sps.getBitDepth(chType);
PelBuf piOrg = cs.getOrgBuf (area);//图像原始数据
PelBuf piPred = cs.getPredBuf (area);//图像预测数据
PelBuf piResi = cs.getResiBuf (area);//图像残差数据
PelBuf piOrgResi = cs.getOrgResiBuf(area);//图像原始残差数据
PelBuf piReco = cs.getRecoBuf (area);//图像重建数据
const PredictionUnit &pu = *cs.getPU(area.pos(), chType);
const uint32_t uiChFinalMode = PU::getFinalIntraMode(pu, chType);//最终选中的帧内预测模式
const bool bUseCrossCPrediction = pps.getPpsRangeExtension().getCrossComponentPredictionEnabledFlag() && isChroma( compID ) && PU::isChromaIntraModeCrossCheckMode( pu ) && checkCrossCPrediction;
const bool ccUseRecoResi = m_pcEncCfg->getUseReconBasedCrossCPredictionEstimate();
#if INCLUDE_ISP_CFG_FLAG
const bool ispSplitIsAllowed = sps.getUseISP() && CU::canUseISPSplit( *tu.cu, compID );
#else
const bool ispSplitIsAllowed = CU::canUseISPSplit( *tu.cu, compID );
#endif
//===== init availability pattern =====
#if JVET_N0054_JOINT_CHROMA
//定义联合CbCr残差编码,1为启用联合编码,0为关闭
bool jointCbCr = tu.jointCbCr && compID == COMPONENT_Cb;
//各种亮度预测编码
if ( compID == COMPONENT_Y )
{
#endif
PelBuf sharedPredTS( m_pSharedPredTransformSkip[compID], area );
if( default0Save1Load2 != 2 )
{
initIntraPatternChType( *tu.cu, area );
//===== get prediction signal =====
//获取预测信号,得到对应色度候选模式的预测值
if( compID != COMPONENT_Y && PU::isLMCMode( uiChFinalMode ) )
{
{
xGetLumaRecPixels( pu, area );
}
predIntraChromaLM( compID, piPred, pu, area, uiChFinalMode );
}
else
{
#if JVET_N0217_MATRIX_INTRAPRED
if( PU::isMIP( pu, chType ) )
{
predIntraMip( compID, piPred, pu );
}
else
{
#endif
predIntraAng( compID, piPred, pu );
#if JVET_N0217_MATRIX_INTRAPRED
}
#endif
}
// save prediction
//保存预测块的信息,包括预测值
if( default0Save1Load2 == 1 )
{
sharedPredTS.copyFrom( piPred );
}
}
else
{
// load prediction
piPred.copyFrom( sharedPredTS );
}
#if JVET_N0054_JOINT_CHROMA
}
#endif
DTRACE( g_trace_ctx, D_PRED, "@(%4d,%4d) [%2dx%2d] IMode=%d\n", tu.lx(), tu.ly(), tu.lwidth(), tu.lheight(), uiChFinalMode );
//DTRACE_PEL_BUF( D_PRED, piPred, tu, tu.cu->predMode, COMPONENT_Y );
const Slice &slice = *cs.slice;
bool flag = slice.getReshapeInfo().getUseSliceReshaper() && (slice.isIntra() || (!slice.isIntra() && m_pcReshape->getCTUFlag()));
//如果色度的Reshape被激活
if (flag && slice.getReshapeInfo().getSliceReshapeChromaAdj() && isChroma(compID))
{
const Area area = tu.Y().valid() ? tu.Y() : Area(recalcPosition(tu.chromaFormat, tu.chType, CHANNEL_TYPE_LUMA, tu.blocks[tu.chType].pos()), recalcSize(tu.chromaFormat, tu.chType, CHANNEL_TYPE_LUMA, tu.blocks[tu.chType].size()));
const CompArea &areaY = CompArea(COMPONENT_Y, tu.chromaFormat, area );
PelBuf piPredY;
piPredY = cs.picture->getPredBuf(areaY);
const Pel avgLuma = piPredY.computeAvg();
//定义adj是色度残差的规模
int adj = m_pcReshape->calculateChromaAdj(avgLuma);
tu.setChromaAdj(adj);
}
//获取残差信号
//===== get residual signal =====
piResi.copyFrom( piOrg );//首先将原始数据复制到piResi中,以便后面减去预测值计算残差用
//如果是亮度TU
if (slice.getReshapeInfo().getUseSliceReshaper() && m_pcReshape->getCTUFlag() && compID==COMPONENT_Y)
{
CompArea tmpArea(COMPONENT_Y, area.chromaFormat, Position(0, 0), area.size());
PelBuf tmpPred = m_tmpStorageLCU.getBuf(tmpArea);
tmpPred.copyFrom(piPred);
piResi.rspSignal(m_pcReshape->getFwdLUT());
//减去预测值计算残差
piResi.subtract(tmpPred);
}
else//如果是色度TU
//减去预测值计算残差
piResi.subtract( piPred );
if (pps.getPpsRangeExtension().getCrossComponentPredictionEnabledFlag() && isLuma(compID))
{
piOrgResi.copyFrom (piResi);//piOrgResi和piResi一样
}
if (bUseCrossCPrediction)
{
if (xCalcCrossComponentPredictionAlpha(tu, compID, ccUseRecoResi) == 0)
{
return;
}
CrossComponentPrediction::crossComponentPrediction(tu, compID, cs.getResiBuf(tu.Y()), piResi, piResi, false);
}
//对残差进行变换和量化
//===== transform and quantization =====
//为RDOQ初始化估计数组
//--- init rate estimation arrays for RDOQ ---
//--- transform and quantization ---
TCoeff uiAbsSum = 0;//变换量化后的残差信号的总和
const QpParam cQP(tu, compID);//定义色度的量化参数
#if RDOQ_CHROMA_LAMBDA
m_pcTrQuant->selectLambda(compID);//根据不同的颜色分量选择合适的Lambda
#endif
flag =flag && (tu.blocks[compID].width*tu.blocks[compID].height > 4);
if (flag && isChroma(compID) && slice.getReshapeInfo().getSliceReshapeChromaAdj() )
{
int cResScaleInv = tu.getChromaAdj();
double cResScale = round((double)(1 << CSCALE_FP_PREC) / (double)cResScaleInv);
m_pcTrQuant->setLambda(m_pcTrQuant->getLambda() / (cResScale*cResScale));
#if JVET_N0054_JOINT_CHROMA
//如果联合色度关闭
if ( !jointCbCr ) // Joint CbCr signal is to be scaled in the case of joint chroma
//联合色度残差的情况下,CbCr的残差将会被缩减
#endif
//缩减残差信号
piResi.scaleSignal(cResScaleInv, 1, tu.cu->cs->slice->clpRng(compID));
}
#if JVET_N0054_JOINT_CHROMA
//定义Cr预测块的预测区域、原始Cr值、预测值、残差值、重建值
const CompArea &crArea = tu.blocks [ COMPONENT_Cr ];
PelBuf crOrg = cs.getOrgBuf ( crArea );
PelBuf crPred = cs.getPredBuf ( crArea );
PelBuf crResi = cs.getResiBuf ( crArea );
PelBuf crReco = cs.getRecoBuf ( crArea );
//联合色度模式开启
if ( jointCbCr )
{
// Get Cr prediction and residual、
//获取Cr的预测值和残差
crResi.copyFrom( crOrg );
crResi.subtract( crPred );
// Create joint residual and store it for Cb component: jointResi = (cbResi - crResi)/2
//定义CbCr联合残差并且将其存储到Cb分量的预测块中,jointResi = (cbResi - crResi)/2
//残差相减并且求平均
piResi.subtractAndHalve( crResi );
// Scale the joint signal
//缩放联合残差信号
if ( flag && slice.getReshapeInfo().getSliceReshapeChromaAdj() )
piResi.scaleSignal(tu.getChromaAdj(), 1, tu.cu->cs->slice->clpRng(compID));
// Lambda is loosened for the joint mode with respect to single modes as the same residual is used for both chroma blocks
//为了确保联合模式保留单独模式在两个分量的色度块具有相同的残差这样的特性,要将Lambda设置为更加宽松一些,就是设置Lambda更小一些
m_pcTrQuant->setLambda( 0.60 * m_pcTrQuant->getLambda() );//计算setLambda的值并返回,该Lambda
}
else if ( isChroma(compID) && tu.cu->cs->slice->getSliceQp() > 18 )//如果不是联合模式,且是色度分量且QP>18
m_pcTrQuant->setLambda( 1.10 * m_pcTrQuant->getLambda());//计算setLambda的值并返回,Lambda的值设置大一些
#endif
double diagRatio = 0, horVerRatio = 0;
if( trModes )//具体用什么变换模式
{
//残差信号的变换以及量化的主函数入口
m_pcTrQuant->transformNxN( tu, compID, cQP, trModes, CU::isIntra( *tu.cu ) ? m_pcEncCfg->getIntraMTSMaxCand() : m_pcEncCfg->getInterMTSMaxCand(), ispSplitIsAllowed ? &diagRatio : nullptr, ispSplitIsAllowed ? &horVerRatio : nullptr );
tu.mtsIdx = trModes->at(0).first;
}
m_pcTrQuant->transformNxN( tu, compID, cQP, uiAbsSum, m_CABACEstimator->getCtx(), loadTr, &diagRatio, &horVerRatio );
#if INCLUDE_ISP_CFG_FLAG
if ( !tu.cu->ispMode && isLuma(compID) && ispSplitIsAllowed && tu.mtsIdx == MTS_DCT2_DCT2 && ispSplitIsAllowed )
#else
if ( !tu.cu->ispMode && isLuma(compID) && ispSplitIsAllowed && tu.mtsIdx == MTS_DCT2_DCT2 )
#endif
{
m_intraModeDiagRatio .push_back(diagRatio);
m_intraModeHorVerRatio .push_back(horVerRatio);
m_intraModeTestedNormalIntra.push_back((int)uiChFinalMode);
}
DTRACE( g_trace_ctx, D_TU_ABS_SUM, "%d: comp=%d, abssum=%d\n", DTRACE_GET_COUNTER( g_trace_ctx, D_TU_ABS_SUM ), compID, uiAbsSum );
//--- inverse transform ---
if (uiAbsSum > 0)//若果变换量化后的信号总和存在,则反量化反变换
{
//反量化反变换主函数
m_pcTrQuant->invTransformNxN(tu, compID, piResi, cQP);
}
else//否则初始化为0
{
//初始化残差信号为0
piResi.fill(0);
}
//===== reconstruction =====
//在解码器端计算重建值
if (flag && uiAbsSum > 0 && isChroma(compID) && slice.getReshapeInfo().getSliceReshapeChromaAdj() )
{
//对解码后的残差信号进行微调
piResi.scaleSignal(tu.getChromaAdj(), 0, tu.cu->cs->slice->clpRng(compID));
}
if (bUseCrossCPrediction)
{
CrossComponentPrediction::crossComponentPrediction(tu, compID, cs.getResiBuf(tu.Y()), piResi, piResi, true);
}
if (slice.getReshapeInfo().getUseSliceReshaper() && m_pcReshape->getCTUFlag() && compID == COMPONENT_Y)
{
CompArea tmpArea(COMPONENT_Y, area.chromaFormat, Position(0,0), area.size());
PelBuf tmpPred = m_tmpStorageLCU.getBuf(tmpArea);
tmpPred.copyFrom(piPred);//拿到预测信号,用于重建信号的计算
//根据compID计算单个色度分量的重建图像数据(若为传统模式,则在外层有两次Cb和Cr的循环,计算各自分量的重建值;
// 若为联合模式,则在外层函数只对Cb预测块进行调用,piResi为联合残差,保存在Cb块内,只计算Cb块的重建值)
piReco.reconstruct(tmpPred, piResi, cs.slice->clpRng(compID));//残差+预测值
}
else
piReco.reconstruct(piPred, piResi, cs.slice->clpRng( compID ));
#if JVET_N0054_JOINT_CHROMA
if ( jointCbCr )//如果是联合模式,则Cr预测块的解码残差和重建值需要单独计算
{
// Cr uses negative of the signalled Cb residual
if (uiAbsSum > 0)//Cr的残差等于Cb残差的负值,因为这里已经是联合色度模式了,所以piResi里存的就是联合残差
crResi.copyAndNegate( piResi );
else
crResi.fill(0);
tu.getCoeffs(COMPONENT_Cr).fill(0);
// Set cbf also for Cr
TU::setCbfAtDepth (tu, COMPONENT_Cr, tu.depth, uiAbsSum > 0 ? true : false);
// Cr reconstruction and its contribution to the total error
//计算Cr预测块的重建值
crReco.reconstruct(crPred, crResi, cs.slice->clpRng( COMPONENT_Cr ));
#if WCG_EXT
if ( m_pcEncCfg->getLumaLevelToDeltaQPMapping().isEnabled() ||
(m_pcEncCfg->getReshaper()
&& slice.getReshapeInfo().getUseSliceReshaper()
&& (m_pcReshape->getCTUFlag() || (isChroma(compID) && m_pcEncCfg->getReshapeIntraCMD()))))
{
const CPelBuf orgLuma = cs.getOrgBuf( cs.area.blocks[COMPONENT_Y] );
ruiDist += m_pcRdCost->getDistPart( crOrg, crReco, bitDepth, COMPONENT_Cr, DF_SSE_WTD, &orgLuma );
}
else
#endif
{
ruiDist += m_pcRdCost->getDistPart( crOrg, crReco, bitDepth, COMPONENT_Cr, DF_SSE );
}
}
#endif
//===== update distortion =====
#if WCG_EXT
if (m_pcEncCfg->getLumaLevelToDeltaQPMapping().isEnabled() || (m_pcEncCfg->getReshaper()
&& slice.getReshapeInfo().getUseSliceReshaper() && (m_pcReshape->getCTUFlag() || (isChroma(compID) && m_pcEncCfg->getReshapeIntraCMD()))))
{
const CPelBuf orgLuma = cs.getOrgBuf( cs.area.blocks[COMPONENT_Y] );
if (compID == COMPONENT_Y && !(m_pcEncCfg->getLumaLevelToDeltaQPMapping().isEnabled()))
{
CompArea tmpArea1(COMPONENT_Y, area.chromaFormat, Position(0, 0), area.size());
PelBuf tmpRecLuma = m_tmpStorageLCU.getBuf(tmpArea1);
tmpRecLuma.copyFrom(piReco);
tmpRecLuma.rspSignal(m_pcReshape->getInvLUT());
ruiDist += m_pcRdCost->getDistPart(piOrg, tmpRecLuma, sps.getBitDepth(toChannelType(compID)), compID, DF_SSE_WTD, &orgLuma);
}
else
ruiDist += m_pcRdCost->getDistPart(piOrg, piReco, bitDepth, compID, DF_SSE_WTD, &orgLuma);
}
else
#endif
{
ruiDist += m_pcRdCost->getDistPart( piOrg, piReco, bitDepth, compID, DF_SSE );
}
}