H.266/VVC变换代码学习:transformNxN函数

H.266/VVC中有两个transformNxN函数,它们的参数不同,如下:

void transformNxN     ( TransformUnit& tu, const ComponentID& compID, const QpParam& cQP, std::vector<TrMode>* trModes, const int maxCand );

void transformNxN     ( TransformUnit& tu, const ComponentID& compID, const QpParam& cQP, TCoeff& uiAbsSum, const Ctx& ctx, const bool loadTr = false );

1.transformNxN函数(1)

第一个transformNxN()函数主要是通过比较DCT-2变换和TransformSkip的SAD来决定修改trModes对应模式是否使用。

注意:在LFNST关闭的情况下,对于亮度块,这个函数会比较DCT-2 TS MTS四种变换核的SAD来修改trModes对应模式。

VTM7.0中加入了色度模式下的TransformSkip模式,也随之多了色度下DCT-2和TransformSkip的比较。

每次进行变换时,都会将变换后的系数保存到m_mtsCoeffs变量里,用于之后第二个transfromNxN函数进行加载。

总共五种模式:

enum MTSIdx
{
  MTS_DCT2_DCT2 = 0,
  MTS_SKIP = 1,
  MTS_DST7_DST7 = 2,
  MTS_DCT8_DST7 = 3,
  MTS_DST7_DCT8 = 4,
  MTS_DCT8_DCT8 = 5
};

代码如下: 

/*
通过比较DCT-2的SAD和TransformSkip的SAD决定是否使用TransFormSkip,只有尺寸小于等于32才调用
trModes数组存有多核变换的索引MTSIdx和能否进行参与后续比较(int,bool),经过本函数后修改MTSIdx对应的bool值
*/
void TrQuant::transformNxN( TransformUnit& tu, const ComponentID& compID, const QpParam& cQP, std::vector<TrMode>* trModes, const int maxCand )
{
        CodingStructure &cs = *tu.cs;
  const CompArea &rect      = tu.blocks[compID];
  const uint32_t width      = rect.width;
  const uint32_t height     = rect.height;

  const CPelBuf  resiBuf    = cs.getResiBuf(rect);

  CHECK( cs.sps->getMaxTbSize() < width, "Unsupported transformation size" );

  int pos = 0;//表示trCost的第二个值,应该表示MTSIdx
  std::vector<TrCost> trCosts;//trCosts:第一个值为SAD的值,第二个值为bool值
  std::vector<TrMode>::iterator it = trModes->begin();//it有两个值,一个代表多核变换的模式号,一个是bool值表示是否参与后续比较
  const double facBB[] = { 1.2, 1.3, 1.3, 1.4, 1.5 };//一个系数值,含义?
  while( it != trModes->end() )
  { //遍历trModes,获得每一个多核变换模式对应的cost
#if JVET_P0058_CHROMA_TS
    tu.mtsIdx[compID] = it->first;
#else
    tu.mtsIdx = it->first;
#endif
#if JVET_P0058_CHROMA_TS
    CoeffBuf tempCoeff( m_mtsCoeffs[tu.mtsIdx[compID]], rect);//获取MTS对应系数
#else
    CoeffBuf tempCoeff( m_mtsCoeffs[tu.mtsIdx], rect );
#endif
    if( tu.noResidual )//如果没有残差,跳过
    {
      int sumAbs = 0;
      trCosts.push_back( TrCost( sumAbs, pos++ ) );
      it++;
      continue;
    }

#if JVET_P0058_CHROMA_TS
    if ( tu.mtsIdx[compID] == MTS_SKIP )//TransformSkip模式:变换跳过模式
#else
#if JVET_P0059_CHROMA_BDPCM
    if ((isLuma(compID) && tu.mtsIdx == MTS_SKIP) || (isChroma(compID) && tu.cu->bdpcmModeChroma))
#else
    if( isLuma(compID) && tu.mtsIdx == MTS_SKIP )
#endif
#endif
    {
      xTransformSkip( tu, compID, resiBuf, tempCoeff.buf );
    }
    else//Transform模式:正常变换
    {
      xT( tu, compID, resiBuf, tempCoeff, width, height );
    }

    int sumAbs = 0;//绝对误差和:SAD
    for( int pos = 0; pos < width*height; pos++ )
    {
      //这里的pos是局部变量,表示的是每一个块里面的每一个像素值,和外部pos意义不同
      sumAbs += abs( tempCoeff.buf[pos] );
    }

    double scaleSAD=1.0;
#if JVET_P0058_CHROMA_TS
    if ( tu.mtsIdx[compID] == MTS_SKIP && ((floorLog2(width) + floorLog2(height)) & 1) == 1)
#else
    if (isLuma(compID) && tu.mtsIdx==MTS_SKIP && ((floorLog2(width) + floorLog2(height)) & 1) == 1 )
#endif
    {
      scaleSAD=1.0/1.414213562; // //补偿系数 compensate for not scaling transform skip coefficients by 1/sqrt(2)
    }
#if JVET_P1000_REMOVE_TRANFORMSHIFT_IN_TS_MODE
#if JVET_P0058_CHROMA_TS
    if (tu.mtsIdx[compID] == MTS_SKIP)
#else
    if (isLuma(compID) && tu.mtsIdx == MTS_SKIP)
#endif
    {
        int trShift = getTransformShift(tu.cu->slice->getSPS()->getBitDepth(toChannelType(compID)), rect.size(), tu.cu->slice->getSPS()->getMaxLog2TrDynamicRange(toChannelType(compID)));
        scaleSAD *= pow(2, trShift);
    }
#endif

    trCosts.push_back( TrCost( int(sumAbs*scaleSAD), pos++ ) );
    it++;
  }

  int numTests = 0;
  std::vector<TrCost>::iterator itC = trCosts.begin();
#if JVET_P0058_CHROMA_TS
  const double fac   = facBB[std::max(0, floorLog2(std::max(width, height)) - 2)];
#else
  const double fac   = facBB[floorLog2(std::max(width, height))-2];
#endif
  const double thr   = fac * trCosts.begin()->first;//第一个SAD,带系数的(系数thr用于简化,是一种快速算法),用于其他MTS模式比较时
  const double thrTS = trCosts.begin()->first;//第一个SAD,用于TransformSkip时比较
  while( itC != trCosts.end() )//遍历变换代价值
  {
    const bool testTr = itC->first <= ( itC->second == 1 ? thrTS : thr ) && numTests <= maxCand;
    trModes->at( itC->second ).second = testTr;//修改MTS模式对应的bool值
    numTests += testTr;
    itC++;
  }
}

