H.266/VVC-VTM代码学习-帧内预测16-解码端xIntraRecBlk完成TU指定分量帧内重建(附ISP变换和预测尺寸不同、JCCR、LMCS简述)

H.266/VVC专栏传送

上一篇:H.266/VVC-VTM代码学习-帧内预测15-解码端解压缩decompressCtu函数及xReconIntraQT调用xIntraRecQT函数完成帧内预测重建
下一篇:持续创作中…

目录

前言

VTM是H.266/VVC视频编码标准的参考软件,研究VTM代码给研究人员解释了VVC编码标准的详细标准规范与细节。

本文是笔者对VTM代码的一点学习记录,成文于笔者刚开始接触VVC期间,期间很多概念和理论框架还很不成熟,若文中存在错误欢迎批评指正,也欢迎广大视频编码学习者沟通交流、共同进步。

VTM代码的下载及编译请参考博文:
【视频编码学习】H.266/VVC参考软件VTM配置运行(VTM-6.0版本)

本文涉及的代码主要存在于工程下的Lib\DecoderLib\DecCu.cpp文件中。

一、xIntraRecBlk函数完成指定TU的指定分量的帧内重建

该函数入口在上一篇博客的xIntraRecQT函数中

void DecCu::xIntraRecBlk( TransformUnit& tu, const ComponentID compID )
{
  //如果当前TU的指定分量无效(不满足色度采样模式有效,且当前分量ID有效,且当前块的尺寸不为0),则返回
  if( !tu.blocks[ compID ].valid() )
  {
    return;
  }

  //当前编码结构
        CodingStructure &cs = *tu.cs;
  //当前区域
  const CompArea &area      = tu.blocks[compID];

  //当前通道类型LUMA or CHROMA
  const ChannelType chType  = toChannelType( compID );

  //对应位置预测缓存
        PelBuf piPred       = cs.getPredBuf( area );

  //对应PU
  const PredictionUnit &pu  = *tu.cs->getPU( area.pos(), chType );
  //帧内预测模式
  const uint32_t uiChFinalMode  = PU::getFinalIntraMode( pu, chType );
  //解码缓存
  PelBuf pReco              = cs.getRecoBuf(area);

  //===== init availability pattern =====
  //预测尺寸和变换尺寸是否不同。当前为Y分量,且ISP模式为垂直划分,且((w == 8 && h > 4) 或 w == 4)时,预测尺寸和变换尺寸不同
  bool predRegDiffFromTB = CU::isPredRegDiffFromTB(*tu.cu, compID);
  //是否当前使用ISP模式,且TB为第一块TB
  bool firstTBInPredReg = CU::isFirstTBInPredReg(*tu.cu, compID, area);
  //预测区域
  CompArea areaPredReg(COMPONENT_Y, tu.chromaFormat, area);
  //若使用ISP且当前为LUMA块
  if (tu.cu->ispMode && isLuma(compID))
  {
    //若预测尺寸与变换尺寸不同
    if (predRegDiffFromTB)
    {
      //若当前TB第一次进行预测
      if (firstTBInPredReg)
      {
        //调整预测区域,将CU尺寸为4xN and 8xN (N > 4)的块的ISP预测区域改为4xN
        CU::adjustPredArea(areaPredReg);
        //初始化ISP下的帧内预测(设置帧内预测参数,并获取参考像素、进行参考像素滤波)
        m_pcIntraPred->initIntraPatternChTypeISP(*tu.cu, areaPredReg, pReco);
      }
    }
    //若预测尺寸与变换尺寸相同
    else
    {
      //初始化ISP下的帧内预测(设置帧内预测参数,并获取参考像素、进行参考像素滤波)
      m_pcIntraPred->initIntraPatternChTypeISP(*tu.cu, area, pReco);
    }
  }
  //不使用ISP或当前为CHROMA块
  else
  {
    //初始化帧内预测(设置帧内预测参数,并获取参考像素、进行参考像素滤波)
    m_pcIntraPred->initIntraPatternChType(*tu.cu, area);
  }

  //===== get prediction signal =====
  //=====    得到预测信号    =====
  //当前为色度分量,且为CCLM模式(67-69)
  if( compID != COMPONENT_Y && PU::isLMCMode( uiChFinalMode ) )
  {
    //当前CU的第一个PU
    const PredictionUnit& pu = *tu.cu->firstPU;
    //对重构的亮度块根据YUV格式进行相应的下采样
    m_pcIntraPred->xGetLumaRecPixels( pu, area );
    //通过线性映射得到色度分量预测值
    m_pcIntraPred->predIntraChromaLM( compID, piPred, pu, area, uiChFinalMode );
  }
  //不是色度分量,或不为CCLM模式
  else
  {
    //如果是MIP模式
    if( PU::isMIP( pu, chType ) )
    {
      //获取参考像素并进行相应下采样
      m_pcIntraPred->initIntraMip( pu, area );
      //将输入向量与对应矩阵相乘,需要上采样时进行上采样,得到MIP预测结果
      m_pcIntraPred->predIntraMip( compID, piPred, pu );
    }
    //如果不是MIP模式
    else
    {
      //若预测尺寸与变换尺寸不同
      if (predRegDiffFromTB)
      {
        //使用ISP模式且当前TB为第一块
        if (firstTBInPredReg)
        {
          //预测区域缓存
          PelBuf piPredReg = cs.getPredBuf(areaPredReg);
          //根据不同模式进行预测,需要时使用PDPC
          m_pcIntraPred->predIntraAng(compID, piPredReg, pu);
        }
      }
      //预测尺寸与变换尺寸相同时
      else
        //根据不同模式进行预测,需要时使用PDPC
        m_pcIntraPred->predIntraAng(compID, piPred, pu);
    }
  }
  const Slice           &slice = *cs.slice;
  //LMCS可用,且当前为帧内模式或不为帧内模式但CTUFlag有效
  bool flag = slice.getLmcsEnabledFlag() && (slice.isIntra() || (!slice.isIntra() && m_pcReshape->getCTUFlag()));
  //flag有效,且LmcsChromaResidualScaleFlag有效,且当前不为亮度分量,且使用JCCR时
  if (flag && slice.getPicHeader()->getLmcsChromaResidualScaleFlag() && (compID != COMPONENT_Y) && (tu.cbf[COMPONENT_Cb] || tu.cbf[COMPONENT_Cr]))
  {
    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);
    //求得LMCS中基于亮度的色度残差缩放的scale
    int adj = m_pcReshape->calculateChromaAdjVpduNei(tu, areaY);
    //保存scale
    tu.setChromaAdj(adj);
  }
  //===== inverse transform =====
  //=====     反变换     =====
  //获取残差缓存
  PelBuf piResi = cs.getResiBuf( area );

  const QpParam cQP( tu, compID );

  //使用JCCR且当前为色度分量
  if( tu.jointCbCr && isChroma(compID) )
  {
    //若当前为Cb分量
    if( compID == COMPONENT_Cb )
    {
      //获取Cr残差缓存
      PelBuf resiCr = cs.getResiBuf( tu.blocks[ COMPONENT_Cr ] );
      //jointCbCr > 1
      if( tu.jointCbCr >> 1 )
      {
        //反变换得到联合色度残差放入Cb
        m_pcTrQuant->invTransformNxN( tu, COMPONENT_Cb, piResi, cQP );
      }
      //jointCbCr <= 1
      else
      {
        const QpParam qpCr( tu, COMPONENT_Cr );
        //反变换得到联合色度残差放入Cr
        m_pcTrQuant->invTransformNxN( tu, COMPONENT_Cr, resiCr, qpCr );
      }
      //根据联合色度残差得到Cb和Cr的值
      m_pcTrQuant->invTransformICT( tu, piResi, resiCr );
    }
  }
  //不使用JCCR,或当前为亮度分量
  else
  //code block flag不为0
  if( TU::getCbf( tu, compID ) )
  {
    //直接得到当前分量反变换值
    m_pcTrQuant->invTransformNxN( tu, compID, piResi, cQP );
  }
  //code block flag为0
  else
  {
    //反变换值直接填0
    piResi.fill( 0 );
  }

  //===== reconstruction =====
  //=====     重建     =====
  //flag有效(LMCS可用,且当前为帧内模式或不为帧内模式但CTUFlag有效),且TU的宽×高 > 4
  flag = flag && (tu.blocks[compID].width*tu.blocks[compID].height > 4);
  //若flag有效,且code block flag不为0 或 使用JCCR,且当前为色度块,且LmcsChromaResidualScaleFlag有效
  if (flag && (TU::getCbf(tu, compID) || tu.jointCbCr) && isChroma(compID) && slice.getPicHeader()->getLmcsChromaResidualScaleFlag())
  {
    //通过scale将色度残差缩放
    piResi.scaleSignal(tu.getChromaAdj(), 0, tu.cu->cs->slice->clpRng(compID));
  }

  //若当前块不使用ISP,或当前为色度块
  if( !tu.cu->ispMode || !isLuma( compID ) )
  {
    //设置当前区域已解码完成
    cs.setDecomp( area );
  }
  //当前块使用ISP,且是亮度分量,且当前块是ISP的第一块
  else if( tu.cu->ispMode && isLuma( compID ) && CU::isISPFirst( *tu.cu, tu.blocks[compID], compID ) )
  {
    //设置当前区域已解码完成
    cs.setDecomp( tu.cu->blocks[compID] );
  }

