H.266/VVC-VTM代码学习-帧内预测02-获取参考像素并对其滤波xFillReferenceSamples、xFilterReferenceSamples
H.266/VVC专栏传送
上一篇:H.266/VVC-VTM代码学习-帧内预测01-初始化帧内预测参数IntraPrediction::initPredIntraParams
下一篇:H.266/VVC-VTM代码学习-帧内预测03-DC模式下计算预测像素值xPredIntraDc、xGetPredValDc
前言
VTM是H.266/VVC视频编码标准的参考软件,研究VTM代码给研究人员解释了VVC编码标准的详细标准规范与细节。
本文是笔者对VTM代码的一点学习记录,成文于笔者刚开始接触VVC期间,期间很多概念和理论框架还很不成熟,若文中存在错误欢迎批评指正,也欢迎广大视频编码学习者沟通交流、共同进步。
VTM代码的下载及编译请参考博文:
【视频编码学习】H.266/VVC参考软件VTM配置运行(VTM-6.0版本)
本文涉及的代码存在于工程下的/lib/CommonLib/SourceFiles/IntraPrediction.cpp文件中。
一、主要函数
1.函数代码
(1)入口函数 IntraPrediction::initIntraPatternChType
//获取参考像素并对参考像素滤波的入口函数
void IntraPrediction::initIntraPatternChType(const CodingUnit &cu, const CompArea &area, const bool forceRefFilterFlag)
{
CHECK(area.width == 2, "Width of 2 is not supported");
const CodingStructure& cs = *cu.cs;
if (!forceRefFilterFlag)
{
//初始化帧内预测参数
initPredIntraParams(*cu.firstPU, area, *cs.sps);
}
//未滤波的参考像素
Pel *refBufUnfiltered = m_refBuffer[area.compID][PRED_BUF_UNFILTERED];
//滤波后的参考像素
Pel *refBufFiltered = m_refBuffer[area.compID][PRED_BUF_FILTERED];
//-------- step0: 获取参考像素的长度 ---------
//m_leftRefLength = 2H
//m_topRefLength = 2W
setReferenceArrayLengths( area );
//-------- Step 1: 获取参考像素 ---------
xFillReferenceSamples( cs.picture->getRecoBuf( area ), refBufUnfiltered, area, cu );
//-------- Step 2: 参考像素滤波 ---------
if( m_ipaParam.refFilterFlag || forceRefFilterFlag )
{
xFilterReferenceSamples( refBufUnfiltered, refBufFiltered, area, *cs.sps, cu.firstPU->multiRefIdx );
}
}
(2)获取参考像素函数IntraPrediction::xFillReferenceSamples
void IntraPrediction::xFillReferenceSamples( const CPelBuf &recoBuf, Pel* refBufUnfiltered, const CompArea &area, const CodingUnit &cu )
{
const ChannelType chType = toChannelType( area.compID );
const CodingStructure &cs = *cu.cs;
const SPS &sps = *cs.sps;
const PreCalcValues &pcv = *cs.pcv;
//多行预测参考行索引
const int multiRefIdx = (area.compID == COMPONENT_Y) ? cu.firstPU->multiRefIdx : 0;
const int tuWidth = area.width;
const int tuHeight = area.height;
//上方参考像素长度
const int predSize = m_topRefLength;
//左侧参考像素长度
const int predHSize = m_leftRefLength;
//当前参考行长度
const int predStride = predSize + 1 + multiRefIdx;
m_refBufferStride[area.compID] = predStride;
// don't shift on the lowest level (chroma not-split)
const bool noShift = pcv.noChroma2x2 && area.width == 4;
// 单元宽
const int unitWidth = tuWidth <= 2 && cu.ispMode && isLuma(area.compID) ? tuWidth : pcv.minCUWidth >> (noShift ? 0 : getComponentScaleX(area.compID, sps.getChromaFormatIdc()));
//单元高
const int unitHeight = tuHeight <= 2 && cu.ispMode && isLuma(area.compID) ? tuHeight : pcv.minCUHeight >> (noShift ? 0 : getComponentScaleY(area.compID, sps.getChromaFormatIdc()));
//上方参考单元数
const int totalAboveUnits = (predSize + (unitWidth - 1)) / unitWidth;
//左侧参考单元数
const int totalLeftUnits = (predHSize + (unitHeight - 1)) / unitHeight;
//总参考单元数
const int totalUnits = totalAboveUnits + totalLeftUnits + 1; //+1 for top-left
//正上方参考像素单元个数
const int numAboveUnits = std::max<int>( tuWidth / unitWidth, 1 );
//正左侧参考像素单元个数
const int numLeftUnits = std::max<int>( tuHeight / unitHeight, 1 );
//右上方参考像素单元个数
const int numAboveRightUnits = totalAboveUnits - numAboveUnits;
//左下方参考像素单元个数
const int numLeftBelowUnits = totalLeftUnits - numLeftUnits;
CHECK( numAboveUnits <= 0 || numLeftUnits <= 0 || numAboveRightUnits <= 0 || numLeftBelowUnits <= 0, "Size not supported" );
// ----- Step 1: analyze neighborhood -----
// ----- Step 1: 分析边界 -----
const Position posLT = area;
const Position posRT = area.topRight();
const Position posLB = area.bottomLeft();
//记录参考像素块是否可用
bool neighborFlags[4 * MAX_NUM_PART_IDXS_IN_CTU_WIDTH + 1];
//可用参考像素块数
int numIntraNeighbor = 0;
memset( neighborFlags, 0, totalUnits );
//先验证左上角参考像素块有效性
neighborFlags[totalLeftUnits] = isAboveLeftAvailable( cu, chType, posLT );
//若左上角有效,则有效参考像素+1
numIntraNeighbor += neighborFlags[totalLeftUnits] ? 1 : 0;
//验证并统计正上方参考像素有效性
numIntraNeighbor += isAboveAvailable ( cu, chType, posLT, numAboveUnits, unitWidth, (neighborFlags + totalLeftUnits + 1) );
//验证并统计右上方参考像素有效性
numIntraNeighbor += isAboveRightAvailable( cu, chType, posRT, numAboveRightUnits, unitWidth, (neighborFlags + totalLeftUnits + 1 + numAboveUnits) );
//验证并统计正左侧参考像素有效性
numIntraNeighbor += isLeftAvailable ( cu, chType, posLT, numLeftUnits, unitHeight, (neighborFlags + totalLeftUnits - 1) );
//验证并统计左下方参考像素有效性
numIntraNeighbor += isBelowLeftAvailable ( cu, chType, posLB, numLeftBelowUnits, unitHeight, (neighborFlags + totalLeftUnits - 1 - numLeftUnits) );
// ----- Step 2: fill reference samples (depending on neighborhood) -----
//----- Step 2: 填充参考像素 -----
//图像像素原始buffer
const Pel* srcBuf = recoBuf.buf;
//图像原始宽度
const int srcStride = recoBuf.stride;
//未滤波的参考像素
Pel* ptrDst = refBufUnfiltered;
const Pel* ptrSrc;
//设置valueDC为像素最大值的一半
const Pel valueDC = 1 << (sps.getBitDepth( chType ) - 1);
//参考像素都不可用,则填充valueDC
if( numIntraNeighbor == 0 )
{
//参考像素行填充valueDC
for (int j = 0; j <= predSize + multiRefIdx; j++)
{
ptrDst[j] = valueDC;
}
//参考像素列填充valueDC
for (int i = 0; i <= predHSize + multiRefIdx; i++)
{
ptrDst[i + predStride] = valueDC;
}
}
//所有参考像素都可用时,直接复制
else if( numIntraNeighbor == totalUnits )
{
//先将srcBuf后退(1 + multiRefIdx) * srcStride + (1 + multiRefIdx)个单位
//srcBuf指向当前块的左上位置,该操作后ptrSrc指向其上侧参考行的第一个像素位置
ptrSrc = srcBuf - (1 + multiRefIdx) * srcStride - (1 + multiRefIdx);
//将当前块上侧参考像素赋值给ptrDst[i]
for (int j = 0; j <= predSize + multiRefIdx; j++)
{
ptrDst[j] = ptrSrc[j];
}
//将当前块左侧参考像素赋值给ptrDst[i+predStride]
for (int i = 0; i <= predHSize + multiRefIdx; i++)
{
ptrDst[i + predStride] = ptrSrc[i * srcStride];
}
}
//reference samples are partially available 参考像素部分不可用
else
{
//将指针移动到segmentD的第一个
ptrSrc = srcBuf - (1 + multiRefIdx) * srcStride - (1 + multiRefIdx);
ptrDst = refBufUnfiltered;
//如果左上方参考像素单元可用
if (neighborFlags[totalLeftUnits])
{
//填充左上角参考点
ptrDst[0] = ptrSrc[0];
ptrDst[predStride] = ptrSrc[0];
//填充segmentC和segmentD除左上角以外的参考点
for (int i = 1; i <= multiRefIdx; i++)
{
//填充SegmentD中除左上角点之外的参考点
ptrDst[i] = ptrSrc[i];
//填充SegmentC中除左上角点之外的参考点
ptrDst[i + predStride] = ptrSrc[i * srcStride];
}
}
// Fill left & below-left samples if available (downwards) 填充左和左下方参考点
//将指针移动到SegmentB第一个参考点
ptrSrc += (1 + multiRefIdx) * srcStride;
ptrDst += (1 + multiRefIdx) + predStride;
//从上往下遍历左侧参考像素单元
for (int unitIdx = totalLeftUnits - 1; unitIdx > 0; unitIdx--)
{
//若当前参考像素单元可用
if (neighborFlags[unitIdx])
{
//遍历将该参考像素单元复制到prtDst中
for (int i = 0; i < unitHeight; i++)
{
ptrDst[i] = ptrSrc[i * srcStride];
}
}
//将ptrSrc指向下一个参考像素单元起始位置
ptrSrc += unitHeight * srcStride;
//将ptrDst指向下一个参考像素单元起始存放位置
ptrDst += unitHeight;
}
// Fill last below-left sample(s) 填充最后的左下方参考点
//若左下方参考像素可用
if (neighborFlags[0])
{
//左下方参考像素单元高度为unitHeight或unitHeight
int lastSample = (predHSize % unitHeight == 0) ? unitHeight : predHSize % unitHeight;
//遍历赋值左下方参考像素单元
for (int i = 0; i < lastSample; i++)
{
ptrDst[i] = ptrSrc[i * srcStride];
}
}
// Fill above & above-right samples if available (left-to-right) 填充上方和右上方参考点
//将指针移动到segmentE的第一个参考点
ptrSrc = srcBuf - srcStride * (1 + multiRefIdx);
//将指针移动到对应位置
ptrDst = refBufUnfiltered + 1 + multiRefIdx;
//从上方第一个参考像素单元开始遍历整个上侧(包括右上方)的参考像素单元
for (int unitIdx = totalLeftUnits + 1; unitIdx < totalUnits - 1; unitIdx++)
{
//若当前遍历的参考像素单元可用
if (neighborFlags[unitIdx])
{
//循环遍历当前参考像素单元赋值
for (int j = 0; j < unitWidth; j++)
{
ptrDst[j] = ptrSrc[j];
}
}
//指针移动到下一参考像素单元起始位置
ptrSrc += unitWidth;
ptrDst += unitWidth;
}
// Fill last above-right sample(s) 填充最后的右上角像素
//若右上角参考像素可用
if (neighborFlags[totalUnits - 1])
{
int lastSample = (predSize % unitWidth == 0) ? unitWidth : predSize % unitWidth;
//遍历赋值右上方参考像素单元
for (int j = 0; j < lastSample; j++)
{
ptrDst[j] = ptrSrc[j];
}
}
// 若左下角像素不可用,从左下角向上寻找可用的参考像素
// pad from first available down to the last below-left
ptrDst = refBufUnfiltered;
int lastAvailUnit = 0;
if (!neighborFlags[0])
{
int firstAvailUnit = 1;
while (firstAvailUnit < totalUnits && !neighborFlags[firstAvailUnit])
{
firstAvailUnit++;
}
// 第一个可用参考像素的位置
// first available sample
int firstAvailRow = -1;
int firstAvailCol = 0;
//第一个可用参考像素单元在当前单元左侧
if (firstAvailUnit < totalLeftUnits)
{
//第一个可用参考像素所在行
firstAvailRow = (totalLeftUnits - firstAvailUnit) * unitHeight + multiRefIdx;
}
//第一个可用参考像素单元在当前单元左侧最上端
else if (firstAvailUnit == totalLeftUnits)
{
//第一个可用参考像素所在行
firstAvailRow = multiRefIdx;
}
//第一个可用参考像素单元在当前单元上侧
else
{
//第一个参考像素所在列
firstAvailCol = (firstAvailUnit - totalLeftUnits - 1) * unitWidth + 1 + multiRefIdx;
}
//根据位置获取第一个可用参考像素的值
const Pel firstAvailSample = ptrDst[firstAvailRow < 0 ? firstAvailCol : firstAvailRow + predStride];// 根据位置获取第一个可用参考像素的值
// last sample below-left (n.a.)
//左下最后一个参考像素
int lastRow = predHSize + multiRefIdx;
// fill left column
//左下到最后一个可用参考像素,填充最后一个可用参考像素值
for (int i = lastRow; i > firstAvailRow; i--)
{
ptrDst[i + predStride] = firstAvailSample;
}
// fill top row
if (firstAvailCol > 0)
{
//左上到最后一个可用参考像素,填充最后一个可用参考像素值
for (int j = 0; j < firstAvailCol; j++)
{
ptrDst[j] = firstAvailSample;
}
}
//记录在这之前的参考像素已经填充完毕
lastAvailUnit = firstAvailUnit;
}
// pad all other reference samples.填充剩余参考像素
int currUnit = lastAvailUnit + 1;
//遍历剩余参考像素单元
while (currUnit < totalUnits)
{
if (!neighborFlags[currUnit]) // samples not available
{
// last available sample寻找最近可用像素
int lastAvailRow = -1;
int lastAvailCol = 0;
if (lastAvailUnit < totalLeftUnits)
{
lastAvailRow = (totalLeftUnits - lastAvailUnit - 1) * unitHeight + multiRefIdx + 1;
}
else if (lastAvailUnit == totalLeftUnits)
{
lastAvailCol = multiRefIdx;
}
else
{
lastAvailCol = (lastAvailUnit - totalLeftUnits) * unitWidth + multiRefIdx;
}
//得到最后可用参考像素
const Pel lastAvailSample = ptrDst[lastAvailRow < 0 ? lastAvailCol : lastAvailRow + predStride];
// fill current unit with last available sample用最邻近可用像素填充
//若当前遍历参考像素单元在左侧
if (currUnit < totalLeftUnits)
{
//遍历用最后可用参考像素赋值当前单元
for (int i = lastAvailRow - 1; i >= lastAvailRow - unitHeight; i--)
{
ptrDst[i + predStride] = lastAvailSample;
}
}
//若当前遍历参考像素单元在左侧最上方
else if (currUnit == totalLeftUnits)
{
//遍历用最后可用参考像素赋值当前单元
for (int i = 0; i < multiRefIdx + 1; i++)
{
ptrDst[i + predStride] = lastAvailSample;
}
for (int j = 0; j < multiRefIdx + 1; j++)
{
ptrDst[j] = lastAvailSample;
}
}
//若当前遍历参考像素单元在上侧
else
{
int numSamplesInUnit = (currUnit == totalUnits - 1) ? ((predSize % unitWidth == 0) ? unitWidth : predSize % unitWidth) : unitWidth;
//遍历用最后可用参考像素赋值当前单元
for (int j = lastAvailCol + 1; j <= lastAvailCol + numSamplesInUnit; j++)
{
ptrDst[j] = lastAvailSample;
}
}
}
//将该单元设为下次遍历的最后可用参考像素单元
lastAvailUnit = currUnit;
//将指针指向下一单元
currUnit++;
}
}
}
(3)对参考像素进行滤波的函数IntraPrediction::xFilterReferenceSamples
void IntraPrediction::xFilterReferenceSamples(const Pel *refBufUnfiltered, Pel *refBufFiltered, const CompArea &area,
const SPS &sps, int multiRefIdx)
{
if (area.compID != COMPONENT_Y)// 色度用0号参考行
{
multiRefIdx = 0;
}
//上方参考行长度
const int predSize = m_topRefLength + multiRefIdx;
//左侧参考列长度
const int predHSize = m_leftRefLength + multiRefIdx;
//(2w + 1 + multiRefIdx)参考行长度
const size_t predStride = m_refBufferStride[area.compID];
//左上角滤波后像素(当前位置*2 + 下像素 + 右像素) / 4
const Pel topLeft =
(refBufUnfiltered[0] + refBufUnfiltered[1] + refBufUnfiltered[predStride] + refBufUnfiltered[predStride + 1] + 2)
>> 2;
refBufFiltered[0] = topLeft;
//对上侧一整行参考像素进行滤波
for (int i = 1; i < predSize; i++)
{
//[0.25, 0.5, 0.25]滤波
refBufFiltered[i] = (refBufUnfiltered[i - 1] + 2 * refBufUnfiltered[i] + refBufUnfiltered[i + 1] + 2) >> 2;
}
//最右上角像素不进行滤波
refBufFiltered[predSize] = refBufUnfiltered[predSize];
//准备对列进行操作
refBufFiltered += predStride;
refBufUnfiltered += predStride;
// ------------------开始对列参考像素滤波操作------------------------
refBufFiltered[0] = topLeft;
for (int i = 1; i < predHSize; i++)
{
refBufFiltered[i] = (refBufUnfiltered[i - 1] + 2 * refBufUnfiltered[i] + refBufUnfiltered[i + 1] + 2) >> 2;
}
//最左下角像素不进行滤波
refBufFiltered[predHSize] = refBufUnfiltered[predHSize];
}
2.逻辑结构
(1)获取参考像素函数
1.分析边界:确定当前块参考像素长度和参考像素块尺寸、参考像素块数量等参数。求得当前块各个方向的可用参考像素块数量以及总数,并记录各块是否可用。
2.复制填充所有可用参考像素。
3.填充参考像素:
① 若参考像素都不可用,则所有参考像素都以像素最大值的一半填充。
② 若参考像素都可用,则直接复制填充参考像素。
③ 若参考像素部分可用且左下方参考像素可用,则从左下方参考像素开始向上向右填充最邻近的可用像素。
④ 若参考像素部分可用且左下方参考像素不可用,则从左下方参考像素开始向上向右找到第一个可用参考像素,并以其值填充之前的像素。再遍历之后的像素,填充最邻近的可用像素。
(2)对参考像素进行滤波函数
1.对左上角参考像素单独滤波。
2.对上侧参考像素进行滤波,右上角参考像素不进行滤波。
3.对左侧参考像素进行滤波,左下角参考像素不进行滤波。
二、MRL(多参考行)

需要注意的是,MRL 中的参考行实际上不是相邻的关系,index=1 和 index=2 的参考行之间间隔了一行。
上一篇:H.266/VVC-VTM代码学习-帧内预测01-初始化帧内预测参数IntraPrediction::initPredIntraParams
下一篇:H.266/VVC-VTM代码学习-帧内预测03-DC模式下计算预测像素值xPredIntraDc、xGetPredValDc

浙公网安备 33010602011771号