HM编码器代码阅读(26)——去方块滤波

去方块滤波


原理和技术


    去方块滤波的目的是去除去方块效应
    去方块滤波介绍
    1、方块效应是编码块边界的不连续性,因为HEVC是一个基于块的混合编码框架,各个编码块的操作是相互独立的,因此不用的块使用了不同的参数,就造成了方块效应
    2、去方块效应就是对块的边界进行一个平滑的操作,让方块看起来不那么明显
    3、处理单位是8x8的块的边界(即CU、PU、TU的边界),图像的边界不进行处理
    4、边界的两边各修正3个像素值
    5、对于色度分量,当边界的两边至少存在一个块采用了帧内预测模式时,才进行滤波


    去方块滤波技术
    1、滤波决策
         (1)获取(计算)边界强度BS(取值是0(不滤波),1(弱滤波),2(强滤波))
         (2)滤波开关决策,根据视频块的内容判断是否需要进行滤波
         (3)滤波强弱的选择
    2、滤波处理
         (1)亮度分量的强滤波——对像素值进行大范围大幅度的修正
         (2)亮度分量的弱滤波——对像素值进行小范围小幅度的修正
         (3)色度分量的滤波——当BS等于2的时候才对色度分量进行滤波操作


    详细细节请看 HEVC/H.265理论知识(7)——环路滤波



去方块滤波在HEVC中的位置



    通过一个简化版的compressGOP,可以看到去方块滤波在量化之后,在SAO和熵编码之前

Void TEncGOP::compressGOP( Int iPOCLast, Int iNumPicRcvd, TComList& rcListPic, TComList& rcListPicYuvRecOut, std::list& accessUnitsInGOP, bool isField, bool isTff)
{
	// 省略...

    // 图像组初始化(设置图像组中图像的个数)
    xInitGOP( iPOCLast, iNumPicRcvd, rcListPic, rcListPicYuvRecOut, isField );

    // 省略...
    for ( Int iGOPid=0; iGOPid < m_iGopSize; iGOPid++ )
    {
		// 省略...
        m_pcSliceEncoder->initEncSlice ( pcPic, iPOCLast, pocCurr, iNumPicRcvd, iGOPid, pcSlice, m_pcEncTop->getSPS(), m_pcEncTop->getPPS(), isField );
        // 省略...
		
        // 缩放列表设置
		
		//【=========================参考帧方面的设置 begin==========================】
        // Do decoding refresh marking if any
        // 设置解码刷新标记
        pcSlice->decodingRefreshMarking(m_pocCRA, m_bRefreshPending, rcListPic);
        // 选择参考图像集
        m_pcEncTop->selectReferencePictureSet(pcSlice, pocCurr, iGOPid);

		// 省略...
		
        // 设置长期参考图像的数量为0
        pcSlice->getRPS()->setNumberOfLongtermPictures(0);

		// 省略...

        // 检测所有的参考图像是否可用
#if ALLOW_RECOVERY_POINT_AS_RAP
        if ((pcSlice->checkThatAllRefPicsAreAvailable(rcListPic, pcSlice->getRPS(), false, m_iLastRecoveryPicPOC, m_pcCfg->getDecodingRefreshType() == 3) != 0)
			|| (pcSlice->isIRAP())
#if EFFICIENT_FIELD_IRAP
            || 
			(
				isField && pcSlice->getAssociatedIRAPType() >= NAL_UNIT_CODED_SLICE_BLA_W_LP
				&& pcSlice->getAssociatedIRAPType() <= NAL_UNIT_CODED_SLICE_CRA
				&& pcSlice->getAssociatedIRAPPOC() == pcSlice->getPOC() + 1
			)
#endif
            )
        {
            //Function for constructing an explicit Reference Picture Set out of the available pictures in a referenced Reference Picture Set
            // 创建一个明确的参考图像集
            pcSlice->createExplicitReferencePictureSetFromReference(rcListPic, pcSlice->getRPS(), pcSlice->isIRAP(), m_iLastRecoveryPicPOC, m_pcCfg->getDecodingRefreshType() == 3);
        }
#else
        if ((pcSlice->checkThatAllRefPicsAreAvailable(rcListPic, pcSlice->getRPS(), false) != 0) || (pcSlice->isIRAP()))
        {
            pcSlice->createExplicitReferencePictureSetFromReference(rcListPic, pcSlice->getRPS(), pcSlice->isIRAP());
        }
#endif

        // 应用参考图像集
        pcSlice->applyReferencePictureSet(rcListPic, pcSlice->getRPS());

        // 省略...

        // 排列RPS中参考图像集
        arrangeLongtermPicturesInRPS(pcSlice, rcListPic);

        // 参考图像集修改(修改标志都设置为0)
        TComRefPicListModification* refPicListModification = pcSlice->getRefPicListModification();
        refPicListModification->setRefPicListModificationFlagL0(0);
        refPicListModification->setRefPicListModificationFlagL1(0);

        // 设置当前条带参考的图像的索引(列表0和列表1中)
        pcSlice->setNumRefIdx(REF_PIC_LIST_0,min(m_pcCfg->getGOPEntry(iGOPid).m_numRefPicsActive,pcSlice->getRPS()->getNumberOfPictures()));
        pcSlice->setNumRefIdx(REF_PIC_LIST_1,min(m_pcCfg->getGOPEntry(iGOPid).m_numRefPicsActive,pcSlice->getRPS()->getNumberOfPictures()));

#if ADAPTIVE_QP_SELECTION
        // 设置变换和量化器类
        pcSlice->setTrQuant( m_pcEncTop->getTrQuant() );
#endif

        //  Set reference list
        // 设置参考图像列表
        // 经过上面的一系列处理图像参考集 之后,rcListPic最终放在参考帧
        pcSlice->setRefPicList ( rcListPic );

		// 省略...

        // 设置条带的参考POC列表
        pcSlice->setRefPOCList();

        // 设置条带的list1的索引到list0的索引的映射关系
        pcSlice->setList1IdxToList0Idx();

        // 设置是否启用TMVP
		// 省略...
        
        /// Compress a slice
        //  Slice compression
        // 是否使用了自适应的搜索范围
        if (m_pcCfg->getUseASR())
        {
            m_pcSliceEncoder->setSearchRange(pcSlice);
        }

        Bool bGPBcheck=false;

        // 省略...

        if(bGPBcheck)
        {
            pcSlice->setMvdL1ZeroFlag(true);
        }
        else
        {
            pcSlice->setMvdL1ZeroFlag(false);
        }
        pcPic->getSlice(pcSlice->getSliceIdx())->setMvdL1ZeroFlag(pcSlice->getMvdL1ZeroFlag());

		//【=========================参考帧方面的设置 end==========================】
		
        // 省略...

		//【=========================码率控制初始化 begin==========================】
        // 如果使用了码率控制
        if ( m_pcCfg->getUseRateCtrl() )
        {
            Int frameLevel = m_pcRateCtrl->getRCSeq()->getGOPID2Level( iGOPid );
            if ( pcPic->getSlice(0)->getSliceType() == I_SLICE )
            {
                frameLevel = 0;
            }

            m_pcRateCtrl->initRCPic( frameLevel );

            estimatedBits = m_pcRateCtrl->getRCPic()->getTargetBits();

            Int sliceQP = m_pcCfg->getInitialQP();

            if ( ( pcSlice->getPOC() == 0 && m_pcCfg->getInitialQP() > 0 ) || ( frameLevel == 0 && m_pcCfg->getForceIntraQP() ) ) // QP is specified
            {
                Int    NumberBFrames = ( m_pcCfg->getGOPSize() - 1 );
                Double dLambda_scale = 1.0 - Clip3( 0.0, 0.5, 0.05*(Double)NumberBFrames );
                Double dQPFactor     = 0.57*dLambda_scale;
                Int    SHIFT_QP      = 12;
                Int    bitdepth_luma_qp_scale = 0;
                Double qp_temp = (Double) sliceQP + bitdepth_luma_qp_scale - SHIFT_QP;
                lambda = dQPFactor*pow( 2.0, qp_temp/3.0 );
            }
            else if ( frameLevel == 0 )   // intra case, but use the model
            {
                m_pcSliceEncoder->calCostSliceI(pcPic);
                if ( m_pcCfg->getIntraPeriod() != 1 )   // do not refine allocated bits for all intra case
                {
                    Int bits = m_pcRateCtrl->getRCSeq()->getLeftAverageBits();
                    bits = m_pcRateCtrl->getRCPic()->getRefineBitsForIntra( bits );
                    if ( bits < 200 )
                    {
                        bits = 200;
                    }
                    m_pcRateCtrl->getRCPic()->setTargetBits( bits );
                }
                list listPreviousPicture = m_pcRateCtrl->getPicList();
                m_pcRateCtrl->getRCPic()->getLCUInitTargetBits();
                lambda  = m_pcRateCtrl->getRCPic()->estimatePicLambda( listPreviousPicture, pcSlice->getSliceType());
                sliceQP = m_pcRateCtrl->getRCPic()->estimatePicQP( lambda, listPreviousPicture );
            }
            else    // normal case
            {
                list listPreviousPicture = m_pcRateCtrl->getPicList();
                lambda  = m_pcRateCtrl->getRCPic()->estimatePicLambda( listPreviousPicture, pcSlice->getSliceType());
                sliceQP = m_pcRateCtrl->getRCPic()->estimatePicQP( lambda, listPreviousPicture );
            }

            sliceQP = Clip3( -pcSlice->getSPS()->getQpBDOffsetY(), MAX_QP, sliceQP );
            m_pcRateCtrl->getRCPic()->setPicEstQP( sliceQP );

            m_pcSliceEncoder->resetQP( pcPic, sliceQP, lambda );
        }
		
		//【=========================码率控制初始化 end==========================】

        // 省略...
		
        // Allocate some coders, now we know how many tiles there are.
        // 创建WPP编码器
        m_pcEncTop->createWPPCoders(iNumSubstreams);
		
        pcSbacCoders = m_pcEncTop->getSbacCoders();

        pcSubstreamsOut = new TComOutputBitstream[iNumSubstreams];

        // 省略...

		//【===compressSlice遍历各种模式的组合,选取最优的模式组合以及参数,同时得到变换系数 begin===】
        // 循环判断CU地址是否已经到了条带边界
        // 即处理slice中的每一个CU
        while(nextCUAddrsetNextSlice       ( false );
            pcSlice->setNextSliceSegment( false );
            assert(pcPic->getNumAllocatedSlice() == startCUAddrSliceIdx);

            m_pcSliceEncoder->precompressSlice( pcPic );
			
            m_pcSliceEncoder->compressSlice   ( pcPic );

            // 省略...

            nextCUAddr = (startCUAddrSlice > startCUAddrSliceSegment) ? startCUAddrSlice : startCUAddrSliceSegment;
        }
		//【====compressSlice遍历各种模式的组合,选取最优的模式组合以及参数,同时得到变换系数 end===】
        
		// 省略...
        if( pcSlice->getSPS()->getUseSAO() && m_pcCfg->getSaoLcuBoundary() )
        {
            m_pcSAO->getPreDBFStatistics(pcPic);
        }
        //-- Loop filter
        Bool bLFCrossTileBoundary = pcSlice->getPPS()->getLoopFilterAcrossTilesEnabledFlag();

        // 设置环路滤波器是否跨越边界
        m_pcLoopFilter->setCfg(bLFCrossTileBoundary);

        if ( m_pcCfg->getDeblockingFilterMetric() )
        {
            dblMetric(pcPic, uiNumSlices);
        }

		//【!!!!重要!!!!!!!】
		//【====================去方块滤波========================】
        m_pcLoopFilter->loopFilterPic( pcPic );
		//【!!!!重要!!!!!!!】

        /// File writing
        // Set entropy coder

        // 设置CAVLC编码器
        // 帧或者条带中的数据(连同CU的语法元素等)使用的是CABAC进行熵编码
        // 但是各种参数集以及外部的信息,使用的是cavlc进行熵编码
        m_pcEntropyCoder->setEntropyCoder   ( m_pcCavlcCoder, pcSlice );

		//【===对参数集进行编码 begin===】
        /* write various header sets. */
        // 是否序列头,对图像的第一帧进行此处理,其余的帧不进行此处理
        if ( m_bSeqFirst )
        {
            // NAL单元(包含一个slice片段的数据),类型是视频参数集
            OutputNALUnit nalu(NAL_UNIT_VPS);

            // 熵编码器的结果放到NAL的子比特流中去
            m_pcEntropyCoder->setBitstream(&nalu.m_Bitstream);

            // 编码视频参数集
            m_pcEntropyCoder->encodeVPS(m_pcEncTop->getVPS());
            writeRBSPTrailingBits(nalu.m_Bitstream);

            // 将一个NAL单元存放到列表中
            accessUnit.push_back(new NALUnitEBSP(nalu));
            actualTotalBits += UInt(accessUnit.back()->m_nalUnitData.str().size()) * 8;

            // 类型是序列参数集
            nalu = NALUnit(NAL_UNIT_SPS);
            m_pcEntropyCoder->setBitstream(&nalu.m_Bitstream);

			// 把HRD信息写入sps中
			// 省略...
			
            // 编码序列参数集
            m_pcEntropyCoder->encodeSPS(pcSlice->getSPS());
            writeRBSPTrailingBits(nalu.m_Bitstream);
            accessUnit.push_back(new NALUnitEBSP(nalu));
            actualTotalBits += UInt(accessUnit.back()->m_nalUnitData.str().size()) * 8;

            // 类型是图像参数集
            nalu = NALUnit(NAL_UNIT_PPS);
            m_pcEntropyCoder->setBitstream(&nalu.m_Bitstream);
            // 对图像参数集进行编码
            m_pcEntropyCoder->encodePPS(pcSlice->getPPS());
            writeRBSPTrailingBits(nalu.m_Bitstream);
            accessUnit.push_back(new NALUnitEBSP(nalu));
            actualTotalBits += UInt(accessUnit.back()->m_nalUnitData.str().size()) * 8;

            // 写入SEI信息
            xCreateLeadingSEIMessages(accessUnit, pcSlice->getSPS());

            m_bSeqFirst = false;
        }
		
		//【===对参数集进行编码 end===】

        // 省略...
		
        // SEI信息的写入
		
        nextCUAddr                 = 0;

        // 获取条带的处理状态
        Int processingState = (pcSlice->getSPS()->getUseSAO())?(EXECUTE_INLOOPFILTER):(ENCODE_SLICE);
        Bool skippedSlice=false;

        while (nextCUAddr < uiRealEndAddress) // Iterate over all slices
        {
            switch(processingState)
            {
            case ENCODE_SLICE:
                {
					//【===熵编码 begin===】
                    // 省略...

                    // 对条带头进行编码
                    m_pcEntropyCoder->encodeSliceHeader(pcSlice);
                    actualHeadBits += ( m_pcEntropyCoder->getNumberOfWrittenBits() - tmpBitsBeforeWriting );

                    // 省略...

                    // 条带处理结束
                    pcSlice->setFinalized(true);

                    m_pcSbacCoder->load( &pcSbacCoders[0] );

                    pcSlice->setTileOffstForMultES( uiOneBitstreamPerSliceLength );
                    pcSlice->setTileLocationCount ( 0 );

                    // 对条带进行编码
                    m_pcSliceEncoder->encodeSlice(pcPic, pcSubstreamsOut);

                    {
                        // 省略...
						
                        for ( UInt ui = 0 ; ui < iNumSubstreams; ui++ )
                        {
                            // Flush all substreams -- this includes empty ones.
                            // Terminating bit and flush.
                            m_pcEntropyCoder->setEntropyCoder   ( &pcSbacCoders[ui], pcSlice );
                            m_pcEntropyCoder->setBitstream      (  &pcSubstreamsOut[ui] );

                            // 编码结束比特
                            m_pcEntropyCoder->encodeTerminatingBit( 1 );

                            // 对条带的熵编码完成
                            m_pcEntropyCoder->encodeSliceFinish();

                            // 省略...
                        }

                        // Complete the slice header info.
                        // 将熵编码器设置为CAVLC
                        m_pcEntropyCoder->setEntropyCoder   ( m_pcCavlcCoder, pcSlice );
                        m_pcEntropyCoder->setBitstream(&nalu.m_Bitstream);

                        // 编码区块波前前向进入点
                        m_pcEntropyCoder->encodeTilesWPPEntryPoint( pcSlice );

                        // 省略...
                    }

                    // 省略...
					
                    processingState = ENCODE_SLICE;
					
					//【===熵编码 end===】
                }
                break;
            case EXECUTE_INLOOPFILTER:
                {
					//【===SAO begin===】
                    // set entropy coder for RD
                    m_pcEntropyCoder->setEntropyCoder ( m_pcSbacCoder, pcSlice );
                    if ( pcSlice->getSPS()->getUseSAO() )
                    {
                        m_pcEntropyCoder->resetEntropy();

                        // 设置比特流
                        m_pcEntropyCoder->setBitstream( m_pcBitCounter );
                        Bool sliceEnabled[NUM_SAO_COMPONENTS];
                        m_pcSAO->initRDOCabacCoder(m_pcEncTop->getRDGoOnSbacCoder(), pcSlice);
                        // SAO处理过程
                        m_pcSAO->SAOProcess(pcPic
                            , sliceEnabled
                            , pcPic->getSlice(0)->getLambdas()
#if SAO_ENCODE_ALLOW_USE_PREDEBLOCK
                            , m_pcCfg->getSaoLcuBoundary()
#endif
                            );
                        m_pcSAO->PCMLFDisableProcess(pcPic);

                        // 省略...
                    }
                    processingState = ENCODE_SLICE;
					//【===SAO end===】
                }
                break;
            default:
                {
                    printf("Not a supported encoding state\n");
                    assert(0);
                    exit(-1);
                }
            }
        } // end iteration over slices

        // 运动估计编码
        pcPic->compressMotion();

        // 省略...

        xCalculateAddPSNR( pcPic, pcPic->getPicYuvRec(), accessUnit, dEncTime );

        // 省略...
		
		//【===码率控制更新 begin===】
        if ( m_pcCfg->getUseRateCtrl() )
        {
            Double avgQP     = m_pcRateCtrl->getRCPic()->calAverageQP();
            Double avgLambda = m_pcRateCtrl->getRCPic()->calAverageLambda();
            if ( avgLambda < 0.0 )
            {
                avgLambda = lambda;
            }
            m_pcRateCtrl->getRCPic()->updateAfterPicture( actualHeadBits, actualTotalBits, avgQP, avgLambda, pcSlice->getSliceType());
            m_pcRateCtrl->getRCPic()->addToPictureLsit( m_pcRateCtrl->getPicList() );

            m_pcRateCtrl->getRCSeq()->updateAfterPic( actualTotalBits );
            if ( pcSlice->getSliceType() != I_SLICE )
            {
                m_pcRateCtrl->getRCGOP()->updateAfterPicture( actualTotalBits );
            }
            else    // for intra picture, the estimated bits are used to update the current status in the GOP
            {
                m_pcRateCtrl->getRCGOP()->updateAfterPicture( estimatedBits );
            }
        }
		//【===码率控制更新 end===】

		// 省略...
        pcPic->getPicYuvRec()->copyToPic(pcPicYuvRecOut);

        // 设置重建标志
        pcPic->setReconMark   ( true );
        // 省略...
    }
    // 省略...
}


