H.266/VVC专栏传送
上一篇:H.266/VVC-VTM代码学习-帧内预测04-Planar模式下计算预测像素值xPredIntraPlanar
下一篇:H.266/VVC-VTM代码学习-帧内预测06-VVC的宽角度预测模式
前言
VTM是H.266/VVC视频编码标准的参考软件,研究VTM代码给研究人员解释了VVC编码标准的详细标准规范与细节。
本文是笔者对VTM代码的一点学习记录,成文于笔者刚开始接触VVC期间,期间很多概念和理论框架还很不成熟,若文中存在错误欢迎批评指正,也欢迎广大视频编码学习者沟通交流、共同进步。
VTM代码的下载及编译请参考博文:
【视频编码学习】H.266/VVC参考软件VTM配置运行(VTM-6.0版本)
本文涉及的代码存在于工程下的/lib/CommonLib/SourceFiles/IntraPrediction.cpp文件中。
一、主要函数
1.函数代码
void IntraPrediction::xPredIntraAng( const CPelBuf &pSrc, PelBuf &pDst, const ChannelType channelType, const ClpRng& clpRng)
{
int width =int(pDst.width);
int height=int(pDst.height);
//predMode >= DIA_IDX(34) 视为类垂直模式
const bool bIsModeVer = m_ipaParam.isModeVer;
//多参考行索引
const int multiRefIdx = m_ipaParam.multiRefIndex;
//signAng * absAng 带正负号的偏移角度
const int intraPredAngle = m_ipaParam.intraPredAngle;
//角度偏移值的倒数*512*32,便于除法运算
const int absInvAngle = m_ipaParam.absInvAngle;
Pel* refMain;
Pel* refSide;
//用于存放上侧参考像素
Pel refAbove[2 * MAX_CU_SIZE + 3 + 33 * MAX_REF_LINE_IDX];
//用于存放左侧参考像素
Pel refLeft [2 * MAX_CU_SIZE + 3 + 33 * MAX_REF_LINE_IDX];
//Initialize the Main and Left reference array.初始化参考像素
//偏移角度值小于0即19~49,不需要用到segmentA和segmentF
//垂直类模式(34~49),需将左侧映射到上侧,水平类模式(19~33),需将上侧映射到左侧
if (intraPredAngle < 0)
{
for (int x = 0; x <= width + 1 + multiRefIdx; x++)
{
//将segmentD、segmentE参考像素放于refAbove[x + height]
//+height是给投影到上方的左侧参考像素留位置
refAbove[x + height] = pSrc.at(x, 0);
}
for (int y = 0; y <= height + 1 + multiRefIdx; y++)
{
//将segmentC、segmentB参考像素放于refAbove[y + width]
//+width是给投影到左侧的上方参考像素留位置
refLeft[y + width] = pSrc.at(y, 1);
}
//主要侧:垂直类为上侧,水平类为左侧
refMain = bIsModeVer ? refAbove + height : refLeft + width;
// 次要侧:水平类为上侧,垂直类为左侧
refSide = bIsModeVer ? refLeft + width : refAbove + height;
//Extend the Main reference to the left.将次要侧参考行投影到主要侧
int sizeSide = bIsModeVer ? height : width;
for (int k = -sizeSide; k <= -1; k++)
{
//(offset/32)是偏移角的tan值,因为投影时主要侧的k位置存放次要测的k/tan(θ)位置
//k/tan(θ)即k/(offset/32)即32*k/offset
//又因为absInvAngle = 32 * 512 /offset
//所以k/tan(θ) = k * absInvAngle / 512
refMain[k] = refSide[std::min((-k * absInvAngle + 256) >> 9, sizeSide)];
}
}
//对于角度偏移值大于0的模式,只需用segmentA和segmentB或segmentE和segmentF
else
{
for (int x = 0; x <= m_topRefLength + multiRefIdx; x++)
{
//将segmentD、segmentE参考像素放于refAbove[x]
refAbove[x] = pSrc.at(x, 0);
}
for (int y = 0; y <= m_leftRefLength + multiRefIdx; y++)
{
//将segmentC、segmentB参考像素放于refAbove[x]
refLeft[y] = pSrc.at(y, 1);
}
//主要侧:垂直类为上侧,水平类为左侧
refMain = bIsModeVer ? refAbove : refLeft;
//次要侧:水平类为上侧,垂直类为左侧
refSide = bIsModeVer ? refLeft : refAbove;
//Extend main reference to right using replication使用复制将主参考行向右扩展
//log2(width / height)
const int log2Ratio = floorLog2(width) - floorLog2(height);
const int s = std::max<int>(0, bIsModeVer ? log2Ratio : -log2Ratio);
//垂直模式:multiRefIdx * width / height + 2
//水平模式:multiRefIdx * height / width + 2
const int maxIndex = (multiRefIdx << s) + 2;
const int refLength = bIsModeVer ? m_topRefLength : m_leftRefLength;
const Pel val = refMain[refLength + multiRefIdx];
//复制segmentE最后像素,填充segmentF
for (int z = 1; z <= maxIndex; z++)
{
refMain[refLength + multiRefIdx + z] = val;
}
}
// swap width/height if we are doing a horizontal mode:水平模式时交换height和width,便于后续操作
if (!bIsModeVer)
{
std::swap(width, height);
}
Pel tempArray[MAX_CU_SIZE * MAX_CU_SIZE];
const int dstStride = bIsModeVer ? pDst.stride : width;
Pel * pDstBuf = bIsModeVer ? pDst.buf : tempArray;
// compensate for line offset in reference line buffers补充多参考行中的偏移
//将指针指向segmentB或segmentE第一个参考像素
refMain += multiRefIdx;
//将指针指向segmentE或segmentB第一个参考像素
refSide += multiRefIdx;
Pel *pDsty = pDstBuf;
//pure vertical or pure horizontal 完全水平或完全垂直模式(18和50),直接用对应参考像素预测
if( intraPredAngle == 0 )
{
for( int y = 0; y < height; y++ )
{
for( int x = 0; x < width; x++ )
{
//直接预测
pDsty[x] = refMain[x + 1];
}
//使用PDPC
if (m_ipaParam.applyPDPC)
{
const int scale = (floorLog2(width) + floorLog2(height) - 2) >> 2;
const Pel topLeft = refMain[0];
const Pel left = refSide[1 + y];
for (int x = 0; x < std::min(3 << scale, width); x++)
{
const int wL = 32 >> (2 * x >> scale);
const Pel val = pDsty[x];
//预测值是原始得到的预测值(主参考行的投影)加上对应次要侧位置和次要侧起始位置的差值
pDsty[x] = ClipPel(val + ((wL * (left - topLeft) + 32) >> 6), clpRng);
}
}
pDsty += dstStride;
}
}
//对除完全水平和完全垂直的模式
else
{
//遍历次要侧,deltaPos为当前遍历点的y*offset
for (int y = 0, deltaPos = intraPredAngle * (1 + multiRefIdx); y<height; y++, deltaPos += intraPredAngle, pDsty += dstStride)
{
//当前像素对应主要侧中的位置,即当前遍历点的(y*offset)/32
const int deltaInt = deltaPos >> 5;
// 计算当前像素对应参考像素的加权因子Ω
const int deltaFract = deltaPos & 31;
//对于除水平、垂直、2、34、66之外的模式进行使用高斯插值滤波器插值处理
//偏移值不是32的整数倍
if ( !isIntegerSlope( abs(intraPredAngle) ) )
{
//亮度块四抽头滤波
if( isLuma(channelType) )
{
const bool useCubicFilter = !m_ipaParam.interpolationFlag;
const TFilterCoeff intraSmoothingFilter[4] = {TFilterCoeff(16 - (deltaFract >> 1)), TFilterCoeff(32 - (deltaFract >> 1)), TFilterCoeff(16 + (deltaFract >> 1)), TFilterCoeff(deltaFract >> 1)};
const TFilterCoeff* const f = (useCubicFilter) ? InterpolationFilter::getChromaFilterTable(deltaFract) : intraSmoothingFilter;
//对主要侧遍历,滤波求出预测值
for (int x = 0; x < width; x++)
{
Pel p[4];
p[0] = refMain[deltaInt + x];
p[1] = refMain[deltaInt + x + 1];
p[2] = refMain[deltaInt + x + 2];
p[3] = refMain[deltaInt + x + 3];
Pel val = (f[0] * p[0] + f[1] * p[1] + f[2] * p[2] + f[3] * p[3] + 32) >> 6;
pDsty[x] = ClipPel(val, clpRng); // always clip even though not always needed
}
}
//色度块线性滤波
else
{
// Do linear filtering线性滤波
for (int x = 0; x < width; x++)
{
Pel p[2];
p[0] = refMain[deltaInt + x + 1];
p[1] = refMain[deltaInt + x + 2];
pDsty[x] = p[0] + ((deltaFract * (p[1] - p[0]) + 16) >> 5);
}
}
}
//对于不插值处理的模式(水平、垂直、2、34、66),直接将滤波后的参考像素作为预测像素
else
{
// Just copy the integer samples
for( int x = 0; x < width; x++ )
{
pDsty[x] = refMain[x + deltaInt + 1];
}
}
if (m_ipaParam.applyPDPC)// 使用PDPC
{
const int scale = m_ipaParam.angularScale;
int invAngleSum = 256;
for (int x = 0; x < std::min(3 << scale, width); x++)
{
invAngleSum += absInvAngle;
int wL = 32 >> (2 * x >> scale);
Pel left = refSide[y + (invAngleSum >> 9) + 1];
//pDsty[x]是插值滤波后的结果,left是其对应次要侧的参考像素(未经过滤波),wL调节left在结果中的权重
pDsty[x] = pDsty[x] + ((wL * (left - pDsty[x]) + 32) >> 6);
}
}
}
}
//Flip the block if this is the horizontal mode 如果是水平模式,反转块。因为水平模式时交换了height和width,是列优先进行处理的,故需要将输出缓存列优先排列
if( !bIsModeVer )
{
for( int y = 0; y < height; y++ )
{
for( int x = 0; x < width; x++ )
{
pDst.at( y, x ) = pDstBuf[x];
}
pDstBuf += dstStride;
}
}
}
2.逻辑结构
1.初始化参考像素:
对于角度偏移值小于0的模式(19-49):
将次要侧参考像素投影至主要侧的对应位置。
对于角度偏移值大于0的模式(2-17和51-66):
将主参考侧向右或向下扩展。
2.水平模式时交换width和height的值,便于后续操作。
3.完全水平或完全垂直模式时,直接投影对应的参考像素作为预测值。若使用PDPC,则在原预测值基础上加上次要侧对应位置像素与次要侧第一个像素的差值。
4.若为非水平、垂直、对角(2、34、66)的模式,亮度块进行四抽头插值滤波,色度块进行线性滤波。若为对角模式,直接将对应的参考像素作为预测值。若使用PDPC,则将未插值滤波的当前点在次要侧对应点像素与原预测值加权结合。
上一篇:H.266/VVC-VTM代码学习-帧内预测04-Planar模式下计算预测像素值xPredIntraPlanar
下一篇:H.266/VVC-VTM代码学习-帧内预测06-VVC的宽角度预测模式