H.266/VVC专栏传送
上一篇:H.266/VVC-VTM代码学习-帧内预测15-解码端解压缩decompressCtu函数及xReconIntraQT调用xIntraRecQT函数完成帧内预测重建
下一篇:H.266/VVC-VTM代码学习-帧内预测17-initIntraPatternChTypeISP函数初始化ISP的帧内预测
目录
前言
VTM是H.266/VVC视频编码标准的参考软件,研究VTM代码给研究人员解释了VVC编码标准的详细标准规范与细节。
本文是笔者对VTM代码的一点学习记录,成文于笔者刚开始接触VVC期间,期间很多概念和理论框架还很不成熟,若文中存在错误欢迎批评指正,也欢迎广大视频编码学习者沟通交流、共同进步。
VTM代码的下载及编译请参考博文:
【视频编码学习】H.266/VVC参考软件VTM配置运行(VTM-6.0版本)
本文涉及的代码主要存在于工程下的Lib\DecoderLib\DecCu.cpp文件中。
一、xIntraRecBlk函数完成指定TU的指定分量的帧内重建
该函数入口在上一篇博客的xIntraRecQT函数中
void DecCu::xIntraRecBlk( TransformUnit& tu, const ComponentID compID )
{
//如果当前TU的指定分量无效(不满足色度采样模式有效,且当前分量ID有效,且当前块的尺寸不为0),则返回
if( !tu.blocks[ compID ].valid() )
{
return;
}
//当前编码结构
CodingStructure &cs = *tu.cs;
//当前区域
const CompArea &area = tu.blocks[compID];
//当前通道类型LUMA or CHROMA
const ChannelType chType = toChannelType( compID );
//对应位置预测缓存
PelBuf piPred = cs.getPredBuf( area );
//对应PU
const PredictionUnit &pu = *tu.cs->getPU( area.pos(), chType );
//帧内预测模式
const uint32_t uiChFinalMode = PU::getFinalIntraMode( pu, chType );
//解码缓存
PelBuf pReco = cs.getRecoBuf(area);
//===== init availability pattern =====
//预测尺寸和变换尺寸是否不同。当前为Y分量,且ISP模式为垂直划分,且((w == 8 && h > 4) 或 w == 4)时,预测尺寸和变换尺寸不同
bool predRegDiffFromTB = CU::isPredRegDiffFromTB(*tu.cu, compID);
//是否当前使用ISP模式,且TB为第一块TB
bool firstTBInPredReg = CU::isFirstTBInPredReg(*tu.cu, compID, area);
//预测区域
CompArea areaPredReg(COMPONENT_Y, tu.chromaFormat, area);
//若使用ISP且当前为LUMA块
if (tu.cu->ispMode && isLuma(compID))
{
//若预测尺寸与变换尺寸不同
if (predRegDiffFromTB)
{
//若当前TB第一次进行预测
if (firstTBInPredReg)
{
//调整预测区域,将CU尺寸为4xN and 8xN (N > 4)的块的ISP预测区域改为4xN
CU::adjustPredArea(areaPredReg);
//初始化ISP下的帧内预测(设置帧内预测参数,并获取参考像素、进行参考像素滤波)
m_pcIntraPred->initIntraPatternChTypeISP(*tu.cu, areaPredReg, pReco);
}
}
//若预测尺寸与变换尺寸相同
else
{
//初始化ISP下的帧内预测(设置帧内预测参数,并获取参考像素、进行参考像素滤波)
m_pcIntraPred->initIntraPatternChTypeISP(*tu.cu, area, pReco);
}
}
//不使用ISP或当前为CHROMA块
else
{
//初始化帧内预测(设置帧内预测参数,并获取参考像素、进行参考像素滤波)
m_pcIntraPred->initIntraPatternChType(*tu.cu, area);
}
//===== get prediction signal =====
//===== 得到预测信号 =====
//当前为色度分量,且为CCLM模式(67-69)
if( compID != COMPONENT_Y && PU::isLMCMode( uiChFinalMode ) )
{
//当前CU的第一个PU
const PredictionUnit& pu = *tu.cu->firstPU;
//对重构的亮度块根据YUV格式进行相应的下采样
m_pcIntraPred->xGetLumaRecPixels( pu, area );
//通过线性映射得到色度分量预测值
m_pcIntraPred->predIntraChromaLM( compID, piPred, pu, area, uiChFinalMode );
}
//不是色度分量,或不为CCLM模式
else
{
//如果是MIP模式
if( PU::isMIP( pu, chType ) )
{
//获取参考像素并进行相应下采样
m_pcIntraPred->initIntraMip( pu, area );
//将输入向量与对应矩阵相乘,需要上采样时进行上采样,得到MIP预测结果
m_pcIntraPred->predIntraMip( compID, piPred, pu );
}
//如果不是MIP模式
else
{
//若预测尺寸与变换尺寸不同
if (predRegDiffFromTB)
{
//使用ISP模式且当前TB为第一块
if (firstTBInPredReg)
{
//预测区域缓存
PelBuf piPredReg = cs.getPredBuf(areaPredReg);
//根据不同模式进行预测,需要时使用PDPC
m_pcIntraPred->predIntraAng(compID, piPredReg, pu);
}
}
//预测尺寸与变换尺寸相同时
else
//根据不同模式进行预测,需要时使用PDPC
m_pcIntraPred->predIntraAng(compID, piPred, pu);
}
}
const Slice &slice = *cs.slice;
//LMCS可用,且当前为帧内模式或不为帧内模式但CTUFlag有效
bool flag = slice.getLmcsEnabledFlag() && (slice.isIntra() || (!slice.isIntra() && m_pcReshape->getCTUFlag()));
//flag有效,且LmcsChromaResidualScaleFlag有效,且当前不为亮度分量,且使用JCCR时
if (flag && slice.getPicHeader()->getLmcsChromaResidualScaleFlag() && (compID != COMPONENT_Y) && (tu.cbf[COMPONENT_Cb] || tu.cbf[COMPONENT_Cr]))
{
const Area area = tu.Y().valid() ? tu.Y() : Area(recalcPosition(tu.chromaFormat, tu.chType, CHANNEL_TYPE_LUMA, tu.blocks[tu.chType].pos()), recalcSize(tu.chromaFormat, tu.chType, CHANNEL_TYPE_LUMA, tu.blocks[tu.chType].size()));
const CompArea &areaY = CompArea(COMPONENT_Y, tu.chromaFormat, area);
//求得LMCS中基于亮度的色度残差缩放的scale
int adj = m_pcReshape->calculateChromaAdjVpduNei(tu, areaY);
//保存scale
tu.setChromaAdj(adj);
}
//===== inverse transform =====
//===== 反变换 =====
//获取残差缓存
PelBuf piResi = cs.getResiBuf( area );
const QpParam cQP( tu, compID );
//使用JCCR且当前为色度分量
if( tu.jointCbCr && isChroma(compID) )
{
//若当前为Cb分量
if( compID == COMPONENT_Cb )
{
//获取Cr残差缓存
PelBuf resiCr = cs.getResiBuf( tu.blocks[ COMPONENT_Cr ] );
//jointCbCr > 1
if( tu.jointCbCr >> 1 )
{
//反变换得到联合色度残差放入Cb
m_pcTrQuant->invTransformNxN( tu, COMPONENT_Cb, piResi, cQP );
}
//jointCbCr <= 1
else
{
const QpParam qpCr( tu, COMPONENT_Cr );
//反变换得到联合色度残差放入Cr
m_pcTrQuant->invTransformNxN( tu, COMPONENT_Cr, resiCr, qpCr );
}
//根据联合色度残差得到Cb和Cr的值
m_pcTrQuant->invTransformICT( tu, piResi, resiCr );
}
}
//不使用JCCR,或当前为亮度分量
else
//code block flag不为0
if( TU::getCbf( tu, compID ) )
{
//直接得到当前分量反变换值
m_pcTrQuant->invTransformNxN( tu, compID, piResi, cQP );
}
//code block flag为0
else
{
//反变换值直接填0
piResi.fill( 0 );
}
//===== reconstruction =====
//===== 重建 =====
//flag有效(LMCS可用,且当前为帧内模式或不为帧内模式但CTUFlag有效),且TU的宽×高 > 4
flag = flag && (tu.blocks[compID].width*tu.blocks[compID].height > 4);
//若flag有效,且code block flag不为0 或 使用JCCR,且当前为色度块,且LmcsChromaResidualScaleFlag有效
if (flag && (TU::getCbf(tu, compID) || tu.jointCbCr) && isChroma(compID) && slice.getPicHeader()->getLmcsChromaResidualScaleFlag())
{
//通过scale将色度残差缩放
piResi.scaleSignal(tu.getChromaAdj(), 0, tu.cu->cs->slice->clpRng(compID));
}
//若当前块不使用ISP,或当前为色度块
if( !tu.cu->ispMode || !isLuma( compID ) )
{
//设置当前区域已解码完成
cs.setDecomp( area );
}
//当前块使用ISP,且是亮度分量,且当前块是ISP的第一块
else if( tu.cu->ispMode && isLuma( compID ) && CU::isISPFirst( *tu.cu, tu.blocks[compID], compID ) )
{
//设置当前区域已解码完成
cs.setDecomp( tu.cu->blocks[compID] );
}
#if REUSE_CU_RESULTS
CompArea tmpArea(COMPONENT_Y, area.chromaFormat, Position(0, 0), area.size());
PelBuf tmpPred;
#endif
//若LMCS使能标志有效,且CTU标志有效 或 为帧内模式,且当前为LUMA分量
if (slice.getLmcsEnabledFlag() && (m_pcReshape->getCTUFlag() || slice.isIntra()) && compID == COMPONENT_Y)
{
//若再次使用CU结果
#if REUSE_CU_RESULTS
{
tmpPred = m_tmpStorageLCU->getBuf(tmpArea);
//将预测值copy一份至tmpPred
tmpPred.copyFrom(piPred);
}
#endif
}
//保持预测和残差信号
#if KEEP_PRED_AND_RESI_SIGNALS
//利用预测和残差信号重建,将重建结果放至pReco
pReco.reconstruct( piPred, piResi, tu.cu->cs->slice->clpRng( compID ) );
//不保持预测和残差信号
#else
//利用预测和残差信号重建,将重建结果覆盖piPred
piPred.reconstruct( piPred, piResi, tu.cu->cs->slice->clpRng( compID ) );
#endif
//若不保持预测和残差信号
#if !KEEP_PRED_AND_RESI_SIGNALS
//将重建结果从piPred复制到pReco
pReco.copyFrom( piPred );
#endif
//若LMCS使能标志有效,且CTU标志有效 或 为帧内模式,且当前为LUMA分量
if (slice.getLmcsEnabledFlag() && (m_pcReshape->getCTUFlag() || slice.isIntra()) && compID == COMPONENT_Y)
{
//若再次使用CU结果
#if REUSE_CU_RESULTS
{
//piPred保存原始预测信息
piPred.copyFrom(tmpPred);
}
#endif
}
//若再次使用CU结果
#if REUSE_CU_RESULTS
//将当前重建值和预测值记录
if( cs.pcv->isEncoder )
{
cs.picture->getRecoBuf( area ).copyFrom( pReco );
cs.picture->getPredBuf(area).copyFrom(piPred);
}
#endif
}
二、一些解释
(1)ISP中变换尺寸和预测尺寸不同的情况
这种情况的单独处理是在O0106提案中提出的:
In previous meetings and in the JVET reflector, some experts have raised concerns regarding the handling of 1xN and 2xN subblocks of ISP. It has been mentioned that dependence of 1xN/2xN subblocks on the reconstructed values of preceding 1xN/2xN subblocks of the coding block may result in implementation issues. There was expressed desire to remove this reconstruction dependence.
在以前的会议中,一些专家对ISP的1xN和2xN子块的处理提出了担忧。 已经提到,1×N / 2×N个子块对编码块的先前1×N / 2×N个子块的重构值的依赖性可能导致实现问题。 有人表示希望消除这种重建依赖。
The proposed test removes the dependence of 1xN/2xN subblock prediction on the reconstructed values of previously decoded 1xN/2xN subblocks of the coding block; the minimum width of prediction for subblocks is proposed to be set as four samples. There is no change in the transform block sizes. Figure 1 shows how an 8xN (N > 4) coding block that is coded using ISP with vertical split is split into two prediction regions each of size 4xN and four transforms of size 2xN. Figure 2 shows how a 4xN coding block that is coded using ISP with vertical split is predicted using the full 4xN block; four transform each of 1xN is used.
提案提出的测试消除了1xN / 2xN子块预测对编码块先前解码的1xN / 2xN子块的重构值的依赖性; 建议将子块的最小预测宽度设置为四个采样值。变换块大小没有变化。图1显示了如何将使用垂直分割的ISP编码的8xN(N> 4)编码块拆分为两个大小分别为4xN的预测区域和四个大小为2xN的变换区域。 图2显示了如何将具有垂直分割ISP编码的4xN编码块完整地用于预测;而使用4个1xN变换区域。
下表总结了用于预测和变换大小的提案建议分区大小;对于其他块大小,预测和变换大小与当前为ISP定义的大小相同。 VTM-5.0的更改以黄色突出显示。
(2)JCCR(Joint coding of chroma residuals 色度联合残差编码)
JCCR主要由TU层的两个标志:tu.cbf[COMPONENT_Cb] 和 tu.cbf[COMPONENT_Cr] 控制,是利用Cb和Cr的残差往往在实际过程中呈现一定的关系。
如下表所示,JCCR模式有3个子模式,由tu.cbf[COMPONENT_Cb] 和 tu.cbf[COMPONENT_Cr] 控制。
tu.cbf[COMPONENT_Cb] | tu.cbf[COMPONENT_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 |
(3)LMCS(Luma mapping with chroma scaling / in-loop reshaper)
LMCS位于去方块滤波前,该技术用于SDR、HDR视频中。LMCS主要分为两部分:基于自适应分段线性模型的环内亮度映射、基于亮度的色度残差缩放。
xIntraRecBlk函数代码涉及到的LMCS内容是基于亮度的色度残差缩放部分,利用函数calculateChromaAdjVpduNei获取色度残差的缩放scale,利用函数scaleSignal完成对色度残差的缩放。
上一篇:H.266/VVC-VTM代码学习-帧内预测15-解码端解压缩decompressCtu函数及xReconIntraQT调用xIntraRecQT函数完成帧内预测重建
下一篇:H.266/VVC-VTM代码学习-帧内预测17-initIntraPatternChTypeISP函数初始化ISP的帧内预测