【HEVC学习与研究】32、编码一个CU(帧内部分)1

在一个compressSlice()中,在compressCU函数中实现对一个CU的编码,其中主要进行了CU的初始化,以及实际的编码操作。

Void TEncCu::compressCU( TComDataCU*& rpcCU )
{
  // initialize CU data
  m_ppcBestCU[0]->initCU( rpcCU->getPic(), rpcCU->getAddr() );
  m_ppcTempCU[0]->initCU( rpcCU->getPic(), rpcCU->getAddr() );

#if RATE_CONTROL_LAMBDA_DOMAIN
  m_addSADDepth      = 0;
  m_LCUPredictionSAD = 0;
  m_temporalSAD      = 0;
#endif

  // analysis of CU
  xCompressCU( m_ppcBestCU[0], m_ppcTempCU[0], 0 );

#if ADAPTIVE_QP_SELECTION
  if( m_pcEncCfg->getUseAdaptQpSelect() )
  {
    if(rpcCU->getSlice()->getSliceType()!=I_SLICE) //IIII
    {
      xLcuCollectARLStats( rpcCU);
    }
  }
#endif
}

其中完成实际编码一个CU操作的是xCompressCU方法。前面的综述中已经描述过,每一个CTU按照四叉树结构进行划分,CompressCU中调用的xCompressCU则相当于四叉树的根节点。另外,在每一个xCompressCU方法中间,会对每一个CU进行分析判断是否进行下一级划分。

xCompressCU函数由于包含了Intra和InterFrame编码的代码,因此同样非常长,共有600余行。下面着重对帧内编码的部分做一下梳理。

实现帧内编码的部分代码如下:

Void TEncCu::xCompressCU( TComDataCU*& rpcBestCU, TComDataCU*& rpcTempCU, UInt uiDepth, PartSize eParentPartSize )
{
//......
// do normal intra modes
        if ( !bEarlySkip )
        {
          // speedup for inter frames
          if( rpcBestCU->getSlice()->getSliceType() == I_SLICE ||
            rpcBestCU->getCbf( 0, TEXT_LUMA     ) != 0   ||
            rpcBestCU->getCbf( 0, TEXT_CHROMA_U ) != 0   ||
            rpcBestCU->getCbf( 0, TEXT_CHROMA_V ) != 0     ) // avoid very complex intra if it is unlikely
          {
            xCheckRDCostIntra( rpcBestCU, rpcTempCU, SIZE_2Nx2N );
            rpcTempCU->initEstData( uiDepth, iQP );
            if( uiDepth == g_uiMaxCUDepth - g_uiAddCUDepth )
            {
              if( rpcTempCU->getWidth(0) > ( 1 << rpcTempCU->getSlice()->getSPS()->getQuadtreeTULog2MinSize() ) )
              {
                xCheckRDCostIntra( rpcBestCU, rpcTempCU, SIZE_NxN   );
                rpcTempCU->initEstData( uiDepth, iQP );
              }
            }
          }
        }
//......
}

在这部分代码中xCheckRDCostIntra( rpcBestCU, rpcTempCU, SIZE_2Nx2N )查看了各种intra预测模式下的代价:

