当前位置: 首页 > article >正文

H266/VVC 量化编码中 TCQ(或 DQ)技术

网格编码量化 TCQ(或依赖量化 DQ)

  1. 网格编码量化(Trellis-Coded Quantization,TCQ) 最先在 1990 年论文 [Trellis coded quantization of memoryless and Gauss-Markov sources] 中提出,它可以看成非常高维的矢量量化。仅使用标量量化很难避免率失真损失,而这些失真却可以通过矢量量化来降低。无约束的矢量量化复杂度非常高,TCQ 作为其中的一个变种可以直接用于现在的视频编码框架且对熵编码方法影响非常小,TCQ 是依赖量化(Dependent Quantization,DQ)。在高维数据空间用 TCQ 代替传统的标量量化可以获取更高的堆积密度。TCQ 很好的结合了标量量化和矢量量化的优点,复杂度低并且具有优良的性能。 【~虽然有文献介绍叫 TCQ 技术,但实际在一些提案中或代码中 VTM 或 VVenC 中叫 DQ 技术~】

  2. TCQ 的结构:从解码器的视角看,TCQ 需要两个标量量化器和一个有限状态机在不同量化器间进行切换。标量量化器允许的重建值即反量化后的值都是量化步长( △ ) 的整数倍,一个量化器是 △ 的偶数倍,另一个是 △ 的奇数倍,注意两个量化器都包含重建值为 0 的情况可以提高低码率下的压缩效率。如图 43 是 TCQ 的两个标量量化器 Q0 和 Q1,横轴下方的值都是重建值 t,是△ 的整数倍。实心和空心圆点下面的数字是量化后的值 q。
    在这里插入图片描述

  3. 两个量化器间的状态转换可以用图 44 中有四个状态的有限状态机描述。变换系数的重建(反量化)顺序和其编码顺序一样。初始状态设 s0 (对应第一个变换系数 t0 )设为 0。每个状态 sk+1 都由其前一个状态 sk 和前一个反量化前的值 qk 的奇偶性 pk 决定(即 pk 表示 qk 是奇数还是偶数),如图 44 右侧的表所示。对于变换系数 tk ,当其状态 sk 是 0 或 1 时使用量化器 Q0,当其状态 sk 是 2 或 3 时使用量化器 Q1。状态转换过程隐式的将每个量化其的量化结果划分为两个子集,一个包含奇数一个包含偶数。如图 43 中 Q0 的结果分为 A 和 B,Q1 的 结果分为 C 和 D。当前的状态和所在的子集将决定下一个状态。
    在这里插入图片描述

  4. TCQ 的反量化过程:不同于标量量化可以对每个变换系数独立处理,TCQ 需要按编码顺序反量化每个系数(因为当前状态取决于上一个状态)。加上一个块熵解码后有 N 个系数 qk ,则反量化后变换系数 tk’ 的获取过程如下:
    在这里插入图片描述

    • 其中 sgn(.) 表示获取符号,△ 是量化步长,二维的状态转换表 stateTransTable 是图 44 右侧的表,(sk >>1) 表示使用哪个量化器,(qk &1) 可以获得 qk 的奇偶性 pk
  5. 在 2020 年 1 月的 JVET-Q2002[Algorithm description for Versatile Video Coding and Test Model 8 (VTM 8)] 提案中对 TCQ(或 DQ) 技术进行了总结描述。
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述

  6. 在 VVenC 编码器中 DepQuant.cpp 文件中定义了 DepQuant 类来处理 TCQ(或叫 DQ 技术)。

/*================================================================================*/
  /*=====                                                                      =====*/
  /*=====   T C Q                                                              =====*/
  /*=====                                                                      =====*/
  /*================================================================================*/
  class DepQuant : private RateEstimator, public DepQuantImpl
  {
  public:
    DepQuant( bool enc );

    void quant( TransformUnit& tu, const CCoeffBuf& srcCoeff, const ComponentID compID, const QpParam& cQP, const double lambda, const Ctx& ctx, TCoeff& absSum, bool enableScalingLists, int* quantCoeff );

  private:
    void    xDecideAndUpdate  ( const TCoeff absCoeff, const ScanInfo& scanInfo, bool zeroOut, int quantCoeff);
    void    xDecide           ( const ScanInfo& scanInfo, const TCoeff absCoeff, const int lastOffset, Decision* decisions, bool zeroOut, int quantCoeff );

  private:
    CommonCtx   m_commonCtx;
    State       m_allStates[ 12 ];
    State*      m_currStates;
    State*      m_prevStates;
    State*      m_skipStates;
    State       m_startState;
    Decision    m_trellis[ MAX_TB_SIZEY * MAX_TB_SIZEY ][ 8 ];
    Rom         m_scansRom;
  };
  1. 其中 quant 函数是实现量化的核心函数,其中有关于 TCQ(或 DQ )技术的逻辑实现。