去方块滤波的实现


去方块滤波主函数


    去方块滤波的主函数是TComLoopFilter::loopFilterPic,下面是它的编码流程:

    1、水平边界滤波
        遍历图像中的每一个CU,对每一个CU中的PU或者TU的水平边界进行滤波(CU可以划分成TU或者PU),即调用xDeblockCU
    2、垂直边界滤波。操作同上。

Void TComLoopFilter::loopFilterPic( TComPic* pcPic )
{
	// Horizontal filtering
	// 水平过滤
	// getNumCUsInFrame获取一帧中的CU个数,然后对每一个CU进行处理
	for ( UInt uiCUAddr = 0; uiCUAddr < pcPic->getNumCUsInFrame(); uiCUAddr++ )
	{
		// 
		TComDataCU* pcCU = pcPic->getCU( uiCUAddr );

		::memset( m_aapucBS       [EDGE_VER], 0, sizeof( UChar ) * m_uiNumPartitions );
		::memset( m_aapbEdgeFilter[EDGE_VER], 0, sizeof( Bool  ) * m_uiNumPartitions );

		// CU-based deblocking
		xDeblockCU( pcCU, 0, 0, EDGE_VER );
	}

	// Vertical filtering
	// 垂直过滤
	for ( UInt uiCUAddr = 0; uiCUAddr < pcPic->getNumCUsInFrame(); uiCUAddr++ )
	{
		TComDataCU* pcCU = pcPic->getCU( uiCUAddr );

		::memset( m_aapucBS       [EDGE_HOR], 0, sizeof( UChar ) * m_uiNumPartitions );
		::memset( m_aapbEdgeFilter[EDGE_HOR], 0, sizeof( Bool  ) * m_uiNumPartitions );

		// CU-based deblocking
		xDeblockCU( pcCU, 0, 0, EDGE_HOR );
	}
}



