Apprentissage de code H.266 / VVC: quantification scalaire dépendante

Le module de quantification de VVC utilise une nouvelle technologie: la quantification scalaire dépendante. La quantification dépendante du scalaire signifie qu'un ensemble de valeurs de reconstruction admissibles des coefficients de transformée dépend de la valeur du niveau de coefficient de transformée avant le niveau de coefficient de transformée actuel dans l'ordre de reconstruction. Par rapport à la quantification scalaire indépendante traditionnelle utilisée dans HEVC, le principal effet de cette méthode est que le vecteur de reconstruction autorisé est plus dense dans l'espace vectoriel à N dimensions (N représente le nombre de coefficients de transformation dans le bloc de transformation). Cela signifie que pour un nombre moyen donné de vecteurs de reconstruction admissibles par volume unitaire à N dimensions, la distorsion moyenne entre le vecteur d'entrée et le vecteur de reconstruction le plus proche est réduite.

Le processus de mise en œuvre consistant à s'appuyer sur la quantification scalaire est:

  • Définissez deux quantificateurs scalaires avec différents niveaux de reconstruction;
  • Définissez la méthode de conversion entre deux quantificateurs scalaires.

 Les deux quantificateurs scalaires utilisés sont représentés par Q0 et Q1, comme le montre la figure ci-dessus. La position du niveau de reconstruction disponible est spécifiée uniquement par le pas de quantification δ (delta). Le quantificateur scalaire (Q0 ou Q1) utilisé n'est pas signalé explicitement dans le train binaire. Au contraire, le quantificateur utilisé pour le coefficient de transformée courant est déterminé par la parité du niveau de coefficient de transformée avant le coefficient de transformée courant dans l'ordre de codage / reconstruction.

 Comme le montre la figure ci-dessus, la conversion entre les deux quantificateurs scalaires (Q0 et Q1) est réalisée par une machine à états à 4 états. L'état peut prendre quatre valeurs différentes: 0, 1, 2, 3. Il est uniquement déterminé par la parité du niveau de coefficient de transformée avant le coefficient de transformée courant dans l'ordre de codage / reconstruction. Au début de la quantification inverse du bloc de transformation, l'état est mis à 0. Les coefficients de transformée sont reconstruits dans l'ordre de balayage (c'est-à-dire qu'ils sont décodés par entropie dans le même ordre). Après avoir reconstruit le coefficient de transformation actuel, mettez à jour l'état comme indiqué sur la figure, où k représente la valeur du niveau de coefficient de transformation.

Dans le code VTM, la fonction d'entrée de DQ est la fonction DepQuant :: quant . Les principales étapes sont les suivantes:

(1) Pré-initialiser les paramètres liés à la quantification

(2) Trouvez le premier coefficient non nul de TU

(3) Initialiser tous les paramètres d'état

(4) Parcourez tous les coefficients de l'arrière vers l'avant, trouvez la chaîne d'état quantifiée correspondante, le coefficient quantifié et le coût RD correspondant à l'état quantifié pour chaque coefficient. ( Implémentation de la fonction XDecideAndUpdate )

(5) Déterminer la chaîne d'états quantitative minimale du coût RD

(6) En fonction de la quantité d'état optimale, Hualian balaye tous les coefficients dans la direction aller et stocke les niveaux de coefficients de transformée quantifiés correspondants.

