去方块滤波的目的是去除去方块效应
去方块滤波介绍
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)——环路滤波
通过一个简化版的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、水平边界滤波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进行滤波的流程(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是否需要进行滤波,并保存这些相关的信息
涉及的函数有下面几个:
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; 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;
}
}