此系列是为了记录自己学习VTM10.0的过程和锻炼表达能力,主要是从解码端进行入手。由于本人水平有限,出现的错误恳请大家指正,欢迎与大家一起交流进步。

之前的博文(VTM10.0代码学习3)提到两个重要的函数coding_tree_unit()和decompressCtu(),coding_tree_unit()已经说过了,本篇博文就来讲解一下decompressCtu()。这个函数是利用coding_tree_unit()得到的语义信息来重构CTU块,涉及到多种预测模式。前面也提到过VTM中主要将预测模式分成四种:传统帧内、帧间、PLT和IBC。PLT和IBC的部分就暂且先跳过,本篇博文还会涉及传统帧内预测的过程,至于帧间预测就留到下一篇博文细说。

1. decompressCtu()

下面的代码会省略IBC的部分

const int maxNumChannelType = cs.pcv->chrFormat != CHROMA_400 && CS::isDualITree( cs ) ? 2 : 1;for( int ch = 0; ch < maxNumChannelType; ch++ )
{const ChannelType chType = ChannelType( ch );for( auto &currCU : cs.traverseCUs( CS::getArea( cs, ctuArea, chType ), chType ) ){if (currCU.predMode != MODE_INTRA && currCU.predMode != MODE_PLT && currCU.Y().valid()){//如果预测模式是IBC或者帧间,且亮度分量存在xDeriveCUMV(currCU);//获取MV,自然包括如何获取MVP,也就是HMVP或Merge列表的构建过程switch( currCU.predMode ){case MODE_INTER:case MODE_IBC:xReconInter( currCU );//重构帧间模式或IBC模式下的CU块break;case MODE_PLT:case MODE_INTRA:xReconIntraQT( currCU );//重构传统帧内模式或PLT模式下的CU块break;default:THROW( "Invalid prediction mode" );break;}}}
}

maxNumChannelType:为2表示当前CTU亮度和色度分量的划分方式不同,为1则表示亮度和色度的划分方式相同

第一个for循环:如果开启dual tree,则要分别对亮度和色度执行一次;不开启则只执行一次

chType:表示当前处理的划分方式是对亮度还是色度划分

第二个for循环:依据当前划分方式,遍历CTU内所有CU(可能分亮度和色度划分),逐个重构

xDeriveCUMV():如果预测模式是IBC或帧间,则需要用此函数获取MV。主要分merge(skip是特殊的merge)和AMVP两种情况,具体参考第2大节

switch分支:如果当前的预测模式是帧间或IBC就调用xReconInter()函数(参考下一篇博文),如果当前的预测模式是PLT或传统帧内就调用xReconIntraQT()函数(参考第3大节)

2. xDeriveCUMV()

由于有关merge的情况过于复杂,暂且先跳过不提,简要提及在AMVP情况下是如何获取MV的。如果开启merge要分IBC、affine、regular、CIIP、GPM五种情况,regular的情况下还有是否开启MMVD两种情况。

MergeCtx mrgCtx;
if( pu.mergeFlag )
{}
else
{if ( cu.imv && !pu.cu->affine && !cu.cs->pcv->isEncoder )//如果开启AMVR,且当前不是affine模式,且当前是解码端{PU::applyImv(pu, mrgCtx, m_pcInterPred);//获取非affine情况下开启AMVR时的MV}else{if( pu.cu->affine )//如果开启Affine模式{}else if (CU::isIBC(*pu.cu) && pu.interDir == 1){}else{}PU::spanMotionInfo( pu, mrgCtx );}
}

第一个if分支:判断条件为真就是merge模式,为假就是AMVP模式

第二个if分支:判断条件为真就是非affine模式开启AMVR的情况,调用applyImv()函数获取MV;为假就是其余情况。获取MV的过程都类似,后面挑个开启affine的情况讲,这个函数就不细说了

第二个if分支:if的判断条件为真就是开启affine的情况,elseif的判断条件为真就是IBC预测的情况,进入else就是其余情况。2.1小节会细说开启affine的情况下如何获取MV

spanMotionInfo():向cs中加入当前PU对应的MotionInfo,这些信息用来构建AMVP或merge的候选列表

2.1 开启affine的情况下获取MV

for ( uint32_t uiRefListIdx = 0; uiRefListIdx < 2; uiRefListIdx++ )
{RefPicList eRefList = RefPicList( uiRefListIdx );if ( pu.cs->slice->getNumRefIdx( eRefList ) > 0 && ( pu.interDir & ( 1 << uiRefListIdx ) ) ){AffineAMVPInfo affineAMVPInfo;//存储affine情况下AMVP列表PU::fillAffineMvpCand( pu, eRefList, pu.refIdx[eRefList], affineAMVPInfo );const unsigned mvp_idx = pu.mvpIdx[eRefList];pu.mvpNum[eRefList] = affineAMVPInfo.numCand;if (!cu.cs->pcv->isEncoder)//如果是解码端,则需要将MVD的精度从传送精度变为内部处理精度{pu.mvdAffi[eRefList][0].changeAffinePrecAmvr2Internal(pu.cu->imv);pu.mvdAffi[eRefList][1].changeAffinePrecAmvr2Internal(pu.cu->imv);if (cu.affineType == AFFINEMODEL_6PARAM){pu.mvdAffi[eRefList][2].changeAffinePrecAmvr2Internal(pu.cu->imv);}Mv mvLT = affineAMVPInfo.mvCandLT[mvp_idx] + pu.mvdAffi[eRefList][0];Mv mvRT = affineAMVPInfo.mvCandRT[mvp_idx] + pu.mvdAffi[eRefList][1];mvRT += pu.mvdAffi[eRefList][0];Mv mvLB;if ( cu.affineType == AFFINEMODEL_6PARAM ){mvLB = affineAMVPInfo.mvCandLB[mvp_idx] + pu.mvdAffi[eRefList][2];mvLB += pu.mvdAffi[eRefList][0];}PU::setAllAffineMv(pu, mvLT, mvRT, mvLB, eRefList, true);//设置affine模式下所有子块的MV}}
}

for循环:分reference list 0和reference list 1执行循环体

eRefList:eRefList表示是reference list 0还是reference list 1

第一个if分支:判断条件为真表示码流中存在相应reference list的mvd

affineAMVPInfo:存储着affine情况下的AMVP列表

fillAffineMvpCand():填充AMVP候选列表

mvp_idx:表示当前PU的mvp在AMVP候选列表中的索引

mvpNum:表示AMVP候选列表中mvp的数量

第二个if分支:码流传输MVD的精度可能与VTM中处理MVD的精度不同,所以需要转换。VTM内部处理MVD的精度应该是1/16像素

后面的语句主要是将MVP与MVD相加获取MV。由于affine模式下PU内子块的MV都不同,所以还要调用setAllAffineMv()函数设置子块MV

3. xReconIntraQT()

前面提到过这个函数用来处理PLT预测和传统帧内预测两种情况,有关PLT的内容会暂且先跳过。

if (CU::isPLT(cu))//如果当前CU开启PLT模式
{//...return;
}if (cu.colorTransform)//如果开启ACT模式
{xIntraRecACTQT(cu);
}
else//分亮度和色度调用xIntraRecQT函数
{const uint32_t numChType = ::getNumberValidChannels( cu.chromaFormat );for( uint32_t chType = CHANNEL_TYPE_LUMA; chType < numChType; chType++ ){if( cu.blocks[chType].valid() ){xIntraRecQT( cu, ChannelType( chType ) );}}
}

第一个if分支:判断条件为真表示当前CU的预测方式为PLT,为假就是传统帧内预测模式

第二个if分支:判断条件为真表示开启ACT,调用xIntraRecACTQT()函数重构CU。过程与不开启ACT的情况类似,所以就不细说了。为假表示不开启ACT,分亮度和色度调用xIntraRecQT()函数重构CU,具体参考3.1小节

3.1 xIntraRecQT()

for( auto &currTU : CU::traverseTUs( cu ) )//遍历当前CU内所有TU,调用xIntraRecBlk函数,分Y,Cb,Cr三个分量
{if( isLuma( chType ) ){xIntraRecBlk( currTU, COMPONENT_Y );}else{const uint32_t numValidComp = getNumberValidComponents( cu.chromaFormat );for( uint32_t compID = COMPONENT_Cb; compID < numValidComp; compID++ ){xIntraRecBlk( currTU, ComponentID( compID ) );}}
}

for循环:遍历当前CU内所有TU,逐个重构

if分支:分Y,Cb,Cr三个分量,调用xIntraRecBlk()函数进行重构,具体参考3.2小节

3.2 xIntraRecBlk()

CodingStructure &cs = *tu.cs;
const CompArea &area      = tu.blocks[compID];const ChannelType chType  = toChannelType( compID );PelBuf piPred       = cs.getPredBuf( area );//获取在cs中存储的当前TU的相应分量的像素预测值地址const PredictionUnit &pu  = *tu.cs->getPU( area.pos(), chType );//获取当前TU对应的PU
const uint32_t uiChFinalMode  = PU::getFinalIntraMode( pu, chType );//获得当前PU的预测模式
PelBuf pReco              = cs.getRecoBuf(area);//获取在cs中存储的当前TU的相应分量的像素重建值地址

piPred:cs中存储的当前TU的相应分量的像素预测值地址

uiChFinalMode:当前TU所在PU对应分量的预测模式,主要是如果色度的预测模式是DM,需要转换成具体的预测模式

pReco:cs中存储的当前TU的相应分量的像素重建值地址

//===== init availability pattern =====
bool predRegDiffFromTB = CU::isPredRegDiffFromTB(*tu.cu, compID);
bool firstTBInPredReg = CU::isFirstTBInPredReg(*tu.cu, compID, area);
CompArea areaPredReg(COMPONENT_Y, tu.chromaFormat, area);
if (tu.cu->ispMode && isLuma(compID))//如果当前CU开启ISP模式,同时当前重构的是亮度分量
{if (predRegDiffFromTB)//如果ISP划分后的子块宽小于4,则要将多个子块(2个或4个)合起来获得帧内预测参数{if (firstTBInPredReg){CU::adjustPredArea(areaPredReg);m_pcIntraPred->initIntraPatternChTypeISP(*tu.cu, areaPredReg, pReco);}}else{m_pcIntraPred->initIntraPatternChTypeISP(*tu.cu, area, pReco);//在开启ISP的情况下,初始化帧内预测参数并获取参考像素,如果需要则对参考像素进行滤波}
}
else
{m_pcIntraPred->initIntraPatternChType(*tu.cu, area);//初始化帧内预测参数并获取参考像素,如果需要则对参考像素进行滤波
}

如果ISP划分后的子块宽小于4,则要将多个子块(2个或4个)合起来获得预测值

predRegDiffFromTB:为1表示ISP划分后的子块宽小于4

firstTBInPredReg:为1表示当前TU是子块合并后区域中的第一个TU

areaPredReg:子块合并后的区域,后面会用adjustPredArea()函数将宽调整为4

if分支:如果判断条件为假表示不开启ISP的情况,调用initIntraPatternChType()函数初始化帧内预测参数并获取参考像素,如果需要则对参考像素进行滤波。为真表示开启ISP的情况,调用initIntraPatternChTypeISP()函数,与前一个函数类似,这里推荐大家跟进去看一下

//===== get prediction signal =====
if( compID != COMPONENT_Y && PU::isLMCMode( uiChFinalMode ) )//如果开启CCLM模式
{const PredictionUnit& pu = *tu.cu->firstPU;m_pcIntraPred->xGetLumaRecPixels( pu, area );m_pcIntraPred->predIntraChromaLM( compID, piPred, pu, area, uiChFinalMode );
}
else
{if( PU::isMIP( pu, chType ) )//如果开启MIP模式{m_pcIntraPred->initIntraMip( pu, area );m_pcIntraPred->predIntraMip( compID, piPred, pu );}else{//其余的预测模式if (predRegDiffFromTB){if (firstTBInPredReg)//如果ISP划分后的子块宽小于4,则要将多个子块(2个或4个)合起来获得帧内预测像素值{PelBuf piPredReg = cs.getPredBuf(areaPredReg);m_pcIntraPred->predIntraAng(compID, piPredReg, pu);}}elsem_pcIntraPred->predIntraAng(compID, piPred, pu);//包括65种角度预测模式、DC预测模式、Planar预测模式、BDPCM预测模式以及PDPC的实现}
}

第一个if分支:判断条件为真表示开启CCLM的情况

第二个if分支:判断条件为真表示开启MIP的情况,为假表示是其余的预测模式,调用predIntraAng()函数获取当前TU对应的像素预测值。此函数包括65种角度预测模式、DC预测模式、Planar预测模式、BDPCM预测模式以及PDPC的实现

//下面与LMCS的chroma scaling设置有关
const Slice           &slice = *cs.slice;
bool flag = slice.getLmcsEnabledFlag() && (slice.isIntra() || (!slice.isIntra() && m_pcReshape->getCTUFlag()));//为1表示开启LMCS
if (flag && slice.getPicHeader()->getLmcsChromaResidualScaleFlag() && (compID != COMPONENT_Y) && (tu.cbf[COMPONENT_Cb] || tu.cbf[COMPONENT_Cr]))
{//如果开启LMCS中的chroma scaling,且存在色度残差系数const Area area = tu.Y().valid() ? tu.Y() : Area(recalcPosition(tu.chromaFormat, tu.chType, CHANNEL_TYPE_LUMA, tu.blocks[tu.chType].pos()), recalcSize(tu.chromaFormat, tu.chType, CHANNEL_TYPE_LUMA, tu.blocks[tu.chType].size()));const CompArea &areaY = CompArea(COMPONENT_Y, tu.chromaFormat, area);int adj = m_pcReshape->calculateChromaAdjVpduNei(tu, areaY);//获得LMCS中chroma scaling的缩放系数tu.setChromaAdj(adj);//设置TU中的m_chromaResScaleInv
}

这段主要是用来求出LMCS中chroma scaling的缩放系数

//===== inverse transform =====
PelBuf piResi = cs.getResiBuf( area );//获取在cs中存储的当前TU的相应分量的像素残差值地址const QpParam cQP( tu, compID );if( tu.jointCbCr && isChroma(compID) )//如果色度分量开启JCCR
{if( compID == COMPONENT_Cb ){PelBuf resiCr = cs.getResiBuf( tu.blocks[ COMPONENT_Cr ] );//获取在cs中存储的当前TU的Cr分量的像素残差值地址if( tu.jointCbCr >> 1 ){m_pcTrQuant->invTransformNxN( tu, COMPONENT_Cb, piResi, cQP );}else{const QpParam qpCr( tu, COMPONENT_Cr );m_pcTrQuant->invTransformNxN( tu, COMPONENT_Cr, resiCr, qpCr );}m_pcTrQuant->invTransformICT( tu, piResi, resiCr );}
}
elseif( TU::getCbf( tu, compID ) )//如果相应分量存在非零残差系数{m_pcTrQuant->invTransformNxN( tu, compID, piResi, cQP );//进行残差系数的反量化反变换}
else
{//不存在非零残差系数就填零piResi.fill( 0 );
}

piResi:cs中存储的当前TU的相应分量的像素残差值地址

if分支:判断条件为真表示当前分量开启JCCR;else if判断条件为真表示当前分量存在非零残差系数,调用invTransformNxN()函数进行残差系数的反量化反变换;else表示当前分量不存在非零残差系数,对当前TU对应的像素残差值填0

//下面进行LMCS中的chroma scaling
flag = flag && (tu.blocks[compID].width*tu.blocks[compID].height > 4);
if (flag && (TU::getCbf(tu, compID) || tu.jointCbCr) && isChroma(compID) && slice.getPicHeader()->getLmcsChromaResidualScaleFlag())
{piResi.scaleSignal(tu.getChromaAdj(), 0, tu.cu->cs->slice->clpRng(compID));
}

这段进行LMCS中的chroma scaling

//设置cs中的m_isDecomp
if( !tu.cu->ispMode || !isLuma( compID ) )
{cs.setDecomp( area );
}
else if( tu.cu->ispMode && isLuma( compID ) && CU::isISPFirst( *tu.cu, tu.blocks[compID], compID ) )
{cs.setDecomp( tu.cu->blocks[compID] );
}

这段用来设置cs中的m_isDecomp,m_isDecomp应该是表示当前帧每个像素点是否已重构(不确定)

//===== reconstruction =====
piPred.reconstruct( piPred, piResi, tu.cu->cs->slice->clpRng( compID ) );//将piPred与piResi相加存在piPred中
pReco.copyFrom( piPred );//pReco复制piPred中的内容

这段用来重构像素值,就是预测值和残差值相加

VTM10.0代码学习7:decompressCtu()xReconIntraQT()相关推荐

  1. VTM10.0代码学习5:coding_unit()cu_pred_data()

    此系列是为了记录自己学习VTM10.0的过程和锻炼表达能力,主要是从解码端进行入手.由于本人水平有限,出现的错误恳请大家指正,欢迎与大家一起交流进步. 上一篇博客(VTM10.0代码学习4)讲述了将语 ...

  2. VTM10.0代码学习10:EncGOP_compressGOP()

    此系列是为了记录自己学习VTM10.0的过程,目前正在看编码端.主要的参考文档有JVET-S2001-vH和JVET-S2002-v1.由于本人水平有限,出现的错误恳请大家指正,欢迎与大家一起交流进步 ...

  3. VTM10.0代码学习3:DecSlice_decompressSlice()

    此系列是为了记录自己学习VTM10.0的过程和锻炼表达能力,主要是从解码端进行入手.由于本人水平有限,出现的错误恳请大家指正,欢迎与大家一起交流进步. 接着本系列的上一篇博客继续讲,上一篇博客的末尾讲 ...

  4. 【VTM10.0代码学习】帧间预测xCheckRdCostMerge2N*2N

      xCheckRdCostMerge函数在xCompressCU中被调用,这里包括了常规Merge模式.CIIP模式和MMVD模式,GPM在xCheckRDCostGeo函数中.也就是说常规Merg ...

  5. H.266代码学习:decompressCtu和xDecompressCU函数

    今天来学习一下JEM的decompressCtu和xDecompressCU函数.之前在 H.266代码学习:decodeCtu和xDecodeCU函数 学习了的学习中提到,decodeCtu和xDe ...

  6. 资源|2019 年 11 月最新《TensorFlow 2.0 深度学习算法实战》中文版教材免费开源(附随书代码+pdf)...

    点击上方"AI遇见机器学习",选择"星标"公众号 重磅干货,第一时间送 2019 年 10 月,谷歌正式宣布,开源机器学习库 TensorFlow 2.0 现在 ...

  7. 从0开始学习GitHub系列之「向GitHub 提交代码」

    DevStore首页 >文章 >文章详情 从0开始学习GitHub系列之「向GitHub 提交代码」 糖果果| 2016-06-15 10:57    浏览量(500)    评论(1) ...

  8. 400页《TensorFlow 2.0 深度学习算法实战》中文版教材免费下载(附随书代码+pdf)...

    Tensorflow自谷歌提出以来就成为最受欢迎的深度学习框架之一,到目前为止也已经被下载超过 4000 万次.其中TensorFlow2.0更是修复之前非常多的不人性的特性,备大家欢迎. 今天给大家 ...

  9. 从0开始学习 GitHub 系列之「04.向GitHub 提交代码」----转载自stormzhang 原创文章

    之前的这篇文章「从0开始学习 GitHub 系列之「Git速成」」相信大家都已经对 Git 的基本操作熟悉了,但是这篇文章只介绍了对本地 Git 仓库的基本操作,今天我就来介绍下如何跟远程仓库一起协作 ...

最新文章

  1. 用XCA(X Certificate and key management)可视化程序管理SSL 证书(3)--创建自己定义的凭证管理中心(Certificate Authority)...
  2. Elasticsearch 常用配置参数总结
  3. docker in all
  4. Ubuntu16.04直接修改root密码
  5. Spring @Scheduled定时任务调度配置的详解
  6. 子类重写方法aop切不到_Spring-aop 全面解析(从应用到原理)
  7. jsp内置对象 application
  8. AD9854PCB的绘制以及调试中存在的问题以及解决方法
  9. 记——博客后台管理系统
  10. 好玩的微信互动小游戏有哪些?微信小程序游戏倾情推荐?
  11. 并发策略-CAS算法
  12. CP速配app v2.0.8.2
  13. 【OpenCV 例程 300篇】222. 特征提取之弗里曼链码(Freeman chain code)
  14. 导入eclipse项目时select eclipse projects to import显示nothing to show--踩坑记录
  15. 【移动网络】Ch. 2 移动网络基本原理 (Part1. 无线信道与数据率)
  16. 百胜中国今年计划开设约1000家新店;麦当劳中国推出“会员创异菜单” | 美通企业日报...
  17. MATLAB传递函数分析总结,Matlab实验分析报告.doc
  18. HCIE证书真的有用吗?
  19. vue中引入js,然后new js里的方法
  20. Top 10 JavaScript编辑器,你在用哪个? 1

热门文章

  1. TIOBE 3月编程排行榜出炉!Python杀疯了!
  2. android rom 寿命,手机寿命有多长?苹果用3年而安卓只有2年
  3. 【听】人性的弱点,人际关系交往的鸡汤宝典
  4. AVProVideo在Android端不显示画面问题
  5. Fegin调用时出现JSON parse error:can not deserialize instance of ...
  6. java初级工作总结_【Java初级程序员工作总结_Java初级程序员个人年终总结】-看准网...
  7. PS中括号不能调整画笔或仿章大小
  8. chrome://tracing 性能分析神器
  9. mac的python怎么输入中文_帮你解决mac上python没法输入中文问题
  10. UserWarning: FixedFormatter should only be used together with FixedLocator|Python点点