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; } }
相关文章推荐
- HM编码器代码阅读(36)——帧内预测(三)帧内预测之参考像素块的预处理和滤波
- HEVC去方块滤波原理以及代码流程
- HM编码器代码阅读(2)——框架以及主要流程
- [置顶] HM编码器代码阅读(40)——帧内预测的总结
- HM编码器代码阅读(45)——样点自适应补偿SAO(四)对重建像素进行补偿
- HM编码器代码阅读(44)——样点自适应补偿SAO(三)选取最优的SAO模式并确定补偿值
- 解码端去方块滤波代码详述
- [置顶] HM编码器代码阅读(33)——帧间预测的总结
- JM8.6的解码端去方块滤波代码详述
- HM编码器代码阅读(6)——GOP、IDR帧、I帧周期的关系
- HM编码器代码阅读(18)——变换以及量化(一)
- HM编码器代码阅读(13)——帧间预测之一inter模式
- HM编码器代码阅读(38)——帧内预测(五)帧内预测之正式的预测操作
- HM编码器代码阅读(29)——码率控制
- homerHEVC代码阅读(26)——编码器工作线程/wfpp线程wfpp_encoder_thread
- HM编码器代码阅读(10)——片的编码
- HM编码器代码阅读(21)——熵编码的概念以及在HEVC中应用
- HM编码器代码阅读(42)——率失真优化
- HM编码器代码阅读(31)——帧间预测之AMVP/Merge模式(六)运动补偿
- HM编码器代码阅读(41)——码率控制(二)