2.transformNxN函数(2)

第二个transformNxN()函数主要是进行对残差块进行变换。

流程如下:

  1. RDPCM ???
  2. 若RDPCM模式不是关闭状态且当前块是亮度块,则设置mtsIdx = MTS_Skip,否则执行3
  3. 若RDPCM模式为关闭状态,检查是否为Trans-quant-Bypass模式,若是则跳过变换和量化,否则执行4
  4. 若当前块是亮度块且mtsIdx=MTS_Skip,则执行xTransformSkip()函数,否则执行xT()函数进行主变换
  5. 判断是否使用二次变换,若使用二次变换,则调用xFwdLfnst()函数
  6. 调用xQuant函数进行量化。

注意,这里函数中的参数LoadTr变量,主要是用于防止进行重复的一次变换。

loadTr是在xRecurIntraCodingLumaQT函数和xRecurIntraChromaCodingQT函数传递给xIntraCodingTUBlock函数的参数。若当前待检查的变换模式数目大于1的时候,loadTr参数为True,此时xIntraCodingTUBlock函数里会调用第一个TransformNxN函数进行变换并将一次变换系数保存在m_mtsCoeffs变量里,当调用第二个transformNxN函数时,若loadTr为true,说明已经进行过一次变换,则可直接加载出来。

