H.266/VVC技术学习:色度联合编码(JCCR)技术

VVC支持色度联合编码(joint coding of chroma residual ,JCCR)模式。

色度联合编码模式的使用(激活)由TU级标志tu_joint_cbcr_residual_flag表示,所选模式由色度CBFs隐式表示。如果一个TU的色度CBFs中的一个或两个都等于1(tu_cbf_cb或者tu_cbf_cr),则存在标志tu_joint_cbcr_residual_flag

在PPS和slice head中,联合色度残差编码模式下的色度QP偏移值与常规色度残差编码模式下的色度QP偏移值不同,这些色度QP偏移值用于推导使用联合色度残差编码模式编码的块的色度QP值。

JCCR模式有3个子模式。当相应的联合色度编码模式(表中的模式2)在TU中激活时,在对该TU进行量化和解码时,将该色度QP偏移添加到应用的亮度派生色度QP中(the applied luma-derived chroma QP)。对于其他模式(表中的模式1和模式3),色度QPs的推导方法与常规Cb或Cr块相同。

传输变换块的色度残差(resCb和resCr)的重建过程如表所示。当JCCR模式被激活时,一个联合色度残差块(resJointC [x] [y]被传递过来,然后通过考虑各种信息(比如tu_cbf_cb tu_cbf_cr, CSign)推导出残差块Cb (resCb)和残差块Cr (resCr)。

在编码器端,联合色度分量的推导如下。根据模式(如上表所示),编码器产生resJointC{1,2}如下:

若mode = 2(重构后的单个残差Cb = C, Cr = CSign * C),则根据

   -resJointC[ x ][ y ] = ( resCb[ x ][ y ] + CSign * resCr[ x ][ y ] ) / 2.

否则,若mode = 1(重构后的单个残差Cb = C, Cr = (CSign * C) / 2),则根据

   -resJointC[ x ][ y ] = ( 4 * resCb[ x ][ y ] + 2 * CSign * resCr[ x ][ y ] ) / 5.

否则(mode = 3,重构后的单个残差Cr = C, Cb = (CSign * C) / 2),根据

   -resJointC[ x ][ y ] = ( 4 * resCr[ x ][ y ] + 2 * CSign * resCb[ x ][ y ] ) / 5.

 

tu_cbf_cb

tu_cbf_cr

reconstruction of Cb and Cr residuals

mode

1

0

resCb[ x ][ y ] = resJointC[ x ][ y ]
resCr[ x ][ y ] = ( CSign * resJointC[ x ][ y ] ) >> 1

1

1

1

resCb[ x ][ y ] = resJointC[ x ][ y ]
resCr[ x ][ y ] = CSign * resJointC[ x ][ y ]

2

0

1

resCb[ x ][ y ] = ( CSign * resJointC[ x ][ y ] ) >> 1
resCr[ x ][ y ] = resJointC[ x ][ y ]

3

上述三种联合色度编码模式仅在I片上受支持。在P和B片中,只支持模式2。因此,在P和B片中,语法元素tu_joint_cbcr_residual_flag仅在色度cbfs都为1时才存在(即tu_cbf_cb=1且tu_cbf_cr=1)。

JCCR模式可以与色度变换跳过(TransformSkip,TS)模式相结合。 为了加快编码端选择速度,JCCR变换的选择取决于Cb和Cr分量的独立编码是否选择DCT-2或TS作为最佳变换,以及独立色度编码中是否存在非零系数。具体分为以下两种情况:

(1)当Cb和Cr分量都使用DCT-2或者当只有一个色度分量(Cb或Cr)具有非零系数并且色度分量选择DCT-2时,则JCCR模式仅检查DCT-2。类似地,当Cb和Cr分量都使用TS时,或者当只有一个色度分量具有非零系数并且分量选择TS时,JCCR模式仅检查TS。

(2)当Cb和Cr分量的选择变换不同时,即当一个选择DCT-2而另一个选择TS或反之时,JCCR对DCT-2和TS都进行测试。在这些情况下,在TS模式之前执行DCT-2模式的R-D检查,如果DCT-2的系数都为零,则跳过TS R-D检查。

TrQuant::TrQuant() : m_quant( nullptr )
{
  // allocate temporary buffers

  {
    m_invICT      = m_invICTMem + maxAbsIctMode;
    m_invICT[ 0]  = invTransformCbCr< 0>;
    m_invICT[ 1]  = invTransformCbCr< 1>;
    m_invICT[-1]  = invTransformCbCr<-1>;
    m_invICT[ 2]  = invTransformCbCr< 2>;
    m_invICT[-2]  = invTransformCbCr<-2>;
    m_invICT[ 3]  = invTransformCbCr< 3>;
    m_invICT[-3]  = invTransformCbCr<-3>;
    m_fwdICT      = m_fwdICTMem + maxAbsIctMode;
    m_fwdICT[ 0]  = fwdTransformCbCr< 0>;
    m_fwdICT[ 1]  = fwdTransformCbCr< 1>;
    m_fwdICT[-1]  = fwdTransformCbCr<-1>;
    m_fwdICT[ 2]  = fwdTransformCbCr< 2>;
    m_fwdICT[-2]  = fwdTransformCbCr<-2>;
    m_fwdICT[ 3]  = fwdTransformCbCr< 3>;
    m_fwdICT[-3]  = fwdTransformCbCr<-3>;
  }
}

invTransformCbCr函数如下,该函数主要是通过Cb(Cr)残差求出Cr(Cb)残差

template<int signedMode> void invTransformCbCr( PelBuf &resCb, PelBuf &resCr )
{
  Pel*  cb  = resCb.buf;
  Pel*  cr  = resCr.buf;
  for( SizeType y = 0; y < resCb.height; y++, cb += resCb.stride, cr += resCr.stride )
  {
    for( SizeType x = 0; x < resCb.width; x++ )
    {
      if      ( signedMode ==  1 )  { cr[x] =  cb[x] >> 1;  }
      else if ( signedMode == -1 )  { cr[x] = -cb[x] >> 1;  }
      else if ( signedMode ==  2 )  { cr[x] =  cb[x]; }
      else if ( signedMode == -2 )  { cr[x] = (cb[x] == -32768 && sizeof(Pel) == 2) ? 32767 : -cb[x]; }   // non-normative clipping to prevent 16-bit overflow
      else if ( signedMode ==  3 )  { cb[x] =  cr[x] >> 1; }
      else if ( signedMode == -3 )  { cb[x] = -cr[x] >> 1; }
    }
  }
}

fwdTransformCbCr函数是通过ResCb和ResCr求出ResJointC

template<int signedMode> std::pair<int64_t,int64_t> fwdTransformCbCr( const PelBuf &resCb, const PelBuf &resCr, PelBuf& resC1, PelBuf& resC2 )
{
  const Pel*  cb  = resCb.buf;
  const Pel*  cr  = resCr.buf;
  Pel*        c1  = resC1.buf;
  Pel*        c2  = resC2.buf;
  int64_t     d1  = 0;
  int64_t     d2  = 0;
  for( SizeType y = 0; y < resCb.height; y++, cb += resCb.stride, cr += resCr.stride, c1 += resC1.stride, c2 += resC2.stride )
  {
    for( SizeType x = 0; x < resCb.width; x++ )
    {
      int cbx = cb[x], crx = cr[x];
      if      ( signedMode ==  1 )
      {
        c1[x] = Pel( ( 4*cbx + 2*crx ) / 5 );
        d1   += square( cbx - c1[x] ) + square( crx - (c1[x]>>1) );
      }
      else if ( signedMode == -1 )
      {
        c1[x] = Pel( ( 4*cbx - 2*crx ) / 5 );
        d1   += square( cbx - c1[x] ) + square( crx - (-c1[x]>>1) );
      }
      else if ( signedMode ==  2 )
      {
        c1[x] = Pel( ( cbx + crx ) / 2 );
        d1   += square( cbx - c1[x] ) + square( crx - c1[x] );
      }
      else if ( signedMode == -2 )
      {
        c1[x] = Pel( ( cbx - crx ) / 2 );
        d1   += square( cbx - c1[x] ) + square( crx + c1[x] );
      }
      else if ( signedMode ==  3 )
      {
        c2[x] = Pel( ( 4*crx + 2*cbx ) / 5 );
        d1   += square( cbx - (c2[x]>>1) ) + square( crx - c2[x] );
      }
      else if ( signedMode == -3 )
      {
        c2[x] = Pel( ( 4*crx - 2*cbx ) / 5 );
        d1   += square( cbx - (-c2[x]>>1) ) + square( crx - c2[x] );
      }
      else
      {
        d1   += square( cbx );
        d2   += square( crx );
      }
    }
  }
  return std::make_pair(d1,d2);
}

猜你喜欢

转载自blog.csdn.net/BigDream123/article/details/106267476