#if REUSE_CU_RESULTS
  CompArea    tmpArea(COMPONENT_Y, area.chromaFormat, Position(0, 0), area.size());
  PelBuf tmpPred;
#endif
  //若LMCS使能标志有效,且CTU标志有效 或 为帧内模式,且当前为LUMA分量
  if (slice.getLmcsEnabledFlag() && (m_pcReshape->getCTUFlag() || slice.isIntra()) && compID == COMPONENT_Y)
  {
//若再次使用CU结果
#if REUSE_CU_RESULTS
    {
      tmpPred = m_tmpStorageLCU->getBuf(tmpArea);
      //将预测值copy一份至tmpPred
      tmpPred.copyFrom(piPred);
    }
#endif
  }
//保持预测和残差信号
#if KEEP_PRED_AND_RESI_SIGNALS
  //利用预测和残差信号重建,将重建结果放至pReco
  pReco.reconstruct( piPred, piResi, tu.cu->cs->slice->clpRng( compID ) );
//不保持预测和残差信号
#else
  //利用预测和残差信号重建,将重建结果覆盖piPred
  piPred.reconstruct( piPred, piResi, tu.cu->cs->slice->clpRng( compID ) );
#endif
//若不保持预测和残差信号
#if !KEEP_PRED_AND_RESI_SIGNALS
  //将重建结果从piPred复制到pReco
  pReco.copyFrom( piPred );