对一个CU进行滤波操作

    重点讲解对单独一个CU进行滤波的流程(xDeblockCU)

    1、调用TComLoopFilter::xSetLoopfilterParam函数,用来判断CU的边界是否存在,例如是否有内部边界(假如CU划分成了PU或者TU,那么就拥有),是否拥有左边的边界和上边的边界,以及边界是否可用(或者是否需要滤波),把这些信息保存在LFCUParam对象中。

    2、调用xSetEdgefilterTU,根据LFCUParam对象的信息设置TU及其内部base_unit(4x4大小的块)的边界是否需要滤波。

    3、调用xSetEdgefilterPU,根据LFCUParam对象的信息设置PU及其内部base_unit(4x4大小的块)的边界是否需要滤波。

    4、调用xGetBoundaryStrengthSingle,计算边界强度

    5、调用xEdgeFilterLuma对亮度块进行滤波,在函数的内部会根据一定的图像内容特性来判断是否使用强滤波器,最后调用xPelFilterLuma来进行真实的滤波操作

    6、调用xEdgeFilterChroma对色度块进行滤波(流程和xEdgeFilterLuma类似)


// 去方块滤波的参数结构
typedef struct _LFCUParam
{
	Bool bInternalEdge;                     ///< indicates internal edge 表示内部边界
	Bool bLeftEdge;                         ///< indicates left edge 表示左边边界
	Bool bTopEdge;                          ///< indicates top edge 表示上面边界
} LFCUParam;


对一个CU进行滤波:


// 对一个CU进行去方块滤波
Void TComLoopFilter::xDeblockCU( TComDataCU* pcCU, UInt uiAbsZorderIdx, UInt uiDepth, Int Edge )
{
	if(pcCU->getPic()==0||pcCU->getPartitionSize(uiAbsZorderIdx)==SIZE_NONE)
	{
		return;
	}
	// 获取CU所属的图片
	TComPic* pcPic     = pcCU->getPic();
	UInt uiCurNumParts = pcPic->getNumPartInCU() >> (uiDepth<<1);
	UInt uiQNumParts   = uiCurNumParts>>2;

	if( pcCU->getDepth(uiAbsZorderIdx) > uiDepth )
	{
		for ( UInt uiPartIdx = 0; uiPartIdx < 4; uiPartIdx++, uiAbsZorderIdx+=uiQNumParts )
		{
			// 左上角的坐标
			UInt uiLPelX   = pcCU->getCUPelX() + g_auiRasterToPelX[ g_auiZscanToRaster[uiAbsZorderIdx] ];
			UInt uiTPelY   = pcCU->getCUPelY() + g_auiRasterToPelY[ g_auiZscanToRaster[uiAbsZorderIdx] ];

			if( ( uiLPelX < pcCU->getSlice()->getSPS()->getPicWidthInLumaSamples() ) && ( uiTPelY < pcCU->getSlice()->getSPS()->getPicHeightInLumaSamples() ) )
			{
				// 去方块滤波,这是一个递归调用
				xDeblockCU( pcCU, uiAbsZorderIdx, uiDepth+1, Edge );
			}
		}
		return;
	}

	// 判断CU的左边、上边边界是否可用,把相关信息保存在LFCUParam对象中
	xSetLoopfilterParam( pcCU, uiAbsZorderIdx );

	// 根据LFCUParam对象的信息设置TU及其内部base_unit(4x4大小的块)的边界是否需要滤波
	xSetEdgefilterTU   ( pcCU, uiAbsZorderIdx , uiAbsZorderIdx, uiDepth );
	// 根据LFCUParam对象的信息设置PU及其内部base_unit(4x4大小的块)的边界是否需要滤波
	xSetEdgefilterPU   ( pcCU, uiAbsZorderIdx );

	Int iDir = Edge;
	for( UInt uiPartIdx = uiAbsZorderIdx; uiPartIdx < uiAbsZorderIdx + uiCurNumParts; uiPartIdx++ )
	{
		UInt uiBSCheck;
		if( (g_uiMaxCUWidth >> g_uiMaxCUDepth) == 4 ) 
		{
			uiBSCheck = (iDir == EDGE_VER && uiPartIdx%2 == 0) || (iDir == EDGE_HOR && (uiPartIdx-((uiPartIdx>>2)<<2))/2 == 0);
		}
		else
		{
			uiBSCheck = 1;
		}

		// 对于需要进行滤波的块,才进行边界强度计算
		if ( m_aapbEdgeFilter[iDir][uiPartIdx] && uiBSCheck )
		{
			// 计算边界强度
			xGetBoundaryStrengthSingle ( pcCU, iDir, uiPartIdx );
		}
	}

	UInt uiPelsInPart = g_uiMaxCUWidth >> g_uiMaxCUDepth;
	UInt PartIdxIncr = DEBLOCK_SMALLEST_BLOCK / uiPelsInPart ? DEBLOCK_SMALLEST_BLOCK / uiPelsInPart : 1 ;

	UInt uiSizeInPU = pcPic->getNumPartInWidth()>>(uiDepth);

	for ( UInt iEdge = 0; iEdge < uiSizeInPU ; iEdge+=PartIdxIncr)
	{
		// 对亮度块的边缘进行滤波
		xEdgeFilterLuma     ( pcCU, uiAbsZorderIdx, uiDepth, iDir, iEdge );
		if ( (uiPelsInPart>DEBLOCK_SMALLEST_BLOCK) || (iEdge % ( (DEBLOCK_SMALLEST_BLOCK<<1)/uiPelsInPart ) ) == 0 )
		{
			// 对色度块的边缘进行滤波
			xEdgeFilterChroma   ( pcCU, uiAbsZorderIdx, uiDepth, iDir, iEdge );
		}
	}
}



判断TU、PU及其内部base_unit是否需要滤波


    过程比较简单,根据一些边界信息判断TU/PU以及内部的base_unit是否需要进行滤波,并保存这些相关的信息

    涉及的函数有下面几个:

    xSetLoopfilterParam,判断一个CU的左边、上边边界是否需要滤波(或者是否可用)

    xSetEdgefilterTU,根据LFCUParam对象信息判断TU及其内部base_unit的边界是否需要滤波,调用了  xSetEdgefilterMultiple函数

    xSetEdgefilterPU,根据LFCUParam对象信息判断PU及其内部base_unit的边界是否需要滤波,调用了  xSetEdgefilterMultiple函数

    xSetEdgefilterMultiple,设置TU/PU及其内部base_unit的滤波标识,主要是设置m_aapbEdgeFilter数组

    xCalcBsIdx,计算base_unit的索引(坐标)




计算边界强度


    我们假设当前像素块是Q,与它相邻的像素块是P,边界强度是BS,那么获取边界强度的流程如下:

HM编码器代码阅读(26)——去方块滤波_第1张图片