Void TEncCu::xCheckRDCostIntra( TComDataCU*& rpcBestCU, TComDataCU*& rpcTempCU, PartSize eSize )
{
  UInt uiDepth = rpcTempCU->getDepth( 0 );

  rpcTempCU->setSkipFlagSubParts( false, 0, uiDepth );

  rpcTempCU->setPartSizeSubParts( eSize, 0, uiDepth );
  rpcTempCU->setPredModeSubParts( MODE_INTRA, 0, uiDepth );
  rpcTempCU->setCUTransquantBypassSubParts( m_pcEncCfg->getCUTransquantBypassFlagValue(), 0, uiDepth );

  Bool bSeparateLumaChroma = true; // choose estimation mode
  UInt uiPreCalcDistC      = 0;
  if( !bSeparateLumaChroma )
  {
    m_pcPredSearch->preestChromaPredMode( rpcTempCU, m_ppcOrigYuv[uiDepth], m_ppcPredYuvTemp[uiDepth] );
  }
  m_pcPredSearch  ->estIntraPredQT      ( rpcTempCU, m_ppcOrigYuv[uiDepth], m_ppcPredYuvTemp[uiDepth], m_ppcResiYuvTemp[uiDepth], m_ppcRecoYuvTemp[uiDepth], uiPreCalcDistC, bSeparateLumaChroma );

  m_ppcRecoYuvTemp[uiDepth]->copyToPicLuma(rpcTempCU->getPic()->getPicYuvRec(), rpcTempCU->getAddr(), rpcTempCU->getZorderIdxInCU() );

  m_pcPredSearch  ->estIntraPredChromaQT( rpcTempCU, m_ppcOrigYuv[uiDepth], m_ppcPredYuvTemp[uiDepth], m_ppcResiYuvTemp[uiDepth], m_ppcRecoYuvTemp[uiDepth], uiPreCalcDistC );

  m_pcEntropyCoder->resetBits();
  if ( rpcTempCU->getSlice()->getPPS()->getTransquantBypassEnableFlag())
  {
    m_pcEntropyCoder->encodeCUTransquantBypassFlag( rpcTempCU, 0,          true );
  }
  m_pcEntropyCoder->encodeSkipFlag ( rpcTempCU, 0,          true );
  m_pcEntropyCoder->encodePredMode( rpcTempCU, 0,          true );
  m_pcEntropyCoder->encodePartSize( rpcTempCU, 0, uiDepth, true );
  m_pcEntropyCoder->encodePredInfo( rpcTempCU, 0,          true );
  m_pcEntropyCoder->encodeIPCMInfo(rpcTempCU, 0, true );

  // Encode Coefficients
  Bool bCodeDQP = getdQPFlag();
  m_pcEntropyCoder->encodeCoeff( rpcTempCU, 0, uiDepth, rpcTempCU->getWidth (0), rpcTempCU->getHeight(0), bCodeDQP );
  setdQPFlag( bCodeDQP );

  if( m_bUseSBACRD ) m_pcRDGoOnSbacCoder->store(m_pppcRDSbacCoder[uiDepth][CI_TEMP_BEST]);

  rpcTempCU->getTotalBits() = m_pcEntropyCoder->getNumberOfWrittenBits();
  if(m_pcEncCfg->getUseSBACRD())
  {
    rpcTempCU->getTotalBins() = ((TEncBinCABAC *)((TEncSbac*)m_pcEntropyCoder->m_pcEntropyCoderIf)->getEncBinIf())->getBinsCoded();
  }
  rpcTempCU->getTotalCost() = m_pcRdCost->calcRdCost( rpcTempCU->getTotalBits(), rpcTempCU->getTotalDistortion() );

  xCheckDQP( rpcTempCU );
  xCheckBestMode(rpcBestCU, rpcTempCU, uiDepth);
}

在这个函数中,调用了estIntraPredQT和estIntraPredChromaQT方法,这两个函数的作用是类似的,区别只在于前者针对亮度分量后者针对色度分量。我们重点关注对亮度分量的操作,即estIntraPredQT函数。

下面是estIntraPredQT的一段代码:

Void
TEncSearch::estIntraPredQT( TComDataCU* pcCU,
                           TComYuv*    pcOrgYuv,
                           TComYuv*    pcPredYuv,
                           TComYuv*    pcResiYuv,
                           TComYuv*    pcRecoYuv,
                           UInt&       ruiDistC,
                           Bool        bLumaOnly )
{
//......
      for( Int modeIdx = 0; modeIdx < numModesAvailable; modeIdx++ )
      {
        UInt uiMode = modeIdx;

        predIntraLumaAng( pcCU->getPattern(), uiMode, piPred, uiStride, uiWidth, uiHeight, bAboveAvail, bLeftAvail );

        // use hadamard transform here
        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 );
      }
//......
}

这个for循环的意义就是遍历多种帧内预测模式,其中numModesAvailable==35,对应整个intra的35个模式。

在predIntraLumaAng函数中,编码器完成计算出当前PU的预测值:

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;

	// Create the prediction
	if ( uiDirMode == PLANAR_IDX )
	{
		xPredIntraPlanar( ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight );
	}
	else
	{
		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 );

			if( (uiDirMode == DC_IDX ) && bAbove && bLeft )
			{
				xDCPredFiltering( ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight);
			}
		}
	}
}