#endif
  //若LMCS使能标志有效,且CTU标志有效 或 为帧内模式,且当前为LUMA分量
  if (slice.getLmcsEnabledFlag() && (m_pcReshape->getCTUFlag() || slice.isIntra()) && compID == COMPONENT_Y)
  {
//若再次使用CU结果
#if REUSE_CU_RESULTS
    {
      //piPred保存原始预测信息
      piPred.copyFrom(tmpPred);
    }
#endif
  }
//若再次使用CU结果
#if REUSE_CU_RESULTS
  //将当前重建值和预测值记录
  if( cs.pcv->isEncoder )
  {
    cs.picture->getRecoBuf( area ).copyFrom( pReco );
    cs.picture->getPredBuf(area).copyFrom(piPred);
  }
#endif
}

二、一些解释

(1)ISP中变换尺寸和预测尺寸不同的情况

这种情况的单独处理是在O0106提案中提出的:

In previous meetings and in the JVET reflector, some experts have raised concerns regarding the handling of 1xN and 2xN subblocks of ISP. It has been mentioned that dependence of 1xN/2xN subblocks on the reconstructed values of preceding 1xN/2xN subblocks of the coding block may result in implementation issues. There was expressed desire to remove this reconstruction dependence.

在以前的会议中,一些专家对ISP的1xN和2xN子块的处理提出了担忧。 已经提到,1×N / 2×N个子块对编码块的先前1×N / 2×N个子块的重构值的依赖性可能导致实现问题。 有人表示希望消除这种重建依赖。