// 计算边界强度BS
Void TComLoopFilter::xGetBoundaryStrengthSingle ( TComDataCU* pcCU, Int iDir, UInt uiAbsPartIdx )
{
	TComSlice* const pcSlice = pcCU->getSlice();

	const UInt uiPartQ = uiAbsPartIdx;
	TComDataCU* const pcCUQ = pcCU;

	UInt uiPartP;
	TComDataCU* pcCUP;
	UInt uiBs = 0;

	//-- Calculate Block Index
	if (iDir == EDGE_VER)
	{
		pcCUP = pcCUQ->getPULeft(uiPartP, uiPartQ, !pcCU->getSlice()->getLFCrossSliceBoundaryFlag(), !m_bLFCrossTileBoundary);
	}
	else  // (iDir == EDGE_HOR)
	{
		pcCUP = pcCUQ->getPUAbove(uiPartP, uiPartQ, !pcCU->getSlice()->getLFCrossSliceBoundaryFlag(), false, !m_bLFCrossTileBoundary);
	}

	//-- Set BS for Intra MB : BS = 4 or 3
	// 如果当前CU或者相邻CU使用了帧内预测,那么强度设置为2
	if ( pcCUP->isIntra(uiPartP) || pcCUQ->isIntra(uiPartQ) )
	{
		uiBs = 2;
	}

	//-- Set BS for not Intra MB : BS = 2 or 1 or 0
	if ( !pcCUP->isIntra(uiPartP) && !pcCUQ->isIntra(uiPartQ) )
	{
		UInt nsPartQ = uiPartQ;
		UInt nsPartP = uiPartP;

		// 如果TU/PU边界需要滤波,而且当前块或者相邻块的Cbf存在
		if ( m_aapucBS[iDir][uiAbsPartIdx] && (pcCUQ->getCbf( nsPartQ, TEXT_LUMA, pcCUQ->getTransformIdx(nsPartQ)) != 0 || pcCUP->getCbf( nsPartP, TEXT_LUMA, pcCUP->getTransformIdx(nsPartP) ) != 0) )
		{
			// 边界强度等于1
			uiBs = 1;
		}
		else
		{
			// pcCUP表示相邻块(用P表示),pcCUQ表示当前块(用Q表示)
			if (iDir == EDGE_HOR)
			{
				pcCUP = pcCUQ->getPUAbove(uiPartP, uiPartQ, !pcCU->getSlice()->getLFCrossSliceBoundaryFlag(), false, !m_bLFCrossTileBoundary);
			}
			// 如果P和Q有一个属于B slice
			if (pcSlice->isInterB() || pcCUP->getSlice()->isInterB())
			{
				Int iRefIdx;
				TComPic *piRefP0, *piRefP1, *piRefQ0, *piRefQ1;
				iRefIdx = pcCUP->getCUMvField(REF_PIC_LIST_0)->getRefIdx(uiPartP);
				piRefP0 = (iRefIdx < 0) ? NULL : pcCUP->getSlice()->getRefPic(REF_PIC_LIST_0, iRefIdx);
				iRefIdx = pcCUP->getCUMvField(REF_PIC_LIST_1)->getRefIdx(uiPartP);
				piRefP1 = (iRefIdx < 0) ? NULL : pcCUP->getSlice()->getRefPic(REF_PIC_LIST_1, iRefIdx);
				iRefIdx = pcCUQ->getCUMvField(REF_PIC_LIST_0)->getRefIdx(uiPartQ);
				piRefQ0 = (iRefIdx < 0) ? NULL : pcSlice->getRefPic(REF_PIC_LIST_0, iRefIdx);
				iRefIdx = pcCUQ->getCUMvField(REF_PIC_LIST_1)->getRefIdx(uiPartQ);
				piRefQ1 = (iRefIdx < 0) ? NULL : pcSlice->getRefPic(REF_PIC_LIST_1, iRefIdx);

				TComMv pcMvP0 = pcCUP->getCUMvField(REF_PIC_LIST_0)->getMv(uiPartP);
				TComMv pcMvP1 = pcCUP->getCUMvField(REF_PIC_LIST_1)->getMv(uiPartP);
				TComMv pcMvQ0 = pcCUQ->getCUMvField(REF_PIC_LIST_0)->getMv(uiPartQ);
				TComMv pcMvQ1 = pcCUQ->getCUMvField(REF_PIC_LIST_1)->getMv(uiPartQ);

				if (piRefP0 == NULL) pcMvP0.setZero();
				if (piRefP1 == NULL) pcMvP1.setZero();
				if (piRefQ0 == NULL) pcMvQ0.setZero();
				if (piRefQ1 == NULL) pcMvQ1.setZero();

				// P和Q使有一个参考帧相同
				if ( ((piRefP0==piRefQ0)&&(piRefP1==piRefQ1)) || ((piRefP0==piRefQ1)&&(piRefP1==piRefQ0)) )
				{
					// P的前向和后向参考帧不同
					if ( piRefP0 != piRefP1 )   // Different L0 & L1
					{
						// P和Q的前向参考帧一样
						if ( piRefP0 == piRefQ0 )
						{
							// P和Q的MV(h或者v分量)的差值的绝对值是否大于等于4,如果是,那么强度是1,否则是0
							uiBs  = ((abs(pcMvQ0.getHor() - pcMvP0.getHor()) >= 4) ||
								(abs(pcMvQ0.getVer() - pcMvP0.getVer()) >= 4) ||
								(abs(pcMvQ1.getHor() - pcMvP1.getHor()) >= 4) ||
								(abs(pcMvQ1.getVer() - pcMvP1.getVer()) >= 4)) ? 1 : 0;
						}
						else
						{
							uiBs  = ((abs(pcMvQ1.getHor() - pcMvP0.getHor()) >= 4) ||
								(abs(pcMvQ1.getVer() - pcMvP0.getVer()) >= 4) ||
								(abs(pcMvQ0.getHor() - pcMvP1.getHor()) >= 4) ||
								(abs(pcMvQ0.getVer() - pcMvP1.getVer()) >= 4)) ? 1 : 0;
						}
					}
					// P的前向和后向参考帧相同
					else    // Same L0 & L1
					{
						uiBs  = ((abs(pcMvQ0.getHor() - pcMvP0.getHor()) >= 4) ||
							(abs(pcMvQ0.getVer() - pcMvP0.getVer()) >= 4) ||
							(abs(pcMvQ1.getHor() - pcMvP1.getHor()) >= 4) ||
							(abs(pcMvQ1.getVer() - pcMvP1.getVer()) >= 4)) &&
							((abs(pcMvQ1.getHor() - pcMvP0.getHor()) >= 4) ||
							(abs(pcMvQ1.getVer() - pcMvP0.getVer()) >= 4) ||
							(abs(pcMvQ0.getHor() - pcMvP1.getHor()) >= 4) ||
							(abs(pcMvQ0.getVer() - pcMvP1.getVer()) >= 4)) ? 1 : 0;
					}
				}
				// P和Q的参考帧都不相同
				else // for all different Ref_Idx
				{
					// 强度设置为1
					uiBs = 1;
				}
			}
			// P和Q都属于P slice
			else  // pcSlice->isInterP()
			{
				Int iRefIdx;
				TComPic *piRefP0, *piRefQ0;
				iRefIdx = pcCUP->getCUMvField(REF_PIC_LIST_0)->getRefIdx(uiPartP);
				piRefP0 = (iRefIdx < 0) ? NULL : pcCUP->getSlice()->getRefPic(REF_PIC_LIST_0, iRefIdx);
				iRefIdx = pcCUQ->getCUMvField(REF_PIC_LIST_0)->getRefIdx(uiPartQ);
				piRefQ0 = (iRefIdx < 0) ? NULL : pcSlice->getRefPic(REF_PIC_LIST_0, iRefIdx);
				TComMv pcMvP0 = pcCUP->getCUMvField(REF_PIC_LIST_0)->getMv(uiPartP);
				TComMv pcMvQ0 = pcCUQ->getCUMvField(REF_PIC_LIST_0)->getMv(uiPartQ);

				if (piRefP0 == NULL) pcMvP0.setZero();
				if (piRefQ0 == NULL) pcMvQ0.setZero();

				// P和Q的MV(h或者v分量)的差值的绝对值是否大于等于4,如果是,那么强度是1,否则是0
				uiBs  = ((piRefP0 != piRefQ0) ||
					(abs(pcMvQ0.getHor() - pcMvP0.getHor()) >= 4) ||
					(abs(pcMvQ0.getVer() - pcMvP0.getVer()) >= 4)) ? 1 : 0;
			}
		}   // enf of "if( one of BCBP == 0 )"
	}   // enf of "if( not Intra )"

	// 把边界强度放进数组中保存起来
	m_aapucBS[iDir][uiAbsPartIdx] = uiBs;
}



