H.266/VVC专栏传送
上一篇:H.266/VVC-VTM代码学习-帧内预测08-MIP模式(2)predBlock函数完成Mip预测
下一篇:H.266/VVC-VTM代码学习-帧内预测10-色度预测的CCLM模式(2)xGetLMParameters函数求解CCLM参数α和β
前言
VTM是H.266/VVC视频编码标准的参考软件,研究VTM代码给研究人员解释了VVC编码标准的详细标准规范与细节。
本文是笔者对VTM代码的一点学习记录,成文于笔者刚开始接触VVC期间,期间很多概念和理论框架还很不成熟,若文中存在错误欢迎批评指正,也欢迎广大视频编码学习者沟通交流、共同进步。
VTM代码的下载及编译请参考博文:
【视频编码学习】H.266/VVC参考软件VTM配置运行(VTM-6.0版本)
本文涉及的代码主要存在于工程下的Lib\CommonLib\IntraPrediction.cpp文件中。
一、CCLM模式简述
CCLM技术,即Cross-Component Linear Model,交叉分量线性模型,是假设同一个编码块的色度像素与对应的亮度像素值有线性关系,故CCLM创建了一个从重建亮度像素值生成对应色度预测值的线性模型。
受YUV视频类型影响,Luma和对应Chroma块尺寸不同,故CCLM先将重建的Luma块根据YUV视频类型进行相应的下采样处理。
最终的预测模型参数α和β要通过下采样后的亮度重建像素中的四个像素亮度和对应色度值得到,这四个点的位置与当前应用的CCLM模式有关:
CCLM共有三个预测模式可供选择,分别为LM(模式号:67)、LM-L(模式号:68)、LM-T(模式号:69)。
- 当前为LM模式且上方和左侧参考单元均可用时,四个点分别为(W/4, -1), (3W/4, -1), (-1, H/4), (-1, 3H/4)
- 当前为LM-T模式且上方参考单元可用时,四个点分别为((W+H)/8, -1), (3(W+H)/8, -1), (5(W+H)/8, -1), (7(W+H)/8, -1)
- 当前为LM-L模式且左侧参考单元可用时,四个点分别为(-1, (H+W)/8), (-1, 3(H+W)/8), (-1, 5(H+W)/8), (-1, 7(H+W)/8)
经过四轮比较,将选出的四个点分为亮度较大的两个点和亮度较小的两个点,求得亮度较大的两个点的亮度值分别为 x A 0 x_A^0 xA0和 x A 1 x_A^1 xA1,色度值分别为 y A 0 y_A^0 yA0和 y A 1 y_A^1 yA1。亮度较小的两个点的亮度值分别为 x B 0 x_B^0 xB0和 x B 1 x_B^1 xB1,色度值分别为 y B 0 y_B^0 yB0和 y B 1 y_B^1 yB1。
则求解α和β的公式如下图所示。
求解出α和β后,即可对下采样后的重建亮度像素进行线性映射,得到对应色度块的预测值。
二、主要函数
1.函数代码与分析
本篇博客涉及的xGetLumaRecPixels函数主要内容集中在对重构的亮度块根据YUV格式进行相应的下采样的功能。
void IntraPrediction::xGetLumaRecPixels(const PredictionUnit &pu, CompArea chromaArea)
{
//下采样后的一行
int iDstStride = 0;
//下采样后的缓存
Pel* pDst0 = 0;
//当前色度块的模式
int curChromaMode = pu.intraDir[1];
//若当前色度块模式为MDLM_L或MDLM_T
if ((curChromaMode == MDLM_L_IDX) || (curChromaMode == MDLM_T_IDX))
{
iDstStride = 2 * MAX_CU_SIZE + 1;
pDst0 = m_pMdlmTemp + iDstStride + 1;
}
//若当前色度块模式为LM
else
{
iDstStride = MAX_CU_SIZE + 1;
pDst0 = m_piTemp + iDstStride + 1;
}
//assert 420 chroma subsampling
CompArea lumaArea = CompArea( COMPONENT_Y, pu.chromaFormat, chromaArea.lumaPos(), recalcSize( pu.chromaFormat, CHANNEL_TYPE_CHROMA, CHANNEL_TYPE_LUMA, chromaArea.size() ) );//needed for correct pos/size (4x4 Tus)
CHECK(lumaArea.width == chromaArea.width && CHROMA_444 != pu.chromaFormat, "");
CHECK(lumaArea.height == chromaArea.height && CHROMA_444 != pu.chromaFormat && CHROMA_422 != pu.chromaFormat, "");
//当前色度块宽度
const SizeType uiCWidth = chromaArea.width;
//当前色度块高度
const SizeType uiCHeight = chromaArea.height;
//获得亮度重建像素
const CPelBuf Src = pu.cs->picture->getRecoBuf( lumaArea );
//下采样前亮度重建像素缓存
Pel const* pRecSrc0 = Src.bufAt( 0, 0 );
//亮度重建块的一行
int iRecStride = Src.stride;
//return (isLuma(id) || (fmt==CHROMA_444)) ? 0 : 1
//是否不是YUV444
int logSubWidthC = getChannelTypeScaleX(CHANNEL_TYPE_CHROMA, pu.chromaFormat);
//return (isLuma(id) || (fmt!=CHROMA_420)) ? 0 : 1
//是否是YUV420
int logSubHeightC = getChannelTypeScaleY(CHANNEL_TYPE_CHROMA, pu.chromaFormat);
//非420:亮度块宽度 420:亮度块宽度×2
//因为420色度垂直分辨率下降到一半
int iRecStride2 = iRecStride << logSubHeightC;
const CodingUnit& lumaCU = isChroma( pu.chType ) ? *pu.cs->picture->cs->getCU( lumaArea.pos(), CH_L ) : *pu.cu;
const CodingUnit& cu = *pu.cu;
const CompArea& area = isChroma( pu.chType ) ? chromaArea : lumaArea;
//TU宽
const uint32_t uiTuWidth = area.width;
//TU高
const uint32_t uiTuHeight = area.height;
//4
int iBaseUnitSize = ( 1 << MIN_CU_LOG2 );
//基本单元宽度
const int iUnitWidth = iBaseUnitSize >> getComponentScaleX( area.compID, area.chromaFormat );
//基本单元高度
const int iUnitHeight = iBaseUnitSize >> getComponentScaleY(area.compID, area.chromaFormat);
//TU宽有多少基本单元
const int iTUWidthInUnits = uiTuWidth / iUnitWidth;
//TU高有多少基本单元
const int iTUHeightInUnits = uiTuHeight / iUnitHeight;
//正上面有几个基本单元
const int iAboveUnits = iTUWidthInUnits;
//正左边有几个基本单元
const int iLeftUnits = iTUHeightInUnits;
//色度基本单元宽
const int chromaUnitWidth = iBaseUnitSize >> getComponentScaleX(COMPONENT_Cb, area.chromaFormat);
//色度基本单元高
const int chromaUnitHeight = iBaseUnitSize >> getComponentScaleY(COMPONENT_Cb, area.chromaFormat);
//上方(包括右上方)采样点数
const int topTemplateSampNum = 2 * uiCWidth;
//左侧(包括左下方)采样点数
const int leftTemplateSampNum = 2 * uiCHeight;
assert(m_topRefLength >= topTemplateSampNum);
assert(m_leftRefLength >= leftTemplateSampNum);
//上面(包括右上方)用几个基本单元
const int totalAboveUnits = (topTemplateSampNum + (chromaUnitWidth - 1)) / chromaUnitWidth;
//左侧(包括左下方)用几个基本单元
const int totalLeftUnits = (leftTemplateSampNum + (chromaUnitHeight - 1)) / chromaUnitHeight;
//总单元数
const int totalUnits = totalLeftUnits + totalAboveUnits + 1;
//右上用几个基本单元
const int aboveRightUnits = totalAboveUnits - iAboveUnits;
//左下用几个基本单元
const int leftBelowUnits = totalLeftUnits - iLeftUnits;
//右上可用单元数
int avaiAboveRightUnits = 0;
//左下可用单元数
int avaiLeftBelowUnits = 0;
bool bNeighborFlags[4 * MAX_NUM_PART_IDXS_IN_CTU_WIDTH + 1];
memset(bNeighborFlags, 0, totalUnits);
bool aboveIsAvailable, leftIsAvailable;
//正左侧可用单元数
int availlableUnit = isLeftAvailable(isChroma(pu.chType) ? cu : lumaCU, toChannelType(area.compID), area.pos(),
iLeftUnits, iUnitHeight, (bNeighborFlags + iLeftUnits + leftBelowUnits - 1));
//正左侧单元是否全部可用
leftIsAvailable = availlableUnit == iTUHeightInUnits;
//正上方可用单元数
availlableUnit = isAboveAvailable(isChroma(pu.chType) ? cu : lumaCU, toChannelType(area.compID), area.pos(),
iAboveUnits, iUnitWidth, (bNeighborFlags + iLeftUnits + leftBelowUnits + 1));
//正上方单元是否全部可用
aboveIsAvailable = availlableUnit == iTUWidthInUnits;
//若正左侧可用,则统计左下方可用单元数
if (leftIsAvailable)
avaiLeftBelowUnits = isBelowLeftAvailable(isChroma(pu.chType) ? cu : lumaCU, toChannelType(area.compID), area.bottomLeftComp(area.compID), leftBelowUnits, iUnitHeight, (bNeighborFlags + leftBelowUnits - 1));
}
//若正上方可用,则统计右上方可用单元数
if (aboveIsAvailable)
{
avaiAboveRightUnits = isAboveRightAvailable(isChroma(pu.chType) ? cu : lumaCU, toChannelType(area.compID), area.topRightComp(area.compID), aboveRightUnits, iUnitWidth, (bNeighborFlags + iLeftUnits + leftBelowUnits + iAboveUnits + 1));
}
Pel* pDst = nullptr;
Pel const* piSrc = nullptr;
//是否是CTU的第一行
bool isFirstRowOfCtu = (lumaArea.y & ((pu.cs->sps)->getCTUSize() - 1)) == 0;
//如果正上方全部可用
if (aboveIsAvailable)
{
//移动到下采样后缓存的参考行,即-1行
pDst = pDst0 - iDstStride;
int addedAboveRight = 0;
//如果当前模式是LM-L或者LM-T模式
if ((curChromaMode == MDLM_L_IDX) || (curChromaMode == MDLM_T_IDX))
{
//将右上方加入
addedAboveRight = avaiAboveRightUnits*chromaUnitWidth;
}
//遍历整个上方(LM-T和LM-L包括右上)
for (int i = 0; i < uiCWidth + addedAboveRight; i++)
{
//是否正左侧不可用且当前i==0
const bool leftPadding = i == 0 && !leftIsAvailable;
//如果是YUV444采样率
if (pu.chromaFormat == CHROMA_444)
{
//将指针移动到上方参考行
piSrc = pRecSrc0 - iRecStride;
//直接赋值
pDst[i] = piSrc[i];
}
//如果是CTU的第一行(420采样率下)
else if (isFirstRowOfCtu)
{
//将指针移动到上方参考行
piSrc = pRecSrc0 - iRecStride;
//121下采样
pDst[i] = (piSrc[2 * i] * 2 + piSrc[2 * i - (leftPadding ? 0 : 1)] + piSrc[2 * i + 1] + 2) >> 2;
}
//如果是YUV422采样率
else if (pu.chromaFormat == CHROMA_422)
{
//将指针移动到上方参考行
piSrc = pRecSrc0 - iRecStride2;
//121下采样
int s = 2;
s += piSrc[2 * i] * 2;
s += piSrc[2 * i - (leftPadding ? 0 : 1)];
s += piSrc[2 * i + 1];
pDst[i] = s >> 2;
}
//m_verCollocatedChromaFlag(420采样率下)
else if (pu.cs->sps->getCclmCollocatedChromaFlag())
{
//将指针移动到上方参考行
piSrc = pRecSrc0 - iRecStride2;
//(正上方+正下方+左+右+4*中)/ 8 下采样
int s = 4;
s += piSrc[2 * i - iRecStride];
s += piSrc[2 * i] * 4;
s += piSrc[2 * i - (leftPadding ? 0 : 1)];
s += piSrc[2 * i + 1];
s += piSrc[2 * i + iRecStride];
pDst[i] = s >> 3;
}
//YUV420
else
{
//将指针移动到上方参考行
piSrc = pRecSrc0 - iRecStride2;
//(左+右+2中+左下+右下+2下)/ 8 下采样
int s = 4;
s += piSrc[2 * i] * 2;
s += piSrc[2 * i + 1];
s += piSrc[2 * i - (leftPadding ? 0 : 1)];
s += piSrc[2 * i + iRecStride] * 2;
s += piSrc[2 * i + 1 + iRecStride];
s += piSrc[2 * i + iRecStride - (leftPadding ? 0 : 1)];
pDst[i] = s >> 3;
}
}
}
//如果正左侧全部可用
if (leftIsAvailable)
{
//移动到正左侧第一个位置
pDst = pDst0 - 1;
//444:pRecSrc0 - 1 其他:pRecSrc0 - 2
piSrc = pRecSrc0 - 1 - logSubWidthC;
int addedLeftBelow = 0;
//如果当前模式是LM-L或者LM-T模式
if ((curChromaMode == MDLM_L_IDX) || (curChromaMode == MDLM_T_IDX))
{
//将左下方加入
addedLeftBelow = avaiLeftBelowUnits*chromaUnitHeight;
}
//遍历整个上方(LM-T和LM-L包括左下)
for (int j = 0; j < uiCHeight + addedLeftBelow; j++)
{
//如果是YUV444
if (pu.chromaFormat == CHROMA_444)
{
//直接赋值
pDst[0] = piSrc[0];
}
//如果是YUV422
else if (pu.chromaFormat == CHROMA_422)
{
//121下采样
int s = 2;
s += piSrc[0] * 2;
s += piSrc[-1];
s += piSrc[1];
pDst[0] = s >> 2;
}
//m_verCollocatedChromaFlag(420采样率下)
else if (pu.cs->sps->getCclmCollocatedChromaFlag())
{
//是否正上方不可用且当前j==0
const bool abovePadding = j == 0 && !aboveIsAvailable;
//(上+下+左+右+4中)/8 下采样
int s = 4;
s += piSrc[-(abovePadding ? 0 : iRecStride)];
s += piSrc[0] * 4;
s += piSrc[-1];
s += piSrc[1];
s += piSrc[iRecStride];
pDst[0] = s >> 3;
}
//YUV420
else
{
//(左+右+左下+右下+2中+2下)/8 下采样
int s = 4;
s += piSrc[0] * 2;
s += piSrc[1];
s += piSrc[-1];
s += piSrc[iRecStride] * 2;
s += piSrc[iRecStride + 1];
s += piSrc[iRecStride - 1];
pDst[0] = s >> 3;
}
//移动到当前列下一参考像素
piSrc += iRecStride2;
pDst += iDstStride;
}
}
// inner part from reconstructed picture buffer
//对重建亮度块的里面部分下采样
for( int j = 0; j < uiCHeight; j++ )
{
for( int i = 0; i < uiCWidth; i++ )
{
//YUV444
if (pu.chromaFormat == CHROMA_444)
{
//直接赋值
pDst0[i] = pRecSrc0[i];
}
//YUV422
else if (pu.chromaFormat == CHROMA_422)
{
//是否正左侧不可用且当前i==0
const bool leftPadding = i == 0 && !leftIsAvailable;
//121下采样
int s = 2;
s += pRecSrc0[2 * i] * 2;
s += pRecSrc0[2 * i - (leftPadding ? 0 : 1)];
s += pRecSrc0[2 * i + 1];
pDst0[i] = s >> 2;
}
//m_verCollocatedChromaFlag(420采样率下)
else if (pu.cs->sps->getCclmCollocatedChromaFlag())
{
//是否正左侧不可用且当前i==0
const bool leftPadding = i == 0 && !leftIsAvailable;
//是否正上方不可用且当前j==0
const bool abovePadding = j == 0 && !aboveIsAvailable;
//(上+下+左+右+4中)/8 下采样
int s = 4;
s += pRecSrc0[2 * i - (abovePadding ? 0 : iRecStride)];
s += pRecSrc0[2 * i] * 4;
s += pRecSrc0[2 * i - (leftPadding ? 0 : 1)];
s += pRecSrc0[2 * i + 1];
s += pRecSrc0[2 * i + iRecStride];
pDst0[i] = s >> 3;
}
//YUV420
else
{
CHECK(pu.chromaFormat != CHROMA_420, "Chroma format must be 4:2:0 for vertical filtering");
//是否正左侧不可用且当前i==0
const bool leftPadding = i == 0 && !leftIsAvailable;
//(左+右+左下+右下+2中+2下)/8 下采样
int s = 4;
s += pRecSrc0[2 * i] * 2;
s += pRecSrc0[2 * i + 1];
s += pRecSrc0[2 * i - (leftPadding ? 0 : 1)];
s += pRecSrc0[2 * i + iRecStride] * 2;
s += pRecSrc0[2 * i + 1 + iRecStride];
s += pRecSrc0[2 * i + iRecStride - (leftPadding ? 0 : 1)];
pDst0[i] = s >> 3;
}
}
pDst0 += iDstStride;
pRecSrc0 += iRecStride2;
}
}
2.逻辑结构
(1)上方单元可用时,对上方像素下采样
这部分下采样对于LM-T或LM-L还包括了右上方像素的下采样。主要分为以下几种下采样方式:
- YUV444采样率下,直接赋值对应位置的重建亮度像素
- 如果当前CU是CTU的第一行,则进行121下采样。即 dst(i) = (src(2i-1) + 2src(2i) + src(2i+1)) / 4【若左侧参考单元不可用且当前 i=1,则将式中的src(2i-1)改为src(2*i) 】
- YUV422采样率下,进行121下采样。即 dst(i) = (src(2i-1) + 2src(2i) + src(2i+1)) / 4【若左侧参考单元不可用且当前 i=0,则将式中的src(2i-1)改为src(2*i) 】
- 若m_verCollocatedChromaFlag标志为true,进行(正上方+正下方+左+右+4中)/ 8 下采样。即 dst(i) = (src(2i-stride) + src(2i+stride) + src(2i-1) + src(2i+1) + 4src(2i)) / 8 【若左侧参考单元不可用且当前 i=0,则将式中的src(2i-1)改为src(2*i) 】
- 其他情况下,进行(左+右+2中+左下+右下+2下)/ 8 下采样。即 dst(i) = (src(2i-1) + src(2i+1) + 2src(2i) + src(2i+stride-1) + src(2i+stride+1) + 2src(2i+stride)) / 8【若左侧参考单元不可用且当前 i=0,则将式中的src(2i-1)改为src(2i),将 src(2i+stride-1)改为src(2i+stride)】
(2)左侧单元可用时,对左侧像素下采样
这部分下采样对于LM-T或LM-L还包括了左下方像素的下采样。主要分为以下几种下采样方式:
- YUV444采样率下,直接赋值对应位置的重建亮度像素
- YUV422采样率下,进行121下采样。即 dst(0) = (src(-1) + 2src(0) + src(1)) / 4
- 若m_verCollocatedChromaFlag标志为true,进行(正上方+正下方+左+右+4中)/ 8 下采样。即 dst(0) = (src(-stride) + src(stride) + src(-1) + src(1) + 4src(0)) / 8 【若上方参考单元不可用且当前 j=0,则将式中的src(-stride)改为src(0) 】
- 其他情况下,进行(左+右+2中+左下+右下+2下)/ 8 下采样。即 dst(0) = (src(-1) + src(1) + 2src(0) + src(stride-1) + src(stride+1) + 2src(stride)) / 8
(3)对内部像素下采样
- YUV444采样率下,直接赋值对应位置的重建亮度像素
- 如果当前CU是CTU的第一行,则进行121下采样。即 dst(i) = (src(2i-1) + 2src(2i) + src(2i+1)) / 4【若左侧参考单元不可用且当前 i=1,则将式中的src(2i-1)改为src(2*i) 】
- 若m_verCollocatedChromaFlag标志为true,进行(正上方+正下方+左+右+4中)/ 8 下采样。即 dst(i) = (src(2i-stride) + src(2i+stride) + src(2i-1) + src(2i+1) + 4src(2i)) / 8 【若左侧参考单元不可用且当前 i=0,则将式中的src(2i-1)改为src(2i) ,若上方参考单元不可用且当前 i=0,则将式中的src(2i-stride)改为src(2*i)】
- 其他情况下,进行(左+右+2中+左下+右下+2下)/ 8 下采样。即 dst(i) = (src(2i-1) + src(2i+1) + 2src(2i) + src(2i+stride-1) + src(2i+stride+1) + 2src(2i+stride)) / 8【若左侧参考单元不可用且当前 i=0,则将式中的src(2i-1)改为src(2i),将 src(2i+stride-1)改为src(2i+stride)】
上一篇:H.266/VVC-VTM代码学习-帧内预测08-MIP模式(2)predBlock函数完成Mip预测
下一篇:H.266/VVC-VTM代码学习-帧内预测10-色度预测的CCLM模式(2)xGetLMParameters函数求解CCLM参数α和β