HM signBitHiding代码阅读与学习

Void TComTrQuant::signBitHidingHDQ( const ComponentID compID, TCoeff* pQCoef, TCoeff* pCoef, TCoeff* deltaU, const TUEntropyCodingParameters &codingParameters )
{
  const UInt width     = codingParameters.widthInGroups  << MLS_CG_LOG2_WIDTH;
  const UInt height    = codingParameters.heightInGroups << MLS_CG_LOG2_HEIGHT;
  const UInt groupSize = 1 << MLS_CG_SIZE; //groupSize通常都是16吧

  const TCoeff entropyCodingMinimum = -(1 << g_maxTrDynamicRange[toChannelType(compID)]);
  const TCoeff entropyCodingMaximum =  (1 << g_maxTrDynamicRange[toChannelType(compID)]) - 1;

  Int lastCG = -1; // lastCG = -1代表着最后一个非零系数一直没有找到
  Int absSum = 0 ;
  Int n ;

  for( Int subSet = (width*height-1) >> MLS_CG_SIZE; subSet >= 0; subSet-- )
  { //这个大循环控制着CG遍历的顺序,从最后一个CG向前遍历
    Int  subPos = subSet << MLS_CG_SIZE; // subPos就是CG的实际起始位置点
    Int  firstNZPosInCG=groupSize , lastNZPosInCG=-1 ;
    absSum = 0 ;
    //codingParameters.scan取出的位置与CG scanType有关系,可以关注下scanType成员
    for(n = groupSize-1; n >= 0; --n )
    {
      if( pQCoef[ codingParameters.scan[ n + subPos ]] )
      {
        lastNZPosInCG = n;//从一个CG的最后向前,找最后一个非零系数
        break;
      }
    }

    for(n = 0; n <groupSize; n++ )
    {
      if( pQCoef[ codingParameters.scan[ n + subPos ]] )
      {
        firstNZPosInCG = n////从一个CG的最前向后,找第一个非零系数
        break;
      }
    }

    for(n = firstNZPosInCG; n <=lastNZPosInCG; n++ )
    { //把非零系数的值加起来!不管正负,都叫absSum
      absSum += Int(pQCoef[ codingParameters.scan[ n + subPos ]]); //NOTE: RExt - only one bit is actually required!
    }

    if(lastNZPosInCG>=0 && lastCG==-1)
    {
      lastCG = 1 ;//更新lastCG的标志位
    }
    //SBH_THRESHOLD = 4 即当前CG第一个非零系数与最后一个非零系数的位置(指编码顺序的位置,非座标位置)相差大于等于4,则做signbitHiding,否则,当前CG就结束了。
    if( lastNZPosInCG-firstNZPosInCG>=SBH_THRESHOLD )
    {
      UInt signbit = (pQCoef[codingParameters.scan[subPos+firstNZPosInCG]]>0?0:1) ;//如果CG中量化后的第一个非零系数为正,signbit = 0;否则为1
      if( signbit!=(absSum&0x1) )  //compare signbit with sum_parity
      {//如果CG中量化后的第一个非零系数为正,非零系数的和为奇数 或 CG中量化后的第一个非零系数为负,非零系数的和为偶数。这时候,第一个系数会被hiding成负值,为了得到正确的符号结果,要有一个其他的系数做加1或减1操作,具体是谁,要通过RDO;
        TCoeff curCost    = std::numeric_limits<TCoeff>::max();
        TCoeff minCostInc = std::numeric_limits<TCoeff>::max();
        Int minPos =-1, finalChange=0, curChange=0;

        for( n = (lastCG==1?lastNZPosInCG:groupSize-1) ; n >= 0; --n )
        {//对于所有的非零系数,
          UInt blkPos   = codingParameters.scan[ n+subPos ];
          if(pQCoef[ blkPos ] != 0 )
          {
            if(deltaU[blkPos]>0)
            {
              curCost = - deltaU[blkPos];
              curChange=1 ;
            }
            else
            {
              //curChange =-1;
              if(n==firstNZPosInCG && abs(pQCoef[blkPos])==1)
              {
                curCost = std::numeric_limits<TCoeff>::max();
              }
              else
              {
                curCost = deltaU[blkPos];
                curChange =-1;
              }
            }
          }
          else
          {
            if(n<firstNZPosInCG)
            {
              UInt thisSignBit = (pCoef[blkPos]>=0?0:1);
              if(thisSignBit != signbit )
              {
                curCost = std::numeric_limits<TCoeff>::max();
              }
              else
              {
                curCost = - (deltaU[blkPos])  ;
                curChange = 1 ;
              }
            }
            else
            {
              curCost = - (deltaU[blkPos])  ;
              curChange = 1 ;
            }
          }

          if( curCost<minCostInc)
          {
            minCostInc = curCost ;
            finalChange = curChange ;
            minPos = blkPos ;
          }
        } //CG loop
        // 以上for循环为RDO抉择最优finalChange符号和Position的过程。抉择的依据是之前预存的deltaU[blkPos]。
        //deltaU[blkPos]可以理解为当前量化结果做完+1 -1调整后,新加入的量化的误差,这个误差当然越小越好。+代表量化结果可以考虑向上做+1偏移;为-则向下做偏移。由于不同系数量化的误差不一,所以存在最优的量化调整的系数,使得既能实现+-1操作,cost的损失又最小。
        if(pQCoef[minPos] == entropyCodingMaximum || pQCoef[minPos] == entropyCodingMinimum)
        {
          finalChange = -1;
        }

        if(pCoef[minPos]>=0)
        {
          pQCoef[minPos] += finalChange ;
        }
        else
        {
          pQCoef[minPos] -= finalChange ;
        }
      } // Hide
    }
    if(lastCG==1)
    {
      lastCG=0 ;
    }
  } // TU loop

  return;
}