在这个函数中主要起作用的是xPredIntraPlanar和xPredIntraAng两个函数,另外在PU大小小于16×16,且模式为DC模式时还会调用xDCPredFiltering函数。在这里我们主要关心前面两个。

xPredIntraPlanar的作用是以平面模式构建当前PU的帧内预测块:

Void TComPrediction::xPredIntraPlanar( Int* pSrc, Int srcStride, Pel* rpDst, Int dstStride, UInt width, UInt height )
{
	assert(width == height);
	Int k, l, bottomLeft, topRight;
	Int horPred;
	Int leftColumn[MAX_CU_SIZE], topRow[MAX_CU_SIZE], bottomRow[MAX_CU_SIZE], rightColumn[MAX_CU_SIZE];
	UInt blkSize = width;
	UInt offset2D = width;
	UInt shift1D = g_aucConvertToBit[ width ] + 2;
	UInt shift2D = shift1D + 1;

	// Get left and above reference column and row
	for(k=0;k<blkSize+1;k++)
	{
		topRow[k] = pSrc[k-srcStride];
		leftColumn[k] = pSrc[k*srcStride-1];
	}

	// Prepare intermediate variables used in interpolation
	bottomLeft = leftColumn[blkSize];
	topRight   = topRow[blkSize];
	for (k=0;k<blkSize;k++)
	{
		bottomRow[k]   = bottomLeft - topRow[k];
		rightColumn[k] = topRight   - leftColumn[k];
		topRow[k]      <<= shift1D;
		leftColumn[k]  <<= shift1D;
	}

	// Generate prediction signal
	for (k=0;k<blkSize;k++)
	{
		horPred = leftColumn[k] + offset2D;
		for (l=0;l<blkSize;l++)
		{
			horPred += rightColumn[k];
			topRow[l] += bottomRow[l];
			rpDst[k*dstStride+l] = ( (horPred + topRow[l]) >> shift2D );
		}
	}
}

而xPredIntraAng函数则承担了其他模式的预测块构建,也即,不同的模式索引值代表N多中不同的预测角度,从这些角度上以参考数据构建预测块。