code montrer comme ci-dessous:

  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 )
  {
    CHECKD( tu.cs->sps->getSpsRangeExtension().getExtendedPrecisionProcessingFlag(), "ext precision is not supported" );

    //===== reset / pre-init =====
    const TUParameters& tuPars  = *g_Rom.getTUPars( tu.blocks[compID], compID );
    m_quant.initQuantBlock    ( tu, compID, cQP, lambda );//初始化相关量化参数
    TCoeff*       qCoeff      = tu.getCoeffs( compID ).buf;
    const TCoeff* tCoeff      = srcCoeff.buf;//
    const int     numCoeff    = tu.blocks[compID].area();
    ::memset( tu.getCoeffs( compID ).buf, 0x00, numCoeff*sizeof(TCoeff) );
    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;
    // MTS或者SBT模式,是否进行高频调零
    if( ( tu.mtsIdx[compID] > MTS_SKIP || (tu.cs->sps->getUseMTS() && tu.cu->sbtInfo != 0 && tuPars.m_height <= 32 && tuPars.m_width <= 32)) && compID == COMPONENT_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 = numCoeff - 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();
    for( ; firstTestPos >= 0; firstTestPos-- )//反向扫描找到第一个非零系数
    {
      if (zeroOutforThres && (tuPars.m_scanId2BlkPos[firstTestPos].x >= ((tuPars.m_width == 32 && zeroOut) ? 16 : 32)
                           || tuPars.m_scanId2BlkPos[firstTestPos].y >= ((tuPars.m_height == 32 && zeroOut) ? 16 : 32)))
        continue;
      TCoeff thresTmp = (enableScalingLists) ? TCoeff(thres / (4 * quantCoeff[tuPars.m_scanId2BlkPos[firstTestPos].idx]))
                                             : TCoeff(thres / (4 * defaultQuantisationCoefficient));

      if (abs(tCoeff[tuPars.m_scanId2BlkPos[firstTestPos].idx]) > thresTmp)
      {
        break;
      }
    }
    if( firstTestPos < 0 )
    {
      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 =====
    //===== 尝试不同的状态 =====
    //从后向前遍历所有的系数,针对每一个系数找出其最优的量化状态链
    // 这里的扫描顺序使用的是4x4子块扫描顺序的倒序
    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 =====
    //=====确定RD cost最小的量化状态链 =====
    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 ]  = ( tCoeff[ blkpos ] < 0 ? -decision.absLevel : decision.absLevel );//量化后的系数
      absSum           += decision.absLevel;
    }
  }

}; // namespace DQIntern

La fonction xDecideAndUpdate implémente l'état de quantification, le coefficient de quantification et le coût RD de chaque coefficient de transformation en appelant la fonction xDecide, puis met à jour le coût RD de chaque chaîne d'états de quantification.

  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.spt, absCoeff, lastOffset(scanInfo.scanIdx), decisions, zeroOut, quantCoeff );

    if( scanInfo.scanIdx )
    {
      if( scanInfo.eosbb )
      {
        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 )
      {
        switch( scanInfo.nextNbInfoSbb.num )
        {
        case 0:
          //更新当前状态为0的rdcost的值为decisions[0].rdcost;
          m_currStates[0].updateState<0>( scanInfo, m_prevStates, decisions[0] );
          //更新当前状态为1的rdcost的值为decisions[1].rdcost;
          m_currStates[1].updateState<0>( scanInfo, m_prevStates, decisions[1] );
          //更新当前状态为2的rdcost的值为decisions[2].rdcost;
          m_currStates[2].updateState<0>( scanInfo, m_prevStates, decisions[2] );
          //更新当前状态为3的rdcost的值为decisions[3].rdcost;
          m_currStates[3].updateState<0>( scanInfo, m_prevStates, decisions[3] );
          break;
        case 1:
          m_currStates[0].updateState<1>( scanInfo, m_prevStates, decisions[0] );
          m_currStates[1].updateState<1>( scanInfo, m_prevStates, decisions[1] );
          m_currStates[2].updateState<1>( scanInfo, m_prevStates, decisions[2] );
          m_currStates[3].updateState<1>( scanInfo, m_prevStates, decisions[3] );
          break;
        case 2:
          m_currStates[0].updateState<2>( scanInfo, m_prevStates, decisions[0] );
          m_currStates[1].updateState<2>( scanInfo, m_prevStates, decisions[1] );
          m_currStates[2].updateState<2>( scanInfo, m_prevStates, decisions[2] );
          m_currStates[3].updateState<2>( scanInfo, m_prevStates, decisions[3] );
          break;
        case 3:
          m_currStates[0].updateState<3>( scanInfo, m_prevStates, decisions[0] );
          m_currStates[1].updateState<3>( scanInfo, m_prevStates, decisions[1] );
          m_currStates[2].updateState<3>( scanInfo, m_prevStates, decisions[2] );
          m_currStates[3].updateState<3>( scanInfo, m_prevStates, decisions[3] );
          break;
        case 4:
          m_currStates[0].updateState<4>( scanInfo, m_prevStates, decisions[0] );
          m_currStates[1].updateState<4>( scanInfo, m_prevStates, decisions[1] );
          m_currStates[2].updateState<4>( scanInfo, m_prevStates, decisions[2] );
          m_currStates[3].updateState<4>( scanInfo, m_prevStates, decisions[3] );
          break;
        default:
          m_currStates[0].updateState<5>( scanInfo, m_prevStates, decisions[0] );
          m_currStates[1].updateState<5>( scanInfo, m_prevStates, decisions[1] );
          m_currStates[2].updateState<5>( scanInfo, m_prevStates, decisions[2] );
          m_currStates[3].updateState<5>( scanInfo, m_prevStates, decisions[3] );
        }
      }

      if( scanInfo.spt == SCAN_SOCSBB )
      {
        std::swap( m_prevStates, m_skipStates );
      }
    }
  }