Sign Bit Hiding ,据说是变换系数编码中的符号位隐藏,可以带来coding gain。

通过对每个CG单元量化结果的绝对值和的奇偶性,对编码顺序中第一个非零系数的符号判断,来实现符号位的隐藏,每个CG就可以节省1bit (第一个非零系数的符号位不用编码了)。

HEVC文档里说,

The transform coefficient coding in HEVC includes an option to omit the coding ofone sign flag per TSB(就是CG, coefficient group) if a sufficient number of significant coefficients is present in theblock. The scheme is called sign data hiding. Since the sign flags can be consideredto be uncorrelated, these flags are coded in CABAC bypass mode at the cost of 1 bit(see Sect.10.2). Hence, sign data hiding saves one bit per TSB when applied. The use of sign data hiding is configured by a flag in the PPS. 

SBH是利用里变换系数本身的不相关性,其编码是用bypass模式编码。使用标识为在PPS里。

当CG中的significant coefficient超过了3个(看代码里是编码顺序的 最后一个非零系数与第一个非零系数之间相距超过了3个)则第一个非零系数(扫描序号最小的那个)的符号位不用再编码了,可以通过块内所有系数的绝对值的和的奇偶性来确定。


coeff[0]的符号通过:

coeff[0] = (1 -  ((absSum) % 2)) *| coeff[0] |

也就是说:

当CG系数绝对值的和为奇数,则coeff[0]就会被判断成负值。

当CG系数绝对值的和为偶数,则coeff[0]就会被判断成正值。

如果实际的coeff[0]的符号和CG系数绝对值和的奇偶性不相符,则需要通过对其他的某一个系数做+1 -1操作,是absSum的奇偶性与coeff[0]的符号一致,但具体是哪个系数,做+1还是-1,则需要经过RDO的过程。

deltaU预存了每个系数与其相邻量化结果之间的一定意义上的误差(不知道是哪种意义,但是代表着量化误差就跑不了了)。

也就是说,做了+ 1 或 - 1,误差是有的,就看谁调完误差最小,那么就调谁了。

下图的块,绝对值和是49,而第一个系数13是正值,所以需要做调整,如把1->0

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章