您的位置:首页 > 编程语言

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

2017-02-12 17:40 633 查看

去方块滤波

原理和技术

    去方块滤波的目的是去除去方块效应
    去方块滤波介绍
    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<TComPic*>& rcListPic, TComList<TComPicYuv*>& rcListPicYuvRecOut, std::list<AccessUnit>& 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<TEncRCPic*> 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<TEncRCPic*> 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(nextCUAddr<uiRealEndAddress) // determine slice boundaries
{
pcSlice->setNextSlice       ( 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,那么获取边界强度的流程如下:



// 计算边界强度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; iBlkIdx<uiBlocksInPart; iBlkIdx ++)
{
Int dp0 = xCalcDP( piTmpSrc+iSrcStep*(iIdx*uiPelsInPart+iBlkIdx*4+0), iOffset);
Int dq0 = xCalcDQ( piTmpSrc+iSrcStep*(iIdx*uiPelsInPart+iBlkIdx*4+0), iOffset);
Int dp3 = xCalcDP( piTmpSrc+iSrcStep*(iIdx*uiPelsInPart+iBlkIdx*4+3), iOffset);
Int dq3 = xCalcDQ( piTmpSrc+iSrcStep*(iIdx*uiPelsInPart+iBlkIdx*4+3), iOffset);
Int d0 = dp0 + dq0;
Int d3 = dp3 + dq3;

Int dp = dp0 + dp3;
Int dq = dq0 + dq3;

// d就是最后计算出来的纹理度值
Int d =  d0 + d3;

if (bPCMFilter || pcCU->getSlice()->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;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: