Aprendizaje de código H.266 / VVC: cuantificación escalar dependiente

El módulo de cuantificación de VVC utiliza una nueva tecnología: cuantificación escalar dependiente. La cuantificación dependiente del escalar significa que un conjunto de valores de reconstrucción permisibles de los coeficientes de transformación dependen del valor del nivel del coeficiente de transformación antes del nivel actual del coeficiente de transformación en el orden de reconstrucción. En comparación con la cuantificación escalar independiente tradicional utilizada en HEVC, el efecto principal de este método es que el vector de reconstrucción permitido es más denso en el espacio vectorial N-dimensional (N representa el número de coeficientes de transformación en el bloque de transformación). Esto significa que para un número promedio dado de vectores de reconstrucción permitidos por unidad de volumen N-dimensional, se reduce la distorsión promedio entre el vector de entrada y el vector de reconstrucción más cercano.

El proceso de implementación de confiar en la cuantificación escalar es:

  • Defina dos cuantificadores escalares con diferentes niveles de reconstrucción;
  • Defina el método de conversión entre dos cuantificadores escalares.

 Los dos cuantificadores escalares utilizados están representados por Q0 y Q1, como se muestra en la figura anterior. La posición del nivel de reconstrucción disponible se especifica únicamente mediante el paso de cuantificación δ (delta). El cuantificador escalar (Q0 o Q1) utilizado no se señaliza explícitamente en el tren de bits. Por el contrario, el cuantificador utilizado para el coeficiente de transformación actual está determinado por la paridad del nivel del coeficiente de transformación antes del coeficiente de transformación actual en el orden de codificación / reconstrucción.

 Como se muestra en la figura anterior, la conversión entre los dos cuantificadores escalares (Q0 y Q1) se realiza mediante una máquina de estado con 4 estados. El estado puede tomar cuatro valores diferentes: 0, 1, 2, 3. Se determina de forma única por la paridad del nivel del coeficiente de transformación antes del coeficiente de transformación actual en el orden de codificación / reconstrucción. Al comienzo de la cuantificación inversa del bloque de transformación, el estado se establece en 0. Los coeficientes de transformación se reconstruyen en el orden de exploración (es decir, se decodifican entropía en el mismo orden). Después de reconstruir el coeficiente de transformación actual, actualice el estado como se muestra en la figura, donde k representa el valor del nivel del coeficiente de transformación.

En el código VTM, la función de entrada de DQ es la función DepQuant :: quant . Los pasos principales son los siguientes:

(1) Preinicializar los parámetros relacionados con la cuantificación

(2) Encuentre el primer coeficiente distinto de cero de TU

(3) Inicializar todos los parámetros de estado

(4) Atraviese todos los coeficientes de atrás hacia adelante, encuentre la cadena de estado cuantificado correspondiente, el coeficiente cuantificado y el Costo RD correspondiente al estado cuantificado para cada coeficiente. ( Implementación de la función XDecideAndUpdate )

(5) Determine la cadena de estado cuantitativa mínima del costo de RD

(6) De acuerdo con la cantidad de estado óptimo, Hualian escanea todos los coeficientes en la dirección de avance y almacena los niveles de coeficiente de transformación cuantificados correspondientes.

el código se muestra a continuación:

  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 función xDecideAndUpdate implementa el estado de cuantificación, el coeficiente de cuantificación y el costo RD de cada coeficiente de transformación llamando a la función xDecide, y luego actualiza el costo RD de cada cadena de estado de cuantificación.

  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] );
  }

Aún quedan muchos detalles que no he entendido. . . Más tarde, si tiene tiempo para comprender, agregue

Supongo que te gusta

Origin blog.csdn.net/BigDream123/article/details/106411490
Recomendado
Clasificación