H.266/VVC专栏传送
上一篇:H.266/VVC-VTM代码学习-帧内预测09-色度预测的CCLM模式(1)xGetLumaRecPixels函数对重建亮度块下采样
下一篇:H.266/VVC-VTM代码学习-帧内预测11-编码端亮度块模式选择estIntraPredLumaQT函数
前言
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.函数代码与分析
本篇博客涉及的xGetLMParameters函数主要内容集中在根据下采样的重建亮度块求解CCLM参数α与β。
void IntraPrediction::xGetLMParameters(const PredictionUnit &pu, const ComponentID compID,
const CompArea &chromaArea,
int &a, int &b, int &iShift)
{
CHECK(compID == COMPONENT_Y, "");
//色度块宽度
const SizeType cWidth = chromaArea.width;
//色度块高度
const SizeType cHeight = chromaArea.height;
//左上角位置
const Position posLT = chromaArea;
CodingStructure & cs = *(pu.cs);
const CodingUnit &cu = *(pu.cu);
const SPS & sps = *cs.sps;
const uint32_t tuWidth = chromaArea.width;
const uint32_t tuHeight = chromaArea.height;
const ChromaFormat nChromaFormat = sps.getChromaFormatIdc();
//基本单元尺寸
const int baseUnitSize = 1 << MIN_CU_LOG2;
//基本单元宽
const int unitWidth = baseUnitSize >> getComponentScaleX(chromaArea.compID, nChromaFormat);
//基本单元高
const int unitHeight = baseUnitSize >> getComponentScaleY(chromaArea.compID, nChromaFormat);
//正上方单元数
const int tuWidthInUnits = tuWidth / unitWidth;
//正左侧单元数
const int tuHeightInUnits = tuHeight / unitHeight;
//正上方单元数
const int aboveUnits = tuWidthInUnits;
//正左侧单元数
const int leftUnits = tuHeightInUnits;
//上方总采样点数
int topTemplateSampNum = 2 * cWidth;
//左侧总采样点数
int leftTemplateSampNum = 2 * cHeight;
assert(m_topRefLength >= topTemplateSampNum);
assert(m_leftRefLength >= leftTemplateSampNum);
//上方总单元数
int totalAboveUnits = (topTemplateSampNum + (unitWidth - 1)) / unitWidth;
//左侧总单元数
int totalLeftUnits = (leftTemplateSampNum + (unitHeight - 1)) / unitHeight;
//总单元数
int totalUnits = totalLeftUnits + totalAboveUnits + 1;
//右上单元数
int aboveRightUnits = totalAboveUnits - aboveUnits;
//左下单元数
int leftBelowUnits = totalLeftUnits - leftUnits;
//右上方可用单元数
int avaiAboveRightUnits = 0;
//左下方可用单元数
int avaiLeftBelowUnits = 0;
//正上方可用单元数
int avaiAboveUnits = 0;
//正左侧可用单元数
int avaiLeftUnits = 0;
//当前色度预测模式
int curChromaMode = pu.intraDir[1];
bool neighborFlags[4 * MAX_NUM_PART_IDXS_IN_CTU_WIDTH + 1];
memset(neighborFlags, 0, totalUnits);
bool aboveAvailable, leftAvailable;
//正上方可用单元数
int availableUnit =
isAboveAvailable(cu, CHANNEL_TYPE_CHROMA, posLT, aboveUnits, unitWidth,
(neighborFlags + leftUnits + leftBelowUnits + 1));
//正上方是否全部可用
aboveAvailable = availableUnit == tuWidthInUnits;
//正左侧可用单元数
availableUnit =
isLeftAvailable(cu, CHANNEL_TYPE_CHROMA, posLT, leftUnits, unitHeight,
(neighborFlags + leftUnits + leftBelowUnits - 1));
//正左侧是否全部可用
leftAvailable = availableUnit == tuHeightInUnits;
//如果正左侧单元全部可用
if (leftAvailable)
{
//正左侧可用单元数 = 正左侧单元总数
avaiLeftUnits = tuHeightInUnits;
//计算左下方可用单元数
avaiLeftBelowUnits = isBelowLeftAvailable(cu, CHANNEL_TYPE_CHROMA, chromaArea.bottomLeftComp(chromaArea.compID), leftBelowUnits, unitHeight, (neighborFlags + leftBelowUnits - 1));
}
//如果正上方单元全部可用
if (aboveAvailable)
{
//正上方可用单元数 = 正上方单元总数
avaiAboveUnits = tuWidthInUnits;
//计算右上方可用单元数
avaiAboveRightUnits = isAboveRightAvailable(cu, CHANNEL_TYPE_CHROMA, chromaArea.topRightComp(chromaArea.compID), aboveRightUnits, unitWidth, (neighborFlags + leftUnits + leftBelowUnits + aboveUnits + 1));
}
Pel *srcColor0, *curChroma0;
int srcStride;
PelBuf temp;
//如果为LM_T和LM_L模式
if ((curChromaMode == MDLM_L_IDX) || (curChromaMode == MDLM_T_IDX))
{
//宽度为2倍当前块宽度
srcStride = 2 * MAX_CU_SIZE + 1;
temp = PelBuf(m_pMdlmTemp + srcStride + 1, srcStride, Size(chromaArea));
}
else
{
//LM模式宽度为1倍当前块宽度
srcStride = MAX_CU_SIZE + 1;
temp = PelBuf(m_piTemp + srcStride + 1, srcStride, Size(chromaArea));
}
//下采样结果的非参考行第一个像素
srcColor0 = temp.bufAt(0, 0);
//获得色度块参考像素
curChroma0 = getPredictorPtr(compID);
//位深
unsigned internalBitDepth = sps.getBitDepth(CHANNEL_TYPE_CHROMA);
//下标0表示两个亮度较小点的横坐标均值,下标1表示两个亮度较小点的纵坐标均值
int minLuma[2] = { MAX_INT, 0 };
//下标0表示两个亮度较大点的横坐标均值,下标1表示两个亮度较大点的纵坐标均值
int maxLuma[2] = { -MAX_INT, 0 };
//这里对应亮度下采样后得到的buffer,减去srcStride使得指向-1行
Pel *src = srcColor0 - srcStride;
//上方的实际采样点数
int actualTopTemplateSampNum = 0;
//左侧的实际采样点数
int actualLeftTemplateSampNum = 0;
//LM_T模式下,用上*2
if (curChromaMode == MDLM_T_IDX)
{
//不用左侧参考单元
leftAvailable = 0;
//右上可用单元数选择 将左侧单元置于上侧的单元数 和 原右上可用单元数 中的较小值
avaiAboveRightUnits = avaiAboveRightUnits > (cHeight/unitWidth) ? cHeight/unitWidth : avaiAboveRightUnits;
//上方实际采样点数 = 单元宽*(右上可用单元数+正上可用单元数)
actualTopTemplateSampNum = unitWidth*(avaiAboveUnits + avaiAboveRightUnits);
}
//LM_T模式下,用左*2
else if (curChromaMode == MDLM_L_IDX)
{
//不用上方参考单元
aboveAvailable = 0;
//左下可用单元数选择 将上方单元至于左侧的单元数 和 原左下可用单元数 中的较小值
avaiLeftBelowUnits = avaiLeftBelowUnits > (cWidth/unitHeight) ? cWidth/unitHeight : avaiLeftBelowUnits;
//左侧实际采样点数 = 单元高*(正左可用单元数+左下可用单元数)
actualLeftTemplateSampNum = unitHeight*(avaiLeftUnits + avaiLeftBelowUnits);
}
//LM模式下,用左和上
else if (curChromaMode == LM_CHROMA_IDX)
{
//上方实际采样点数为当前块宽度
actualTopTemplateSampNum = cWidth;
//左侧实际采样点数为当前块高度
actualLeftTemplateSampNum = cHeight;
}
/************取点,根据一一对应的亮度和色度找到最大最小***************/
//存储起始位置
//0:Above, 1: Left
int startPos[2];
//存储步长
int pickStep[2];
//若左侧不可用则上方选4个
int aboveIs4 = leftAvailable ? 0 : 1;
//若上方不可用则左侧选4个
int leftIs4 = aboveAvailable ? 0 : 1;
//上方起始位置
startPos[0] = actualTopTemplateSampNum >> (2 + aboveIs4);
//上方步长
pickStep[0] = std::max(1, actualTopTemplateSampNum >> (1 + aboveIs4));
//左侧起始位置
startPos[1] = actualLeftTemplateSampNum >> (2 + leftIs4);
//左侧步长
pickStep[1] = std::max(1, actualLeftTemplateSampNum >> (1 + leftIs4));
//所选亮度像素
Pel selectLumaPix[4] = { 0, 0, 0, 0 };
//所选色度像素
Pel selectChromaPix[4] = { 0, 0, 0, 0 };
//分别保存上一行和左一列中选出的样本点数
int cntT, cntL;
//初始化为0
cntT = cntL = 0;
//用于遍历,最后保存选出的样本总数
int cnt = 0;
//若上方可用
//根据起始位置和步长选择上方亮度和色度像素
if (aboveAvailable)
{
//上侧行选出的样本点数
cntT = std::min(actualTopTemplateSampNum, (1 + aboveIs4) << 1);
//重建亮度指针指向上侧行首地址
src = srcColor0 - srcStride;
//重建色度指针指向上侧行首地址
const Pel *cur = curChroma0 + 1;
//循环选点,分别保存选好的参考像素亮度和色度值
for (int pos = startPos[0]; cnt < cntT; pos += pickStep[0], cnt++)
{
selectLumaPix[cnt] = src[pos];
selectChromaPix[cnt] = cur[pos];
}
}
//若左侧可用
//根据起始位置和步长选择左侧亮度和色度像素
if (leftAvailable)
{
//左侧列选出的样本点数
cntL = std::min(actualLeftTemplateSampNum, ( 1 + leftIs4 ) << 1 );
//重建亮度指针指向左侧列首地址
src = srcColor0 - 1;
//重建色度指针指向左侧列首地址
const Pel *cur = curChroma0 + m_refBufferStride[compID] + 1;
//循环选点,分别保存选好的参考像素亮度和色度值
for (int pos = startPos[1], cnt = 0; cnt < cntL; pos += pickStep[1], cnt++)
{
selectLumaPix[cnt + cntT] = src[pos * srcStride];
selectChromaPix[cnt + cntT] = cur[pos];
}
}
//总点数,4 or 2 or 0
cnt = cntL + cntT;
//若总共选到2个点,则将两点扩充为4点,实际是复制,即将0 1 2 3分别取原1 0 1 0的值
if (cnt == 2)
{
selectLumaPix[3] = selectLumaPix[0]; selectChromaPix[3] = selectChromaPix[0];
selectLumaPix[2] = selectLumaPix[1]; selectChromaPix[2] = selectChromaPix[1];
selectLumaPix[0] = selectLumaPix[1]; selectChromaPix[0] = selectChromaPix[1];
selectLumaPix[1] = selectLumaPix[3]; selectChromaPix[1] = selectChromaPix[3];
}
int minGrpIdx[2] = { 0, 2 };
int maxGrpIdx[2] = { 1, 3 };
//保存两个亮度较小点
int *tmpMinGrp = minGrpIdx;
//保存两个亮度较大点
int *tmpMaxGrp = maxGrpIdx;
//作比较,选出两个亮度较小点和两个亮度较大点
if (selectLumaPix[tmpMinGrp[0]] > selectLumaPix[tmpMinGrp[1]])
{
std::swap(tmpMinGrp[0], tmpMinGrp[1]);
}
if (selectLumaPix[tmpMaxGrp[0]] > selectLumaPix[tmpMaxGrp[1]])
{
std::swap(tmpMaxGrp[0], tmpMaxGrp[1]);
}
if (selectLumaPix[tmpMinGrp[0]] > selectLumaPix[tmpMaxGrp[1]])
{
std::swap(tmpMinGrp, tmpMaxGrp);
}
if (selectLumaPix[tmpMinGrp[1]] > selectLumaPix[tmpMaxGrp[0]])
{
std::swap(tmpMinGrp[1], tmpMaxGrp[0]);
}
//两个较小点亮度求平均
minLuma[0] = (selectLumaPix[tmpMinGrp[0]] + selectLumaPix[tmpMinGrp[1]] + 1 )>>1;
//两个较小点色度求平均
minLuma[1] = (selectChromaPix[tmpMinGrp[0]] + selectChromaPix[tmpMinGrp[1]] + 1) >> 1;
//两个较大点亮度求平均
maxLuma[0] = (selectLumaPix[tmpMaxGrp[0]] + selectLumaPix[tmpMaxGrp[1]] + 1 )>>1;
//两个较大点色度求平均
maxLuma[1] = (selectChromaPix[tmpMaxGrp[0]] + selectChromaPix[tmpMaxGrp[1]] + 1) >> 1;
//左侧可用或上方可用
if (leftAvailable || aboveAvailable)
{
//两个平均亮度的差值
int diff = maxLuma[0] - minLuma[0];
if (diff > 0)
{
//两个平均色度的差值
int diffC = maxLuma[1] - minLuma[1];
//亮度差值的对数
int x = floorLog2( diff );
static const uint8_t DivSigTable[1 << 4] = {
// 4bit significands - 8 ( MSB is omitted )
0, 7, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 1, 1, 0
};
int normDiff = (diff << 4 >> x) & 15;
int v = DivSigTable[normDiff] | 8;
x += normDiff != 0;
int y = floorLog2( abs( diffC ) ) + 1;
//四舍五入偏移量
int add = 1 << y >> 1;
a = (diffC * v + add) >> y;
iShift = 3 + x - y;
if ( iShift < 1 )
{
iShift = 1;
a = ( (a == 0)? 0: (a < 0)? -15 : 15 ); // a=Sign(a)*15
}
b = minLuma[1] - ((a * minLuma[0]) >> iShift);
}
//最大亮度 == 最小亮度
else
{
a = 0;
b = minLuma[1];
iShift = 0;
}
}
//上方和左侧都不可用
else
{
a = 0;
b = 1 << (internalBitDepth - 1);
iShift = 0;
}
}
2.逻辑结构
(1)取点
- 当前为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)
- 若取得点数为2,则通过复制将点数扩充为4个点
(2)计算
- 通过比较得到四个点中亮度较大的两个点和亮度较小的两个点
- 对亮度较大的两点和亮度较小的两点分别求亮度均值和色度均值
- 求两个亮度均值的差值、两个色度均值的差值
- 若两个亮度均值差值为0,则α=0,β=两个亮度较小点的色度均值
- 若上方和左侧都不可用,则α=0,β=像素最大值一半
- 若左侧可用或上方可用,两个亮度均值差值大于0,则α=色度均值差值/亮度均值差值,β=两个亮度较小点的色度均值 - α * 两个亮度较小点的亮度均值
上一篇:H.266/VVC-VTM代码学习-帧内预测09-色度预测的CCLM模式(1)xGetLumaRecPixels函数对重建亮度块下采样
下一篇:H.266/VVC-VTM代码学习-帧内预测11-编码端亮度块模式选择estIntraPredLumaQT函数