void DepQuant::xDecide( const ScanPosType spt, const TCoeff absCoeff, const int lastOffset, Decision* decisions, bool zeroOut, int quanCoeff)
  {
    ::memcpy( decisions, startDec, 8*sizeof(Decision) );

    if( zeroOut )
    {
      if( spt==SCAN_EOCSBB )
      {
        m_skipStates[0].checkRdCostSkipSbbZeroOut( decisions[0] );
        m_skipStates[1].checkRdCostSkipSbbZeroOut( decisions[1] );
        m_skipStates[2].checkRdCostSkipSbbZeroOut( decisions[2] );
        m_skipStates[3].checkRdCostSkipSbbZeroOut( decisions[3] );
      }
      return;
    }
    //存储4个预量化值的相关参数
    PQData  pqData[4];
    //对absCoeff进行4次预量化,得到量化后的变换系数level和量化成该值的rdcost,第0个和第3个量化值是偶数,第1个和第2个量化值是奇数;
    m_quant.preQuantCoeff( absCoeff, pqData, quanCoeff );
    //前一个量化状态是0,则当前状态可以是0或者2,根据rdcost更新decision[0/2].rdcost的值
    m_prevStates[0].checkRdCosts( spt, pqData[0], pqData[2], decisions[0], decisions[2]);
    //前一个量化状态是1,则当前状态可以是2或者0,根据rdcost更新decision[2/0].rdcost的值
    m_prevStates[1].checkRdCosts( spt, pqData[0], pqData[2], decisions[2], decisions[0]);
    //前一个量化状态是2,则当前状态可以是1或3,根据rdcost更新decision[1/3].rdcost的值
    m_prevStates[2].checkRdCosts( spt, pqData[3], pqData[1], decisions[1], decisions[3]);
    //前一个量化状态是3,则当前状态可以是3或者1,根据rdcost更新decision[3/1].rdcost的值
    m_prevStates[3].checkRdCosts( spt, pqData[3], pqData[1], decisions[3], decisions[1]);
    if( spt==SCAN_EOCSBB )
    {
        m_skipStates[0].checkRdCostSkipSbb( decisions[0] );
        m_skipStates[1].checkRdCostSkipSbb( decisions[1] );
        m_skipStates[2].checkRdCostSkipSbb( decisions[2] );
        m_skipStates[3].checkRdCostSkipSbb( decisions[3] );
    }
    //初始化状态0和2的RD Cost
    m_startState.checkRdCostStart( lastOffset, pqData[0], decisions[0] );
    m_startState.checkRdCostStart( lastOffset, pqData[2], decisions[2] );
  }

Il y a encore beaucoup de détails que je n'ai pas compris. . . Plus tard, si vous avez le temps de comprendre, veuillez ajouter

Je suppose que tu aimes

Origine blog.csdn.net/BigDream123/article/details/106411490
conseillé
Classement