void DepQuant::quant( TransformUnit& tu, const CCoeffBuf& srcCoeff, const ComponentID compID, const QpParam& cQP, const double lambda, const Ctx& ctx, TCoeff& absSum, bool enableScalingLists, int* quantCoeff )
  {
    //===== reset / pre-init =====
    const TUParameters& tuPars  = *m_scansRom.getTUPars( tu.blocks[compID], compID );
    m_quant.initQuantBlock    ( tu, compID, cQP, lambda );
    TCoeffSig*    qCoeff      = tu.getCoeffs( compID ).buf;
    const TCoeff* tCoeff      = srcCoeff.buf;
    const int     numCoeff    = tu.blocks[compID].area();
    ::memset( qCoeff, 0x00, numCoeff * sizeof( TCoeffSig ) );
    absSum                    = 0;

    const CompArea& area      = tu.blocks[ compID ];
    const uint32_t  width     = area.width;
    const uint32_t  height    = area.height;
    const uint32_t  lfnstIdx  = tu.cu->lfnstIdx;
    //===== scaling matrix ====
    //const int         qpDQ = cQP.Qp + 1;
    //const int         qpPer = qpDQ / 6;
    //const int         qpRem = qpDQ - 6 * qpPer;

    //TCoeff thresTmp = thres;
    bool zeroOut = false;
    bool zeroOutforThres = false;
    int effWidth = tuPars.m_width, effHeight = tuPars.m_height;
    if( ( tu.mtsIdx[compID] > MTS_SKIP || ( tu.cs->sps->MTS && tu.cu->sbtInfo != 0 && tuPars.m_height <= 32 && tuPars.m_width <= 32 ) ) && compID == COMP_Y )
    {
      effHeight = ( tuPars.m_height == 32 ) ? 16 : tuPars.m_height;
      effWidth  = ( tuPars.m_width  == 32 ) ? 16 : tuPars.m_width;
      zeroOut   = ( effHeight < tuPars.m_height || effWidth < tuPars.m_width );
    }
    zeroOutforThres = zeroOut || ( 32 < tuPars.m_height || 32 < tuPars.m_width );
    //===== find first test position =====
    int firstTestPos = std::min<int>( tuPars.m_width, JVET_C0024_ZERO_OUT_TH ) * std::min<int>( tuPars.m_height, JVET_C0024_ZERO_OUT_TH ) - 1;
    if( lfnstIdx > 0 && tu.mtsIdx[compID] != MTS_SKIP && width >= 4 && height >= 4 )
    {
      firstTestPos = ( ( width == 4 && height == 4 ) || ( width == 8 && height == 8 ) )  ? 7 : 15 ;
    }

    const TCoeff defaultQuantisationCoefficient = (TCoeff)m_quant.getQScale();
    const TCoeff thres = m_quant.getLastThreshold();
    const int zeroOutWidth  = ( tuPars.m_width  == 32 && zeroOut ) ? 16 : 32;
    const int zeroOutHeight = ( tuPars.m_height == 32 && zeroOut ) ? 16 : 32;

    if( enableScalingLists )
    {
      for( ; firstTestPos >= 0; firstTestPos-- )
      {
        if( zeroOutforThres && ( tuPars.m_scanId2BlkPos[firstTestPos].x >= zeroOutWidth || tuPars.m_scanId2BlkPos[firstTestPos].y >= zeroOutHeight ) ) continue;

        const TCoeff thresTmp = TCoeff( thres / ( 4 * quantCoeff[tuPars.m_scanId2BlkPos[firstTestPos].idx] ) );

        if( abs( tCoeff[tuPars.m_scanId2BlkPos[firstTestPos].idx] ) > thresTmp ) break;
      }
    }
    else
    {
      const TCoeff defaultTh = TCoeff( thres / ( defaultQuantisationCoefficient << 2 ) );

      for( ; firstTestPos >= 0; firstTestPos-- )
      {
        if( zeroOutforThres && ( tuPars.m_scanId2BlkPos[firstTestPos].x >= zeroOutWidth || tuPars.m_scanId2BlkPos[firstTestPos].y >= zeroOutHeight ) ) continue;
        if( abs( tCoeff[tuPars.m_scanId2BlkPos[firstTestPos].idx] ) > defaultTh ) break;
      }
    }

    if( firstTestPos < 0 )
    {
      tu.lastPos[compID] = -1;
      return;
    }

    //===== real init =====
    RateEstimator::initCtx( tuPars, tu, compID, ctx.getFracBitsAcess() );
    m_commonCtx.reset( tuPars, *this );
    for( int k = 0; k < 12; k++ )
    {
      m_allStates[k].init();
    }
    m_startState.init();
    
    int effectWidth  = std::min( 32, effWidth );
    int effectHeight = std::min( 32, effHeight );
    for (int k = 0; k < 12; k++)
    {
      m_allStates[k].effWidth  = effectWidth;
      m_allStates[k].effHeight = effectHeight;
    }
    m_startState.effWidth  = effectWidth;
    m_startState.effHeight = effectHeight;

    //===== populate trellis =====
    for( int scanIdx = firstTestPos; scanIdx >= 0; scanIdx-- )
    {
      const ScanInfo& scanInfo = tuPars.m_scanInfo[ scanIdx ];
      if( enableScalingLists )
      {
        m_quant.initQuantBlock( tu, compID, cQP, lambda, quantCoeff[scanInfo.rasterPos] );
        xDecideAndUpdate( abs( tCoeff[scanInfo.rasterPos] ), scanInfo, zeroOut && ( scanInfo.posX >= effWidth || scanInfo.posY >= effHeight ), quantCoeff[scanInfo.rasterPos] );
      }
      else
        xDecideAndUpdate( abs( tCoeff[scanInfo.rasterPos] ), scanInfo, zeroOut && ( scanInfo.posX >= effWidth || scanInfo.posY >= effHeight ), defaultQuantisationCoefficient );
    }

    //===== find best path =====
    Decision  decision    = { std::numeric_limits<int64_t>::max(), -1, -2 };
    int64_t   minPathCost =  0;
    for( int8_t stateId = 0; stateId < 4; stateId++ )
    {
      int64_t pathCost = m_trellis[0][stateId].rdCost;
      if( pathCost < minPathCost )
      {
        decision.prevId = stateId;
        minPathCost     = pathCost;
      }
    }

    //===== backward scanning =====
    int scanIdx = 0;
    for( ; decision.prevId >= 0; scanIdx++ )
    {
      decision          = m_trellis[ scanIdx ][ decision.prevId ];
      int32_t blkpos    = tuPars.m_scanId2BlkPos[scanIdx].idx;
      qCoeff[ blkpos ]  = TCoeffSig( tCoeff[ blkpos ] < 0 ? -decision.absLevel : decision.absLevel );
      absSum           += decision.absLevel;
    }

    tu.lastPos[compID] = scanIdx - 1;
  }
}; // namespace DQIntern
  1. 其中xDecideAndUpdate 函数是其中核心函数,通过该函数计算每个变换系数的state和量化系数。
