xPredIntraAng函数的作用是对任意大小的块和任意模式,如何将参考像素的值根据其模式的角度填充进每一个像素。
下图是basketball drill的一个16*16的块,其预测模式为10(偏斜上方),得到如下预测结果:
水平模式18可以简单地通过投影的方式得到预测值。但由上图可见,10这个模式并不能简单地通过投影得到像素值。如果单纯看代码,会感觉到这是一个不讲道理的填充方式。
这个函数的最后还进行了近对角模式(2-10和58-66)的PDPC。有关PDPC的内容:
H.266/VVC代码学习20:PDPC技术(predIntraAng)
关于该预测的计算方法,见万帅书P122-123。如果想对这个黑盒进行理解,见朱秀昌书P98-99
VTM4.0中代码如下,可结合注释阅读:
#if JVET_M0102_INTRA_SUBPARTITIONS
void IntraPrediction::xPredIntraAng( const CPelBuf &pSrc, PelBuf &pDst, const ChannelType channelType, const uint32_t dirMode, const ClpRng& clpRng, const SPS& sps,
int multiRefIdx,//参考行索引
const bool useFilteredPredSamples ,
const bool useISP,
const Size cuSize )
#else
void IntraPrediction::xPredIntraAng( const CPelBuf &pSrc, PelBuf &pDst, const ChannelType channelType, const uint32_t dirMode, const ClpRng& clpRng, const SPS& sps
, int multiRefIdx
, const bool useFilteredPredSamples )
#endif
#endif
{
/************************************************ 初始化 *********************************************/
int width =int(pDst.width); //块的宽
int height=int(pDst.height); //块的高
CHECK( !( dirMode > DC_IDX && dirMode < NUM_LUMA_MODE), "Invalid intra dir" );
#if JVET_M0102_INTRA_SUBPARTITIONS
int predMode = useISP ? getWideAngle( cuSize.width, cuSize.height, dirMode ) : getWideAngle( width, height, dirMode );//宽角度模式下的模式编号
#else
int predMode = getWideAngle(width, height, dirMode);
#endif
const bool bIsModeVer = predMode >= DIA_IDX;//大于等于34,视为垂直类型的模式,
const int intraPredAngleMode = (bIsModeVer) ? predMode - VER_IDX : -(predMode - HOR_IDX);//-16~16之间的值,越靠近2和66越大(16),越靠近34越小(-16)
const int absAngMode = abs(intraPredAngleMode);//0~16的值
const int signAng = intraPredAngleMode < 0 ? -1 : 1;//19~49是-1,其余为1
#if HEVC_USE_HOR_VER_PREDFILTERING
const bool edgeFilter = bEnableEdgeFilters && isLuma(channelType) && (width <= MAXIMUM_INTRA_FILTERED_WIDTH) && (height <= MAXIMUM_INTRA_FILTERED_HEIGHT);
#endif
// Set bitshifts and scale the angle parameter to block size 设置位移并将角度参数缩放为块大小
// 设置invAngTable的nudity是为了消除帧内角度预测模式在计算预测值时麻烦的除法运算;
static const int angTable[32] = { 0, 1, 2, 3, 4, 6, 8, 10, 12, 14, 16, 18, 20, 23, 26, 29, 32, 35, 39, 45, 51, 57, 64, 73, 86, 102, 128, 171, 256, 341, 512, 1024 };
static const int invAngTable[32] = { 0, 8192, 4096, 2731, 2048, 1365, 1024, 819, 683, 585, 512, 455, 410, 356, 315, 282, 256, 234, 210, 182, 160, 144, 128, 112, 95, 80, 64, 48, 32, 24, 16, 8 }; // (256 * 32) / Angle
int invAngle = invAngTable[absAngMode]; //当前角度对应的下标值 得到的表格值 的倒数
int absAng = angTable [absAngMode]; //当前角度对应的下标值 得到的表格值
int intraPredAngle = signAng * absAng; //和角度偏移值
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];//左侧参考像素
#if JVET_M0102_INTRA_SUBPARTITIONS
const int whRatio = useISP ? std::max( unsigned( 1 ), cuSize.width / cuSize.height ) : std::max( 1, width / height );//宽高比,最小为1
const int hwRatio = useISP ? std::max( unsigned( 1 ), cuSize.height / cuSize.width ) : std::max( 1, height / width );//高宽比,最小为1
#else
int whRatio = std::max(1, width / height);
int hwRatio = std::max(1, height / width);
#endif
// Initialize the Main and Left reference array.
/************************************** 获取并初始化参考像素(左和上) **********************************/
/********** 模式19-49的操作 **********/
if (intraPredAngle < 0)//模式19~49
{
auto width = int(pDst.width) +1;//扩展的宽
auto height = int(pDst.height)+1;//扩展的高
auto lastIdx = (bIsModeVer ? width : height) + multiRefIdx;//如果是垂直类,用宽度,否则用高度
auto firstIdx = ( ((bIsModeVer ? height : width) -1) * intraPredAngle ) >> 5;
for (int x = 0; x < width + 1 + multiRefIdx; x++)//填充上侧参考像素(这些模式下只需要块正上方那部分即可)
{
refAbove[x + height - 1] = pSrc.at( x, 0 );
}
for (int y = 0; y < height + 1 + multiRefIdx; y++)//填充左侧参考像素(这些模式下只需要块正左方那部分即可)
{
refLeft[y + width - 1] = pSrc.at( 0, y );
}
refMain = (bIsModeVer ? refAbove + height : refLeft + width ) - 1;//主要侧
refSide = (bIsModeVer ? refLeft + width : refAbove + height) - 1;//次要侧
// Extend the Main reference to the left. 把主参考行放到左边(实际上不用左和上两部分预测,只用上面一排)
int invAngleSum = 128; // rounding for (shift by 8)
for( int k = -1; k > firstIdx; k-- )//即数组负数标号的部分填充左边的值
{
invAngleSum += invAngle;
refMain[k] = refSide[invAngleSum>>8];
}
refMain[lastIdx] = refMain[lastIdx-1]; //处理最边界:填充左下角的值
refMain[firstIdx] = refMain[firstIdx+1]; //处理最边界:填充右上角的值
}
/****** 模式2-18 和 50-66的操作 *****/
else
{
for (int x = 0; x < m_topRefLength + 1 + (whRatio + 1) * multiRefIdx; x++)//填充上方参考像素
{
refAbove[x+1] = pSrc.at(x, 0);
}
for (int y = 0; y < m_leftRefLength + 1 + (hwRatio + 1) * multiRefIdx; y++)//填充左方参考像素
{
refLeft[y+1] = pSrc.at(0, y);
}
refMain = bIsModeVer ? refAbove : refLeft ;//主要侧
refSide = bIsModeVer ? refLeft : refAbove;//次要侧
refMain++;//指向第一个像素的正主要侧方
refSide++;//指向第一个像素的正次要侧方
refMain[-1] = refMain[0]; //处理最边界:把第一个像素的左上角填充上一个像素的正主要侧方
auto lastIdx = 1 + ((bIsModeVer) ? m_topRefLength + (whRatio + 1) * multiRefIdx : m_leftRefLength + (hwRatio + 1) * multiRefIdx);
refMain[lastIdx] = refMain[lastIdx-1]; //处理最边界:把右上角填充上最右侧的值或左下角填充上最下侧像素
}
// swap width/height if we are doing a horizontal mode:
/******************** 为方便,若不是垂直模式,把width和height交换 *******************/
Pel tempArray[MAX_CU_SIZE*MAX_CU_SIZE];
const int dstStride = bIsModeVer ? pDst.stride : MAX_CU_SIZE;
Pel *pDstBuf = bIsModeVer ? pDst.buf : tempArray;
if (!bIsModeVer)
{
std::swap(width, height);
}
// compensate for line offset in reference line buffers
/****************************** 找到参考像素中的多参考行的选中行 **********************/
refMain += multiRefIdx;
refSide += multiRefIdx;
/***************************************** 对纯水平或垂直类型的操作 ******************************************/
if( intraPredAngle == 0 ) // pure vertical or pure horizontal
{
for( int y = 0; y < height; y++ )
{
for( int x = 0; x < width; x++ )
{
pDstBuf[y*dstStride + x] = refMain[x + 1];//直接投影即可
}
}
#if HEVC_USE_HOR_VER_PREDFILTERING
if (edgeFilter && multiRefIdx == 0)
{
for( int y = 0; y < height; y++ )
{
pDstBuf[y*dstStride] = ClipPel( pDstBuf[y*dstStride] + ( ( refSide[y + 1] - refSide[0] ) >> 1 ), clpRng );
}
}
#endif
}
/*************************************** 对不是纯水平或垂直类型的操作 ****************************************/
else
{
Pel *pDsty=pDstBuf;//重建buffer
for (int y = 0, deltaPos = intraPredAngle * (1 + multiRefIdx); y<height; y++, deltaPos += intraPredAngle, pDsty += dstStride)
{//对高度循环
const int deltaInt = deltaPos >> 5; //当前像素对应参考像素在“改变为只用上一行”中的位置;
const int deltaFract = deltaPos & (32 - 1); //当前像素对应参考像素的加权因子。这两个值参见万帅书P123
/******** 当模式不是2,34,66的条件下,对宽度如下循环 *******/
if (absAng != 0 && absAng != 32)//模式是不是2,18,34,50,66,如是,则不能直接投影
{
/** 如果是亮度,则四抽头滤波 **/
if( isLuma(channelType) )
{
Pel p[4];
#if JVET_M0102_INTRA_SUBPARTITIONS
const bool useCubicFilter = useISP ? ( width <= 8 ) : ( !useFilteredPredSamples || multiRefIdx > 0 );
#else
const bool useCubicFilter = !useFilteredPredSamples || multiRefIdx > 0;
#endif
TFilterCoeff const * const f = (useCubicFilter) ? InterpolationFilter::getChromaFilterTable(deltaFract) : g_intraGaussFilter[deltaFract];//根据deltaFract选取滤波器的类型
int refMainIndex = deltaInt + 1;
for( int x = 0; x < width; x++, refMainIndex++ )//对参考像素进行四抽头滤波!!!
{
p[0] = refMain[refMainIndex - 1];
p[1] = refMain[refMainIndex];
p[2] = refMain[refMainIndex + 1];
p[3] = f[3] != 0 ? refMain[refMainIndex + 2] : 0;
//插值滤波,f[0], f[1], f[2], f[3]是滤波系数
pDstBuf[y*dstStride + x] = static_cast<Pel>((static_cast<int>(f[0] * p[0]) + static_cast<int>(f[1] * p[1]) + static_cast<int>(f[2] * p[2]) + static_cast<int>(f[3] * p[3]) + 32) >> 6);
if( useCubicFilter ) // only cubic filter has negative coefficients and requires clipping
{//方形滤波器具有负的系数,这时需要进行嵌位
pDstBuf[y*dstStride + x] = ClipPel( pDstBuf[y*dstStride + x], clpRng );
}
}
}
/** 如果是色度,则线性滤波 **/
else
{
// Do linear filtering
const Pel *pRM = refMain + deltaInt + 1;
int lastRefMainPel = *pRM++;
for( int x = 0; x < width; pRM++, x++ )
{
int thisRefMainPel = *pRM;
//计算方式参见万帅书P123
pDsty[x + 0] = ( Pel ) ( ( ( 32 - deltaFract )*lastRefMainPel + deltaFract*thisRefMainPel + 16 ) >> 5 );
lastRefMainPel = thisRefMainPel;
}
}
}
/********* 对水平垂直和对角条件下,对宽度如下循环 ***********/
else
{
// Just copy the integer samples
for( int x = 0; x < width; x++ )//只需复制整数像素值,直接投影
{
pDsty[x] = refMain[x + deltaInt + 1];
}
}
/************* 后续处理 —— 近对角模式的PDPC ************/
const int numModes = 8;
const int scale = ((g_aucLog2[width] - 2 + g_aucLog2[height] - 2 + 2) >> 2);
CHECK(scale < 0 || scale > 31, "PDPC: scale < 0 || scale > 31");
#if JVET_M0102_INTRA_SUBPARTITIONS
if( !useISP )
{
#endif
if ((predMode == 2 || predMode == VDIA_IDX) && multiRefIdx == 0)//如果是模式2或66
{
int wT = 16 >> std::min(31, ((y << 1) >> scale));
for (int x = 0; x < width; x++)
{
int wL = 16 >> std::min(31, ((x << 1) >> scale));
if (wT + wL == 0) break;
int c = x + y + 1;
if (c >= 2 * height) { wL = 0; }
if (c >= 2 * width) { wT = 0; }
const Pel left = (wL != 0) ? refSide[c + 1] : 0;
const Pel top = (wT != 0) ? refMain[c + 1] : 0;
pDsty[x] = ClipPel((wL * left + wT * top + (64 - wL - wT) * pDsty[x] + 32) >> 6, clpRng);
//“ wL * left + wT * top ”是未滤波的,“ (64 - wL - wT) * pDsty[x]” 是滤波后的,“ +32 ”用于四舍五入
}
}
else if (((predMode >= VDIA_IDX - numModes && predMode != VDIA_IDX) || (predMode != 2 && predMode <= (2 + numModes))) && multiRefIdx == 0)//如果是模式58~65和2~10
{
int invAngleSum0 = 2;
for (int x = 0; x < width; x++)
{
invAngleSum0 += invAngle;
int deltaPos0 = invAngleSum0 >> 2;
int deltaFrac0 = deltaPos0 & 63;
int deltaInt0 = deltaPos0 >> 6;
int deltay = y + deltaInt0 + 1;
if (deltay >(bIsModeVer ? m_leftRefLength : m_topRefLength) - 1) break;
int wL = 32 >> std::min(31, ((x << 1) >> scale));
if (wL == 0) break;
Pel *p = refSide + deltay;
#if JVET_M0238_PDPC_NO_INTERPOLATION
Pel left = p[deltaFrac0 >> 5];
#else
Pel left = (((64 - deltaFrac0) * p[0] + deltaFrac0 * p[1] + 32) >> 6);
#endif
pDsty[x] = ClipPel((wL * left + (64 - wL) * pDsty[x] + 32) >> 6, clpRng);
//“ wL * left ”是未滤波的,“ (64 - wL) * pDsty[x]” 是滤波后的,“ +32 ”用于四舍五入
}
}
#if JVET_M0102_INTRA_SUBPARTITIONS
}
#endif
}
#if HEVC_USE_HOR_VER_PREDFILTERING
if( edgeFilter && absAng <= 1 )
{
for( int y = 0; y < height; y++ )
{
pDstBuf[y*dstStride] = ClipPel( pDstBuf[y*dstStride] + ((refSide[y + 1] - refSide[0]) >> 2), clpRng );
}
}
#endif
}
// Flip the block if this is the horizontal mode
/************************************** 如果不是垂直类型模式,将块翻转回来 **********************************/
if( !bIsModeVer )
{
for( int y = 0; y < height; y++ )
{
for( int x = 0; x < width; x++ )
{
pDst.at( y, x ) = pDstBuf[x];
}
pDstBuf += dstStride;
}
}
}
版权声明:本文为weixin_42979679原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。