#if JVET_O0502_ISP_CLEANUP
void TrQuant::transformNxN( TransformUnit& tu, const ComponentID& compID, const QpParam& cQP, TCoeff& uiAbsSum, const Ctx& ctx, const bool loadTr )
#else
void TrQuant::transformNxN( TransformUnit &tu, const ComponentID &compID, const QpParam &cQP, TCoeff &uiAbsSum, const Ctx &ctx, const bool loadTr, double* diagRatio, double* horVerRatio )
#endif
{
        CodingStructure &cs = *tu.cs;
  const SPS &sps            = *cs.sps;
  const CompArea &rect      = tu.blocks[compID];
  const uint32_t uiWidth        = rect.width;
  const uint32_t uiHeight       = rect.height;

  const CPelBuf resiBuf     = cs.getResiBuf(rect);
        CoeffBuf rpcCoeff   = tu.getCoeffs(compID);

  if( tu.noResidual )//如果没有残差,直接设置cbf
  {
    uiAbsSum = 0;//存放残差系数绝对值的和;
    TU::setCbfAtDepth( tu, compID, tu.depth, uiAbsSum > 0 );
    return;
  }

  RDPCMMode rdpcmMode = RDPCM_OFF;//设置RDPCM模式
  rdpcmNxN(tu, compID, cQP, uiAbsSum, rdpcmMode);

  if( tu.cu->bdpcmMode && isLuma(compID) )
  {
    tu.mtsIdx = MTS_SKIP;
  }

  if (rdpcmMode == RDPCM_OFF)
  {
    uiAbsSum = 0;

    // transform and quantize
    if (CU::isLosslessCoded(*tu.cu))//旁路掉变换和量化,即直接把残差系数赋值给rpcCoeff;
    {
      const bool rotateResidual = TU::isNonTransformedResidualRotated( tu, compID );//是否残差旋转

      for( uint32_t y = 0; y < uiHeight; y++ )
      {
        for( uint32_t x = 0; x < uiWidth; x++ )
        {
          const Pel currentSample = resiBuf.at( x, y );

          if( rotateResidual )//存在残差旋转
          {
            rpcCoeff.at( uiWidth - x - 1, uiHeight - y - 1 ) = currentSample;
          }
          else
          {
            rpcCoeff.at( x, y ) = currentSample;
          }

          uiAbsSum += TCoeff( abs( currentSample ) );//计算出SAD的和
        }
      }
    }
    else//没有被旁路,进行变换、量化等操作
    {
#if MAX_TB_SIZE_SIGNALLING
      CHECK( cs.sps->getMaxTbSize() < uiWidth, "Unsupported transformation size" );

#else
      CHECK( MAX_TB_SIZEY < uiWidth, "Unsupported transformation size" );
#endif

      CoeffBuf tempCoeff( loadTr ? m_mtsCoeffs[tu.mtsIdx] : m_plTempCoeff, rect );

      DTRACE_PEL_BUF( D_RESIDUALS, resiBuf, tu, tu.cu->predMode, compID );
	  //loadTr变量用来加载之前保存的变换系数
	  //m_mtsCoeffs变换系数是在另一个TransformNxN函数中保存的系数,仅进行过一次变换
      if( !loadTr )
      {
        if( isLuma(compID) && tu.mtsIdx == MTS_SKIP )
        {
          xTransformSkip( tu, compID, resiBuf, tempCoeff.buf );//变换跳过模式,如果跳过变换,将残差系数经过伸缩和移位后赋值给temCoeff.buff;
        }
        else
        {
          xT( tu, compID, resiBuf, tempCoeff, uiWidth, uiHeight );//正常变换模式,tempCoeff保存经过变换后的残差系数
        }
      }

#if !JVET_O0502_ISP_CLEANUP
      //we do this only with the DCT-II coefficients
      if( isLuma(compID) &&
        !loadTr && tu.mtsIdx == MTS_DCT2_DCT2
        )
      {
        //it gets the distribution of the coefficients energy, which will be useful to discard ISP tests
        //它得到了系数能量的分布,这将有助于抛弃ISP测试
        xGetCoeffEnergy( tu, compID, tempCoeff, diagRatio, horVerRatio );
      }
#endif

      if( sps.getUseLFNST() )//进行二次变换LFNST
      {
        xFwdLfnst( tu, compID, loadTr );
      }

      DTRACE_COEFF_BUF( D_TCOEFF, tempCoeff, tu, tu.cu->predMode, compID );

      xQuant( tu, compID, tempCoeff, uiAbsSum, cQP, ctx );//量化

      DTRACE_COEFF_BUF( D_TCOEFF, tu.getCoeffs( compID ), tu, tu.cu->predMode, compID );
    }
  }

  // set coded block flag (CBF)
  TU::setCbfAtDepth (tu, compID, tu.depth, uiAbsSum > 0);
}

其中xT函数可以参考https://blog.csdn.net/BigDream123/article/details/102748739

xFwdLfnst函数可以参考https://blog.csdn.net/BigDream123/article/details/102748968

xTransformSkip函数可以参考https://blog.csdn.net/BigDream123/article/details/102748968

 

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值