HEVC帧内预测HM实现
1、具体代码位置
角度预测的代码路径:TLibCommon\TComPrediction.cpp xPredIntraAng()
2、代码流程
const Bool bIsModeVer = (dirMode >= 18);
const Int intraPredAngleMode = (bIsModeVer) ? (Int)dirMode - VER_IDX : -((Int)dirMode - HOR_IDX);
const Int absAngMode = abs(intraPredAngleMode);
const Int signAng = intraPredAngleMode < 0 ? -1 : 1;
const Bool edgeFilter = bEnableEdgeFilters && isLuma(channelType) && (width <= MAXIMUM_INTRA_FILTERED_WIDTH) && (height <= MAXIMUM_INTRA_FILTERED_HEIGHT);
// Set bitshifts and scale the angle parameter to block size
static const Int angTable[9] = { 0, 2, 5, 9, 13, 17, 21, 26, 32 };
static const Int invAngTable[9] = { 0, 4096, 1638, 910, 630, 482, 390, 315, 256 }; // (256 * 32) / Angle
Int invAngle = invAngTable[absAngMode];
Int absAng = angTable[absAngMode];
Int intraPredAngle = signAng * absAng;
结合之前的角度图 和 角度表。 上面一段代码的功能就是 根据模式来 取 intraPredAngle 角度。
首先以 18 为分界 分水平 和垂直, 大于等于18 垂直, 小于水平。
水平和 垂直 分类的角度中 又可以以10 为分界 分为 水平向上 向下, 垂直又可以以26为分界分为垂直向左 向右
水平的2-17 从10 往两边角度增加相同的即 2 5 9 13 17 21 26 32。 垂直的18-34 从26 往两边角度增加相同的即 2 5 9 13 17 21 26 32。
所以只要定义这个 就可以把所有模式的角度给计算出来。
intraPredAngleMode = (bIsModeVer) ? (Int)dirMode - VER_IDX : -((Int)dirMode - HOR_IDX);
这个就是计算距离 10 和 26 的差值,absAngMode 这个是距离的绝对值, signAng这个就是符合 是向上 向下 向左 还是向右。
const Int refMainOffsetPreScale = (bIsModeVer ? height : width) - 1;
const Int refMainOffset = height - 1;
for (Int x = 0; x < width + 1; x++)
{
refAbove[x + refMainOffset] = pSrc[x - srcStride - 1];
}
for (Int y = 0; y < height + 1; y++)
{
refLeft[y + refMainOffset] = pSrc[(y - 1)*srcStride - 1];
}
refMain = (bIsModeVer ? refAbove : refLeft) + refMainOffset;
refSide = (bIsModeVer ? refLeft : refAbove) + refMainOffset;
// Extend the Main reference to the left.
Int invAngleSum = 128; // rounding for (shift by 8)
for (Int k = -1; k > (refMainOffsetPreScale + 1)*intraPredAngle >> 5; k--)
{
invAngleSum += invAngle;
refMain[k] = refSide[invAngleSum >> 8];
}
inAngle 的作用, 相当于 是一个算法定点化的一个过程。
这边实际就是之前说过的映射的过程,refMain 主参考边的数组,对于垂直类的模式,主参考边是refAbove ,水平类的就是refLeft。
主参考边的数组确定之后,剩下的一个就是副参考 refSide。
最后的循环就是要将refSide 映射到 refMain。 也就是下面这个公式,就是将refSide一一映射到refMain。
Pel *pDsty = pDst;
for (Int y = 0, deltaPos = intraPredAngle; y < height; y++, deltaPos += intraPredAngle, pDsty += dstStride)
{
const Int deltaInt = deltaPos >> 5;
const Int deltaFract = deltaPos & (32 - 1);
if (deltaFract)
{
// Do linear filtering
const Pel *pRM = refMain + deltaInt + 1;
Int lastRefMainPel = *pRM++;
for (Int x = 0; x < width; pRM++, x++)
{
Int thisRefMainPel = *pRM;
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];
}
}
}
上面这个就是参考像素获取的代码。 最外层的循环 是 y = 0 到 height -1。deltaPos每次都是增加intraPredAngle,pDsty 也是增加dstStride。
算出deltaInt 这个用来确定位置,另外一个则是权重。
refMain 指针移动deltaInt + 1 个字节 pRM指向这个位置。 然后先把这个位置的值取出来赋值给lastRefMainPel,pRM 向前移动一位。
然后在内层循环里面。块中行扫描。 thisRefMainPel 取 pRM 当前指向的值。 预测值 就取lastRefMainPel和thisRefMainPel 的权重。
然后将thisRefMainPel 赋值给lastRefMainPel, pRM 向前移动一位 thisRefMainPel 取pRM。 这样循环。
index的变化 简化就是外面循环加intra_pred_angle,算delta_int,里面的循环delta_int的基础上面取累积。
for (int j = 0; j < iHeight; j++) {
delta_pos = delta_pos + intra_pred_angle;
delta_int = delta_pos >> 5;
for (int i = 0; i < iWidth; i++) {
int ref_main_index = i + delta_int;
int ref_side_index = ref_main_index + 1;
saveDistanceMatri(distanMatri, i, j, ref_main_index, ref_side_index);
}
}
if (!bIsModeVer)
{
for (Int y = 0; y < height; y++)
{
for (Int x = 0; x < width; x++)
{
pTrueDst[x*dstStrideTrue] = pDst[x];
}
pTrueDst++;
pDst += dstStride;
}
}
x方向和y方向之间的关系
垂直方向是按行扫描过去 然后得到的结果一个个存放在 pDsty中。
一行都是相差1的。
水平方向的 是需要按列是相差1的。 但是现在 按照行做完,pDsty 中却是行相差1, 所有应该行列转置一下