对亮度块的边缘进行滤波


    在正式进行滤波之前先做下面的判断和操作:

    1、如果BS=0,那么不需要进行滤波;

    2、计算像素块边界的纹理度d

    3、纹理度越大表示图像越不平坦,当纹理度大于某个阈值的时候,表明这是图像内容本身造成的不平坦,而不是方块效应造成的;因此当纹理度大于某个阈值的时候不需要进行滤波

    4、滤波强弱的选择。对视频的内容进行更加细致的判断,以确定滤波的强度

    5、进行滤波操作

// 对亮度块的边缘进行滤波
Void TComLoopFilter::xEdgeFilterLuma( TComDataCU* pcCU, UInt uiAbsZorderIdx, UInt uiDepth, Int iDir, Int iEdge  )
{
	// 获取指向重建yuv的指针
	TComPicYuv* pcPicYuvRec = pcCU->getPic()->getPicYuvRec();

	// yuv的像素其实地址
	Pel* piSrc    = pcPicYuvRec->getLumaAddr( pcCU->getAddr(), uiAbsZorderIdx );
	Pel* piTmpSrc = piSrc;

	// 偏移
	Int  iStride = pcPicYuvRec->getStride();
	Int iQP = 0;
	Int iQP_P = 0;
	Int iQP_Q = 0;
	UInt uiNumParts = pcCU->getPic()->getNumPartInWidth()>>uiDepth;

	UInt  uiPelsInPart = g_uiMaxCUWidth >> g_uiMaxCUDepth;
	UInt  uiBsAbsIdx = 0, uiBs = 0;
	Int   iOffset, iSrcStep;

	Bool  bPCMFilter = (pcCU->getSlice()->getSPS()->getUsePCM() && pcCU->getSlice()->getSPS()->getPCMFilterDisableFlag())? true : false;

	Bool  bPartPNoFilter = false;
	Bool  bPartQNoFilter = false; 
	UInt  uiPartPIdx = 0;
	UInt  uiPartQIdx = 0;
	TComDataCU* pcCUP = pcCU; 
	TComDataCU* pcCUQ = pcCU;
	Int  betaOffsetDiv2 = pcCUQ->getSlice()->getDeblockingFilterBetaOffsetDiv2();
	Int  tcOffsetDiv2 = pcCUQ->getSlice()->getDeblockingFilterTcOffsetDiv2();

	// 垂直方向
	if (iDir == EDGE_VER)
	{
		iOffset = 1;
		iSrcStep = iStride;
		piTmpSrc += iEdge*uiPelsInPart;
	}
	// 水平方向
	else  // (iDir == EDGE_HOR)
	{
		iOffset = iStride;
		iSrcStep = 1;
		piTmpSrc += iEdge*uiPelsInPart*iStride;
	}

	for ( UInt iIdx = 0; iIdx < uiNumParts; iIdx++ )
	{
		// 获取当前块的索引
		uiBsAbsIdx = xCalcBsIdx( pcCU, uiAbsZorderIdx, iDir, iEdge, iIdx);
		// 得到这一块的边界强度
		uiBs = m_aapucBS[iDir][uiBsAbsIdx];
		
		// 边界强度大于0,才需要进行滤波
		if ( uiBs )
		{
			iQP_Q = pcCU->getQP( uiBsAbsIdx );
			uiPartQIdx = uiBsAbsIdx;
			// Derive neighboring PU index
			if (iDir == EDGE_VER)
			{
				pcCUP = pcCUQ->getPULeft (uiPartPIdx, uiPartQIdx,!pcCU->getSlice()->getLFCrossSliceBoundaryFlag(), !m_bLFCrossTileBoundary);
			}
			else  // (iDir == EDGE_HOR)
			{
				pcCUP = pcCUQ->getPUAbove(uiPartPIdx, uiPartQIdx,!pcCU->getSlice()->getLFCrossSliceBoundaryFlag(), false, !m_bLFCrossTileBoundary);
			}

			iQP_P = pcCUP->getQP(uiPartPIdx);
			iQP = (iQP_P + iQP_Q + 1) >> 1;
			Int iBitdepthScale = 1 << (g_bitDepthY-8);

			Int iIndexTC = Clip3(0, MAX_QP+DEFAULT_INTRA_TC_OFFSET, Int(iQP + DEFAULT_INTRA_TC_OFFSET*(uiBs-1) + (tcOffsetDiv2 << 1)));
			Int iIndexB = Clip3(0, MAX_QP, iQP + (betaOffsetDiv2 << 1));

			Int iTc =  sm_tcTable[iIndexTC]*iBitdepthScale;
			Int iBeta = sm_betaTable[iIndexB]*iBitdepthScale;
			Int iSideThreshold = (iBeta+(iBeta>>1))>>3;
			Int iThrCut = iTc*10;

			UInt  uiBlocksInPart = uiPelsInPart / 4 ? uiPelsInPart / 4 : 1;
			
			// 计算块边界的纹理度,通过这些内容特性来判断是否需要进行滤波操作
			for (UInt iBlkIdx = 0; iBlkIdxgetSlice()->getPPS()->getTransquantBypassEnableFlag())
				{
					// Check if each of PUs is I_PCM with LF disabling
					bPartPNoFilter = (bPCMFilter && pcCUP->getIPCMFlag(uiPartPIdx));
					bPartQNoFilter = (bPCMFilter && pcCUQ->getIPCMFlag(uiPartQIdx));

					// check if each of PUs is lossless coded
					bPartPNoFilter = bPartPNoFilter || (pcCUP->isLosslessCoded(uiPartPIdx) );
					bPartQNoFilter = bPartQNoFilter || (pcCUQ->isLosslessCoded(uiPartQIdx) );
				}

				// 纹理度值越大表示越不平坦,当它大到一定的程度时,表示这是这是图像本身的特性,而不是方块效应,因此不用滤波
				// iBeta是阈值,纹理度值小于它的时候才需要进行滤波
				if (d < iBeta)
				{ 
					Bool bFilterP = (dp < iSideThreshold);
					Bool bFilterQ = (dq < iSideThreshold);

					// 判断是否需要进行强滤波
					Bool sw =  xUseStrongFiltering( iOffset, 2*d0, iBeta, iTc, piTmpSrc+iSrcStep*(iIdx*uiPelsInPart+iBlkIdx*4+0))
						&& xUseStrongFiltering( iOffset, 2*d3, iBeta, iTc, piTmpSrc+iSrcStep*(iIdx*uiPelsInPart+iBlkIdx*4+3));

					for ( Int i = 0; i < DEBLOCK_SMALLEST_BLOCK/2; i++)
					{
						xPelFilterLuma( piTmpSrc+iSrcStep*(iIdx*uiPelsInPart+iBlkIdx*4+i), iOffset, iTc, sw, bPartPNoFilter, bPartQNoFilter, iThrCut, bFilterP, bFilterQ);
					}
				}
			}
		}
	}
}