void DepQuant::xDecideAndUpdate( const TCoeff absCoeff, const ScanInfo& scanInfo, bool zeroOut, int quantCoeff )
  {
    Decision* decisions = m_trellis[ scanInfo.scanIdx ];

    std::swap( m_prevStates, m_currStates );

    xDecide( scanInfo, absCoeff, lastOffset(scanInfo.scanIdx), decisions, zeroOut, quantCoeff );

    if( scanInfo.scanIdx )
    {
      if( scanInfo.insidePos == 0 )
      {
        m_commonCtx.swap();
        m_currStates[0].updateStateEOS( scanInfo, m_prevStates, m_skipStates, decisions[0] );
        m_currStates[1].updateStateEOS( scanInfo, m_prevStates, m_skipStates, decisions[1] );
        m_currStates[2].updateStateEOS( scanInfo, m_prevStates, m_skipStates, decisions[2] );
        m_currStates[3].updateStateEOS( scanInfo, m_prevStates, m_skipStates, decisions[3] );
        ::memcpy( decisions+4, decisions, 4*sizeof(Decision) );
      }
      else if( !zeroOut )
      {
        m_currStates[0].updateState( scanInfo, m_prevStates, decisions[0] );
        m_currStates[1].updateState( scanInfo, m_prevStates, decisions[1] );
        m_currStates[2].updateState( scanInfo, m_prevStates, decisions[2] );
        m_currStates[3].updateState( scanInfo, m_prevStates, decisions[3] );
      }

      if( scanInfo.spt == SCAN_SOCSBB )
      {
        std::swap( m_prevStates, m_skipStates );
      }
    }
  }

http://www.kler.cn/a/515986.html

相关文章:

  • C语言教程——文件处理(1)
  • Python的泛型(Generic)与协变(Covariant)
  • 回归人文主义,探寻情感本质:从文艺复兴到AI时代,我的情感探索之旅
  • K8S中Service详解(三)
  • 实战经验:使用 Python 的 PyPDF 进行 PDF 操作
  • 基于单片机的智能台灯设计
  • oppo25届秋招补录内推来啦
  • 算法中的时间复杂度和空间复杂度
  • Jetson Xavier NX (ARM) 使用 PyTorch 安装 Open3D-ML 指南
  • GESP202309 三级【进制判断】题解(AC)
  • 【易康eCognition实验教程】003:点云数据加载浏览与操作详解
  • 探索WPF中的RelativeSource:灵活的资源绑定利器
  • Linux——文件与内存
  • 【c语言日寄】Vs调试——新手向
  • 大模型 / 智能体在智能运维领域的应用总结与发展趋势概述
  • win32汇编环境,按字节、双字等复制字符的操作
  • uniapp+Vue3(<script setup lang=“ts“>)模拟12306城市左右切换动画效果
  • ssm基于SSM的毕业论文开题评审管理系统
  • 【力扣:新动计划,编程入门 —— 题解 ②】
  • 为什么Foreach循环中为什么不能使用 remove/add操作?
  • 网络(三) 协议
  • DC-DC稳压电源——实战(基于Ti5450芯片)基础知识篇(1)
  • Linux权限管理:从用户切换到文件权限
  • 【MYSQL】mysql 常用命令
  • java基础学习——jdbc基础知识详细介绍
  • JS-Web API -day06