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

亮度块的帧内预测


帧内预测的流程

注意:如果没有特别说明,操作的对象都是亮度块(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、重置熵编码上下文之类的操作
	
	// 删除无关代码*****
}


  • 12
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
HM16.0引入了PCM模式,它是一种特殊的预测模式。PCM模式的作用是将当前块的所有像素值都设置为一个常量值,这个常量值可以通过压缩数据的一个短语来表示,从而达到无损压缩的效果。 在PCM模式下,编码器不需要对当前块进行其他的预测操作,而是直接将所有像素值编码为一个相同的值。这个值的选择可以通过在编码数据发送一个短语来完成,这个短语可以看作是一个索引,用于查找一个预定义的常量数组。对于8位像素值,常量数组的大小通常为256个元素,因此短语的长度为8位。 在解码端,当遇到一个PCM模式的块时,解码器会从压缩数据读取一个短语,然后使用短语所指向的常量值来填充当前块的所有像素值。由于常量值是无损编码的,所以解码器可以精确地重构原始像素值,而不会出现任何失真。 xCheckIntraPCM是HM16.0引入的一种改进的PCM模式,它可以更准确地估计PCM模式编码质量,并在需要时动态地选择是否使用PCM模式。xCheckIntraPCM的基本思想是通过计算当前块的像素值的方差来估计PCM模式编码质量。当方差较小时,使用PCM模式可以达到更好的压缩效果;当方差较大时,使用PCM模式则可能会导致较大的编码失真。为了避免失真,xCheckIntraPCM会动态地选择是否使用PCM模式,从而在保证压缩效率的同时保持编码质量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值