H.266/VVC中有两个transformNxN函数,它们的参数不同,如下:
void transformNxN ( TransformUnit& tu, const ComponentID& compID, const QpParam& cQP, std::vector
void transformNxN ( TransformUnit& tu, const ComponentID& compID, const QpParam& cQP, TCoeff& uiAbsSum, const Ctx& ctx, const bool loadTr = false );
第一个transformNxN()函数主要是通过比较DCT-2变换和TransformSkip的SAD来决定修改trModes对应模式是否使用。
注意:在LFNST关闭的情况下,对于亮度块,这个函数会比较DCT-2 TS MTS四种变换核的SAD来修改trModes对应模式。
VTM7.0中加入了色度模式下的TransformSkip模式,也随之多了色度下DCT-2和TransformSkip的比较。
每次进行变换时,都会将变换后的系数保存到m_mtsCoeffs变量里,用于之后第二个transfromNxN函数进行加载。
总共五种模式:
enum MTSIdx
{
MTS_DCT2_DCT2 = 0,
MTS_SKIP = 1,
MTS_DST7_DST7 = 2,
MTS_DCT8_DST7 = 3,
MTS_DST7_DCT8 = 4,
MTS_DCT8_DCT8 = 5
};
代码如下:
/*
通过比较DCT-2的SAD和TransformSkip的SAD决定是否使用TransFormSkip,只有尺寸小于等于32才调用
trModes数组存有多核变换的索引MTSIdx和能否进行参与后续比较(int,bool),经过本函数后修改MTSIdx对应的bool值
*/
void TrQuant::transformNxN( TransformUnit& tu, const ComponentID& compID, const QpParam& cQP, std::vector* trModes, const int maxCand )
{
CodingStructure &cs = *tu.cs;
const CompArea &rect = tu.blocks[compID];
const uint32_t width = rect.width;
const uint32_t height = rect.height;
const CPelBuf resiBuf = cs.getResiBuf(rect);
CHECK( cs.sps->getMaxTbSize() < width, "Unsupported transformation size" );
int pos = 0;//表示trCost的第二个值,应该表示MTSIdx
std::vector trCosts;//trCosts:第一个值为SAD的值,第二个值为bool值
std::vector::iterator it = trModes->begin();//it有两个值,一个代表多核变换的模式号,一个是bool值表示是否参与后续比较
const double facBB[] = { 1.2, 1.3, 1.3, 1.4, 1.5 };//一个系数值,含义?
while( it != trModes->end() )
{ //遍历trModes,获得每一个多核变换模式对应的cost
#if JVET_P0058_CHROMA_TS
tu.mtsIdx[compID] = it->first;
#else
tu.mtsIdx = it->first;
#endif
#if JVET_P0058_CHROMA_TS
CoeffBuf tempCoeff( m_mtsCoeffs[tu.mtsIdx[compID]], rect);//获取MTS对应系数
#else
CoeffBuf tempCoeff( m_mtsCoeffs[tu.mtsIdx], rect );
#endif
if( tu.noResidual )//如果没有残差,跳过
{
int sumAbs = 0;
trCosts.push_back( TrCost( sumAbs, pos++ ) );
it++;
continue;
}
#if JVET_P0058_CHROMA_TS
if ( tu.mtsIdx[compID] == MTS_SKIP )//TransformSkip模式:变换跳过模式
#else
#if JVET_P0059_CHROMA_BDPCM
if ((isLuma(compID) && tu.mtsIdx == MTS_SKIP) || (isChroma(compID) && tu.cu->bdpcmModeChroma))
#else
if( isLuma(compID) && tu.mtsIdx == MTS_SKIP )
#endif
#endif
{
xTransformSkip( tu, compID, resiBuf, tempCoeff.buf );
}
else//Transform模式:正常变换
{
xT( tu, compID, resiBuf, tempCoeff, width, height );
}
int sumAbs = 0;//绝对误差和:SAD
for( int pos = 0; pos < width*height; pos++ )
{
//这里的pos是局部变量,表示的是每一个块里面的每一个像素值,和外部pos意义不同
sumAbs += abs( tempCoeff.buf[pos] );
}
double scaleSAD=1.0;
#if JVET_P0058_CHROMA_TS
if ( tu.mtsIdx[compID] == MTS_SKIP && ((floorLog2(width) + floorLog2(height)) & 1) == 1)
#else
if (isLuma(compID) && tu.mtsIdx==MTS_SKIP && ((floorLog2(width) + floorLog2(height)) & 1) == 1 )
#endif
{
scaleSAD=1.0/1.414213562; // //补偿系数 compensate for not scaling transform skip coefficients by 1/sqrt(2)
}
#if JVET_P1000_REMOVE_TRANFORMSHIFT_IN_TS_MODE
#if JVET_P0058_CHROMA_TS
if (tu.mtsIdx[compID] == MTS_SKIP)
#else
if (isLuma(compID) && tu.mtsIdx == MTS_SKIP)
#endif
{
int trShift = getTransformShift(tu.cu->slice->getSPS()->getBitDepth(toChannelType(compID)), rect.size(), tu.cu->slice->getSPS()->getMaxLog2TrDynamicRange(toChannelType(compID)));
scaleSAD *= pow(2, trShift);
}
#endif
trCosts.push_back( TrCost( int(sumAbs*scaleSAD), pos++ ) );
it++;
}
int numTests = 0;
std::vector::iterator itC = trCosts.begin();
#if JVET_P0058_CHROMA_TS
const double fac = facBB[std::max(0, floorLog2(std::max(width, height)) - 2)];
#else
const double fac = facBB[floorLog2(std::max(width, height))-2];
#endif
const double thr = fac * trCosts.begin()->first;//第一个SAD,带系数的(系数thr用于简化,是一种快速算法),用于其他MTS模式比较时
const double thrTS = trCosts.begin()->first;//第一个SAD,用于TransformSkip时比较
while( itC != trCosts.end() )//遍历变换代价值
{
const bool testTr = itC->first <= ( itC->second == 1 ? thrTS : thr ) && numTests <= maxCand;
trModes->at( itC->second ).second = testTr;//修改MTS模式对应的bool值
numTests += testTr;
itC++;
}
}
第二个transformNxN()函数主要是进行对残差块进行变换。
流程如下:
注意,这里函数中的参数LoadTr变量,主要是用于防止进行重复的一次变换。
loadTr是在xRecurIntraCodingLumaQT函数和xRecurIntraChromaCodingQT函数传递给xIntraCodingTUBlock函数的参数。若当前待检查的变换模式数目大于1的时候,loadTr参数为True,此时xIntraCodingTUBlock函数里会调用第一个TransformNxN函数进行变换并将一次变换系数保存在m_mtsCoeffs变量里,当调用第二个transformNxN函数时,若loadTr为true,说明已经进行过一次变换,则可直接加载出来。
#if JVET_O0502_ISP_CLEANUP
void TrQuant::transformNxN( TransformUnit& tu, const ComponentID& compID, const QpParam& cQP, TCoeff& uiAbsSum, const Ctx& ctx, const bool loadTr )
#else
void TrQuant::transformNxN( TransformUnit &tu, const ComponentID &compID, const QpParam &cQP, TCoeff &uiAbsSum, const Ctx &ctx, const bool loadTr, double* diagRatio, double* horVerRatio )
#endif
{
CodingStructure &cs = *tu.cs;
const SPS &sps = *cs.sps;
const CompArea &rect = tu.blocks[compID];
const uint32_t uiWidth = rect.width;
const uint32_t uiHeight = rect.height;
const CPelBuf resiBuf = cs.getResiBuf(rect);
CoeffBuf rpcCoeff = tu.getCoeffs(compID);
if( tu.noResidual )//如果没有残差,直接设置cbf
{
uiAbsSum = 0;//存放残差系数绝对值的和;
TU::setCbfAtDepth( tu, compID, tu.depth, uiAbsSum > 0 );
return;
}
RDPCMMode rdpcmMode = RDPCM_OFF;//设置RDPCM模式
rdpcmNxN(tu, compID, cQP, uiAbsSum, rdpcmMode);
if( tu.cu->bdpcmMode && isLuma(compID) )
{
tu.mtsIdx = MTS_SKIP;
}
if (rdpcmMode == RDPCM_OFF)
{
uiAbsSum = 0;
// transform and quantize
if (CU::isLosslessCoded(*tu.cu))//旁路掉变换和量化,即直接把残差系数赋值给rpcCoeff;
{
const bool rotateResidual = TU::isNonTransformedResidualRotated( tu, compID );//是否残差旋转
for( uint32_t y = 0; y < uiHeight; y++ )
{
for( uint32_t x = 0; x < uiWidth; x++ )
{
const Pel currentSample = resiBuf.at( x, y );
if( rotateResidual )//存在残差旋转
{
rpcCoeff.at( uiWidth - x - 1, uiHeight - y - 1 ) = currentSample;
}
else
{
rpcCoeff.at( x, y ) = currentSample;
}
uiAbsSum += TCoeff( abs( currentSample ) );//计算出SAD的和
}
}
}
else//没有被旁路,进行变换、量化等操作
{
#if MAX_TB_SIZE_SIGNALLING
CHECK( cs.sps->getMaxTbSize() < uiWidth, "Unsupported transformation size" );
#else
CHECK( MAX_TB_SIZEY < uiWidth, "Unsupported transformation size" );
#endif
CoeffBuf tempCoeff( loadTr ? m_mtsCoeffs[tu.mtsIdx] : m_plTempCoeff, rect );
DTRACE_PEL_BUF( D_RESIDUALS, resiBuf, tu, tu.cu->predMode, compID );
//loadTr变量用来加载之前保存的变换系数
//m_mtsCoeffs变换系数是在另一个TransformNxN函数中保存的系数,仅进行过一次变换
if( !loadTr )
{
if( isLuma(compID) && tu.mtsIdx == MTS_SKIP )
{
xTransformSkip( tu, compID, resiBuf, tempCoeff.buf );//变换跳过模式,如果跳过变换,将残差系数经过伸缩和移位后赋值给temCoeff.buff;
}
else
{
xT( tu, compID, resiBuf, tempCoeff, uiWidth, uiHeight );//正常变换模式,tempCoeff保存经过变换后的残差系数
}
}
#if !JVET_O0502_ISP_CLEANUP
//we do this only with the DCT-II coefficients
if( isLuma(compID) &&
!loadTr && tu.mtsIdx == MTS_DCT2_DCT2
)
{
//it gets the distribution of the coefficients energy, which will be useful to discard ISP tests
//它得到了系数能量的分布,这将有助于抛弃ISP测试
xGetCoeffEnergy( tu, compID, tempCoeff, diagRatio, horVerRatio );
}
#endif
if( sps.getUseLFNST() )//进行二次变换LFNST
{
xFwdLfnst( tu, compID, loadTr );
}
DTRACE_COEFF_BUF( D_TCOEFF, tempCoeff, tu, tu.cu->predMode, compID );
xQuant( tu, compID, tempCoeff, uiAbsSum, cQP, ctx );//量化
DTRACE_COEFF_BUF( D_TCOEFF, tu.getCoeffs( compID ), tu, tu.cu->predMode, compID );
}
}
// set coded block flag (CBF)
TU::setCbfAtDepth (tu, compID, tu.depth, uiAbsSum > 0);
}
其中xT函数可以参考https://blog.csdn.net/BigDream123/article/details/102748739
xFwdLfnst函数可以参考https://blog.csdn.net/BigDream123/article/details/102748968
xTransformSkip函数可以参考https://blog.csdn.net/BigDream123/article/details/102748968