Void TComPrediction::xPredIntraAng(Int bitDepth, Int* pSrc, Int srcStride, Pel*& rpDst, Int dstStride, UInt width, UInt height, UInt dirMode, Bool blkAboveAvailable, Bool blkLeftAvailable, Bool bFilter )
{
	Int k,l;
	Int blkSize        = width;
	Pel* pDst          = rpDst;

	// Map the mode index to main prediction direction and angle
	assert( dirMode > 0 ); //no planar
	Bool modeDC        = dirMode < 2;
	Bool modeHor       = !modeDC && (dirMode < 18);
	Bool modeVer       = !modeDC && !modeHor;
	Int intraPredAngle = modeVer ? (Int)dirMode - VER_IDX : modeHor ? -((Int)dirMode - HOR_IDX) : 0;
	Int absAng         = abs(intraPredAngle);
	Int signAng        = intraPredAngle < 0 ? -1 : 1;

	// Set bitshifts and scale the angle parameter to block size
	Int angTable[9]    = {0,    2,    5,   9,  13,  17,  21,  26,  32};
	Int invAngTable[9] = {0, 4096, 1638, 910, 630, 482, 390, 315, 256}; // (256 * 32) / Angle
	Int invAngle       = invAngTable[absAng];
	absAng             = angTable[absAng];
	intraPredAngle     = signAng * absAng;

	// Do the DC prediction
	if (modeDC)
	{
		Pel dcval = predIntraGetPredValDC(pSrc, srcStride, width, height, blkAboveAvailable, blkLeftAvailable);

		for (k=0;k<blkSize;k++)
		{
			for (l=0;l<blkSize;l++)
			{
				pDst[k*dstStride+l] = dcval;
			}
		}
	}

	// Do angular predictions
	else
	{
		Pel* refMain;
		Pel* refSide;
		Pel  refAbove[2*MAX_CU_SIZE+1];
		Pel  refLeft[2*MAX_CU_SIZE+1];

		// Initialise the Main and Left reference array.
		if (intraPredAngle < 0)
		{
			for (k=0;k<blkSize+1;k++)
			{
				refAbove[k+blkSize-1] = pSrc[k-srcStride-1];
			}
			for (k=0;k<blkSize+1;k++)
			{
				refLeft[k+blkSize-1] = pSrc[(k-1)*srcStride-1];
			}
			refMain = (modeVer ? refAbove : refLeft) + (blkSize-1);
			refSide = (modeVer ? refLeft : refAbove) + (blkSize-1);

			// Extend the Main reference to the left.
			Int invAngleSum    = 128;       // rounding for (shift by 8)
			for (k=-1; k>blkSize*intraPredAngle>>5; k--)
			{
				invAngleSum += invAngle;
				refMain[k] = refSide[invAngleSum>>8];
			}
		}
		else
		{
			for (k=0;k<2*blkSize+1;k++)
			{
				refAbove[k] = pSrc[k-srcStride-1];
			}
			for (k=0;k<2*blkSize+1;k++)
			{
				refLeft[k] = pSrc[(k-1)*srcStride-1];
			}
			refMain = modeVer ? refAbove : refLeft;
			refSide = modeVer ? refLeft  : refAbove;
		}

		if (intraPredAngle == 0)
		{
			for (k=0;k<blkSize;k++)
			{
				for (l=0;l<blkSize;l++)
				{
					pDst[k*dstStride+l] = refMain[l+1];
				}
			}

			if ( bFilter )
			{
				for (k=0;k<blkSize;k++)
				{
					pDst[k*dstStride] = Clip3(0, (1<<bitDepth)-1, pDst[k*dstStride] + (( refSide[k+1] - refSide[0] ) >> 1) );
				}
			}
		}
		else
		{
			Int deltaPos=0;
			Int deltaInt;
			Int deltaFract;
			Int refMainIndex;

			for (k=0;k<blkSize;k++)
			{
				deltaPos += intraPredAngle;
				deltaInt   = deltaPos >> 5;
				deltaFract = deltaPos & (32 - 1);

				if (deltaFract)
				{
					// Do linear filtering
					for (l=0;l<blkSize;l++)
					{
						refMainIndex        = l+deltaInt+1;
						pDst[k*dstStride+l] = (Pel) ( ((32-deltaFract)*refMain[refMainIndex]+deltaFract*refMain[refMainIndex+1]+16) >> 5 );
					}
				}
				else
				{
					// Just copy the integer samples
					for (l=0;l<blkSize;l++)
					{
						pDst[k*dstStride+l] = refMain[l+deltaInt+1];
					}
				}
			}
		}

		// Flip the block if this is the horizontal mode
		if (modeHor)
		{
			Pel  tmp;
			for (k=0;k<blkSize-1;k++)
			{
				for (l=k+1;l<blkSize;l++)
				{
					tmp                 = pDst[k*dstStride+l];
					pDst[k*dstStride+l] = pDst[l*dstStride+k];
					pDst[l*dstStride+k] = tmp;
				}
			}
		}
	}
}

具体的预测块构建的原理,将在下篇文章中详述。

时间: 2024-09-16 18:54:36

【HEVC学习与研究】32、编码一个CU(帧内部分)1的相关文章

【HEVC学习与研究】36、对帧内预测参考数据进行滤波处理

在帧内预测的过程中,获取临近的Prediction Unit的边缘数据作为当前PU的参考数据.数据获取完成后,并不一定会直接使用这些数据进行预测,而可能会先将这些预测数据进行一次滤波操作.帧内参考像素的滤波在标准文档的8.4.4.2.3节详述. 帧内参考像素的滤波使能标记由一个标志位filterFlag标识.该标志位的判定方法为: 1.如果当前预测模式为DC模式,或者帧内预测的PU为4×4大小时,filterFlag一律为0: 2.计算当前的Intra预测模式同"水平"和"垂

【HEVC学习与研究】28、第一帧第一个宏块的SAO部分完整解析结果

经过了前面一段时间的研究,现在大致将这第一个宏块SAO由码流到语法元素值的解析过程完整整理一下.这里没有太多原理部分,更多的像是一篇流水账一样,聊作记录. 在代码中,我们首先查看一下解析完条带头数据后,当前NAL中带解析的码流.还是看我们一直使用的这个demo序列的编码结果,码流中正式用语解析条带数据的值如: 206 103 162 107 167 87 243 112 29 35 44 3 245 69 197 199 130 168 75 91 169 13 159 38 44 174 14