滤波强弱的选择

滤波强弱选择的依据:

1、如果边界两侧像素值平坦,会在视觉上造成更强的块效应,因此要对边界周围的像素进行大范围、大幅度的修正

2、如果边界处的像素差值特别大,由于像素失真总是会处于一定的范围内,当差值超出一定范围后,这种块边界差别是由视频内容本身造成的

// 滤波强弱的选择
// 强滤波表示进行的滤波的范围大
__inline Bool TComLoopFilter::xUseStrongFiltering( Int offset, Int d, Int beta, Int tc, Pel* piSrc)
{
	Pel m4  = piSrc[0];
	Pel m3  = piSrc[-offset];
	Pel m7  = piSrc[ offset*3];
	Pel m0  = piSrc[-offset*4];

	Int d_strong = abs(m0-m3) + abs(m7-m4);

	return ( (d_strong < (beta>>3)) && (d<(beta>>2)) && ( abs(m3-m4) < ((tc*5+1)>>1)) );
}


滤波函数

    所谓滤波就是对像素做一定程度的修正,减少方块效应

// 滤波操作
__inline Void TComLoopFilter::xPelFilterLuma( Pel* piSrc, Int iOffset, Int tc , Bool sw, Bool bPartPNoFilter, Bool bPartQNoFilter, Int iThrCut, Bool bFilterSecondP, Bool bFilterSecondQ)
{
	Int delta;

	Pel m4  = piSrc[0];
	Pel m3  = piSrc[-iOffset];
	Pel m5  = piSrc[ iOffset];
	Pel m2  = piSrc[-iOffset*2];
	Pel m6  = piSrc[ iOffset*2];
	Pel m1  = piSrc[-iOffset*3];
	Pel m7  = piSrc[ iOffset*3];
	Pel m0  = piSrc[-iOffset*4];

	// 强滤波
	if (sw)
	{
		piSrc[-iOffset]   = Clip3(m3-2*tc, m3+2*tc, ((m1 + 2*m2 + 2*m3 + 2*m4 + m5 + 4) >> 3));
		piSrc[0]          = Clip3(m4-2*tc, m4+2*tc, ((m2 + 2*m3 + 2*m4 + 2*m5 + m6 + 4) >> 3));
		piSrc[-iOffset*2] = Clip3(m2-2*tc, m2+2*tc, ((m1 + m2 + m3 + m4 + 2)>>2));
		piSrc[ iOffset]   = Clip3(m5-2*tc, m5+2*tc, ((m3 + m4 + m5 + m6 + 2)>>2));
		piSrc[-iOffset*3] = Clip3(m1-2*tc, m1+2*tc, ((2*m0 + 3*m1 + m2 + m3 + m4 + 4 )>>3));
		piSrc[ iOffset*2] = Clip3(m6-2*tc, m6+2*tc, ((m3 + m4 + m5 + 3*m6 + 2*m7 +4 )>>3));
	}
	// 弱滤波
	else
	{
		/* Weak filter */
		delta = (9*(m4-m3) -3*(m5-m2) + 8)>>4 ;

		if ( abs(delta) < iThrCut )
		{
			delta = Clip3(-tc, tc, delta);        
			piSrc[-iOffset] = ClipY((m3+delta));
			piSrc[0] = ClipY((m4-delta));

			Int tc2 = tc>>1;
			if(bFilterSecondP)
			{
				Int delta1 = Clip3(-tc2, tc2, (( ((m1+m3+1)>>1)- m2+delta)>>1));
				piSrc[-iOffset*2] = ClipY((m2+delta1));
			}
			if(bFilterSecondQ)
			{
				Int delta2 = Clip3(-tc2, tc2, (( ((m6+m4+1)>>1)- m5-delta)>>1));
				piSrc[ iOffset] = ClipY((m5+delta2));
			}
		}
	}

	if(bPartPNoFilter)
	{
		piSrc[-iOffset] = m3;
		piSrc[-iOffset*2] = m2;
		piSrc[-iOffset*3] = m1;
	}
	if(bPartQNoFilter)
	{
		piSrc[0] = m4;
		piSrc[ iOffset] = m5;
		piSrc[ iOffset*2] = m6;
	}
}




你可能感兴趣的:(HEVC编码器HM源码阅读)