The proposed test removes the dependence of 1xN/2xN subblock prediction on the reconstructed values of previously decoded 1xN/2xN subblocks of the coding block; the minimum width of prediction for subblocks is proposed to be set as four samples. There is no change in the transform block sizes. Figure 1 shows how an 8xN (N > 4) coding block that is coded using ISP with vertical split is split into two prediction regions each of size 4xN and four transforms of size 2xN. Figure 2 shows how a 4xN coding block that is coded using ISP with vertical split is predicted using the full 4xN block; four transform each of 1xN is used.

提案提出的测试消除了1xN / 2xN子块预测对编码块先前解码的1xN / 2xN子块的重构值的依赖性; 建议将子块的最小预测宽度设置为四个采样值。变换块大小没有变化。图1显示了如何将使用垂直分割的ISP编码的8xN(N> 4)编码块拆分为两个大小分别为4xN的预测区域和四个大小为2xN的变换区域。 图2显示了如何将具有垂直分割ISP编码的4xN编码块完整地用于预测;而使用4个1xN变换区域。

H.266/VVC-VTM代码学习-帧内预测16-解码端xIntraRecBlk完成TU指定分量帧内重建(附ISP变换和预测尺寸不同、JCCR、LMCS简述)
H.266/VVC-VTM代码学习-帧内预测16-解码端xIntraRecBlk完成TU指定分量帧内重建(附ISP变换和预测尺寸不同、JCCR、LMCS简述)

下表总结了用于预测和变换大小的提案建议分区大小;对于其他块大小,预测和变换大小与当前为ISP定义的大小相同。 VTM-5.0的更改以黄色突出显示。
H.266/VVC-VTM代码学习-帧内预测16-解码端xIntraRecBlk完成TU指定分量帧内重建(附ISP变换和预测尺寸不同、JCCR、LMCS简述)

(2)JCCR(Joint coding of chroma residuals 色度联合残差编码)

JCCR主要由TU层的两个标志:tu.cbf[COMPONENT_Cb] 和 tu.cbf[COMPONENT_Cr] 控制,是利用Cb和Cr的残差往往在实际过程中呈现一定的关系。

如下表所示,JCCR模式有3个子模式,由tu.cbf[COMPONENT_Cb] 和 tu.cbf[COMPONENT_Cr] 控制。

tu.cbf[COMPONENT_Cb] tu.cbf[COMPONENT_Cr] reconstruction of Cb and Cr residuals mode
1 0 resCB[x][y]=resJointC[x][y] resCr[x][y]=(CSign*resJointC[x][y])>>1 1
1 1 resCB[x][y]=resJointC[x][y] resCr[x][y]=CSign*resJointC[x][y] 2
0 1 resCB[x][y]=(CSign*resJointC[x][y])>>1 resCr[x][y]=resJointC[x][y] 3

(3)LMCS(Luma mapping with chroma scaling / in-loop reshaper)

LMCS位于去方块滤波前,该技术用于SDR、HDR视频中。LMCS主要分为两部分:基于自适应分段线性模型的环内亮度映射、基于亮度的色度残差缩放。

xIntraRecBlk函数代码涉及到的LMCS内容是基于亮度的色度残差缩放部分,利用函数calculateChromaAdjVpduNei获取色度残差的缩放scale,利用函数scaleSignal完成对色度残差的缩放。

上一篇:H.266/VVC-VTM代码学习-帧内预测15-解码端解压缩decompressCtu函数及xReconIntraQT调用xIntraRecQT函数完成帧内预测重建
下一篇:持续创作中…

上一篇:计网知识梳理——概述


下一篇:CCNA上机实验_20-PPPoE