【HEVC学习与研究】38、HEVC编码过程中的块分割结构

[本文主要分为前后两部分,前半部分基本是Vivienne Sze.Madhukar BudagaviGary和J. Sullivan所编著的<High Efficiency Video Coding (HEVC) --Algorithms and Architectures>的第三章前半部分的笔记,后半部分是在HM-10.0中对Intra预测时块分割相应的代码研究.] 0.摘要 在基于块结构的混合编码框架中,每一帧图像被分割成多个像素结构的像素块(block),而一帧图像中多个像素块聚合成为一

【HEVC学习与研究】37、HM编码器的基本结构2:帧内编码部分的代码骨架

第31篇博文大体介绍了HEVC参考代码HM10的编码器结构,但仅仅停留在compressCU()以上的层次,并未对具体编码的结构做深入解析.在此篇博文中我们依据对视频YUV序列的帧内编码的流程,重新梳理HM编码器的代码结构. 1.编码器的上层结构: 主要包括: 入口点函数main()[创建cTAppEncTop类,解析输入的配置函数,设定时间相关的参数] cTAppEncTop.encode()[对编码器所使用的几个对象进行初始化,分配YUV数据缓存,循环读取YUV文件] m_cTEncTop.

【HEVC学习与研究】31、HM编码器的基本结构

通过解码器代码的研究,已经对HEVC的编解码技术有了一个初步的认识.现在我们就对照着编码器的代码进一步理解HEVC视频编码算法的各个技术细节. 编码器在整个HM解决方案中的工程名为TAppEncoder,入口点函数位于encmain.cpp文件中: int main(int argc, char* argv[]) { TAppEncTop cTAppEncTop; // print information fprintf( stdout, "\n" ); fprintf( stdout

【HEVC学习与研究】42、HEVC帧内编码的原理和实现(下)

4.编码帧内预测模式 大量增加可选择的预测模式可以提供更高的编码效率,同时要求编码预测模式有更加高效的方法降低更多模式带来的负担.与H.264采用一个最可能模式不同的是,对于亮度分量,三个最可能模式用于预测实际的帧内预测模式.同时,也考虑了三个最可能模式中的冗余信息,重复的模式会使用其他模式进行替换.对于亮度分量,HEVC采用了同亮度分量相同的预测模式.在编码亮度和色度帧内预测模式时,各个语法元素的设计也体现了亮度分量更多的最可能候选模式以及亮度分量的统计特性. (1)亮度帧内预测模式的预测 H

【HEVC学习与研究】43、HEVC变换编码的实现

[变换和量化是整个视频编码技术系列中理论性和研究性较强的一部分,本文暂时不去研究变换的原理.推导过程等,只是调试一下在参考代码中对预测残差进行变换的实现过程.变换的原理在未来会作为一个研究方向进行探讨.] 1.HM中Intra模式的主要实现逻辑 以Intra的亮度模式为例.主要实现代码实现于TEncSearch::estIntraPredQT方法中.TEncSearch::estIntraPredQT实现时,首先获取当前CU的分割子块的个数,并且对每个子块分别进行预测.变换量化操作(代码中称之为

【HEVC学习与研究】39、HEVC帧内编码的原理和实现(上)

[前面N篇博文都讲了一些HEVC帧内预测的代码结构和简单的方法,但是尚未对整体的算法和实现做一个比较完整的描述.本篇借助参考文献<High Efficiency Video Coding (HEVC) -- Algorithms and Architectures>的相关章节的阅读笔记,对HEVC的帧内预测算法做一个比较完整的阐述.] [摘要]:HEVC的帧内预测的架构分为三个步骤:①构建参考像素数组:②生成预测像素:③后处理操作.HEVC标准将这三个步骤进行了精密设计,以求达到较高的编码效率

【HEVC学习与研究】34、HEVC参考软件HM中Intra预测参考像素的获取与管理

继续上一个section所讨论的问题.在section 33中讨论了HEVC帧内预测的几种不同模式,代表这几种模式的函数xPredIntraPlanar.xPredIntraAng和xDCPredFiltering调用的位置位于Void TComPrediction::predIntraLumaAng()中,所以也可以说,在一个PU内,函数Void TComPrediction::predIntraLumaAng实现了亮度分量的帧内预测.该函数的实现方法如下: Void TComPredicti