第3章 帧内预测:35种方向与模式详解

帧内预测,说白了就是用已经编好的像素去猜还没编的像素。H.265 比 H.264 多了不少花样,从 9 种模式直接干到了 35 种。嗯,这 35 种不是随便加的,每一条方向都有它的物理意义。

我刚开始接触 HEVC 时也觉得 35 种太多了,后来调过几个序列才发现——方向越细,纹理越复杂的块越省码率。你想想看,一个 45° 的斜条纹,用 H.264 的粗略方向去猜,残差能大一圈。

3.1 帧内预测的整体流程

先看一张整体图,把 35 种模式怎么来的、怎么选的说清楚。

H.265 帧内预测模式选择流程 H.265 帧内预测模式选择流程 当前 CU 原始像素 生成 35 种候选模式(Planar/DC/33角度) RMD 粗选(SATD 排序,保留 Top N) 加入 MPM(最可能模式,来自相邻块) RD Cost 精算,选最优模式 输出最佳模式 + 残差 RMD: 粗粒度模式决策

流程不复杂,但每一步都有坑。我后面会一个一个说。

3.2 35 种预测模式的分类

H.265 的帧内模式分三大类:

类别 模式编号 数量 说明
Planar 0 1 平面插值,适合平滑渐变区域
DC 1 1 均值填充,适合平坦区域
角度预测 2–34 33 覆盖 -135° 到 +135° 方向
关键点:Planar 和 DC 是「兜底」模式。纹理复杂时,角度预测才是主力。我实测过,自然场景中角度模式被选中的概率超过 70%。

3.3 33 种角度预测方向

角度模式从 2 到 34,覆盖了从近乎水平到近乎垂直的各个方向。具体对应关系如下:

模式编号 角度偏移 物理方向 典型适用场景
2–9 近水平 右上 → 左下 水平纹理、横条纹
10–17 斜上 左上 → 右下 斜向纹理、45° 边缘
18–25 近垂直 上 → 下 垂直纹理、竖条纹
26–34 陡斜 右上 → 左下(陡) 陡峭边缘、锐角纹理

每个角度模式对应一个 角度偏移量,范围从 -32 到 +32(以 1/32 像素精度为单位)。这个偏移量决定了参考像素的插值位置。

我的习惯:调试时先看模式 10(45° 斜上)和模式 26(45° 斜下),这两个方向在自然图像中出现频率最高。如果这两个方向残差大,说明纹理方向更复杂,需要检查角度细化逻辑。

3.4 角度预测的插值实现

角度预测不是简单复制参考像素,而是要做 分数像素插值。H.265 用 1/32 像素精度,比 H.264 的 1/4 像素细得多。

插值公式如下:

// 角度偏移量: intraPredAngle (范围 -32 ~ 32)
// 参考像素: ref[]
// 预测位置: (x, y)

// 计算投影位置
int offset = (y * intraPredAngle) >> 5;  // 等效于除以 32
int idx = x + offset;
int fact = (y * intraPredAngle) & 31;     // 取小数部分

// 双线性插值
if (fact == 0) {
    pred[x][y] = ref[idx + 1];            // 整数位置,直接取
} else {
    pred[x][y] = ((32 - fact) * ref[idx + 1] + fact * ref[idx + 2] + 16) >> 5;
}

这段代码我调过很多次。有个细节容易忽略:offset 可能是负数,意味着要往反方向取参考像素。边界处理稍不注意就会数组越界。

我曾经踩过的坑:在 ARM 平台上,32 位整数除法用 >> 5 没问题,但 intraPredAngle 如果是负数,右移在 C 标准里是 implementation-defined。后来我统一改成 ((y * intraPredAngle + 16) >> 5) 才稳定。嗯,跨平台编码一定要小心符号右移。

3.5 DC 模式

DC 模式最简单,也最容易被低估。它的预测值就是参考像素的均值:

// 对于 N×N 块
int sum = 0;
for (int i = 0; i < N; i++) {
    sum += refTop[i];     // 上方参考像素
    sum += refLeft[i];    // 左侧参考像素
}
int dcVal = (sum + N) >> (log2(N) + 1);  // 均值,四舍五入

// 所有像素用同一个值
for (int y = 0; y < N; y++)
    for (int x = 0; x < N; x++)
        pred[x][y] = dcVal;

DC 模式在平坦区域表现极好,码率几乎为零。但遇到渐变或纹理,残差会很大。我建议编码器在 RMD 阶段给 DC 一个「保底名额」,别让它被过早淘汰。

3.6 Planar 模式

Planar 模式是 H.265 新增的,H.264 没有。它用水平和垂直两个方向的线性插值做平均,能很好地拟合平滑渐变。

// 水平插值
int horVal = (N - 1 - x) * refLeft[y] + (x + 1) * refTopRight;
// 垂直插值
int verVal = (N - 1 - y) * refTop[x] + (y + 1) * refBottomLeft;
// 最终预测值
pred[x][y] = (horVal + verVal + N) >> (log2(N) + 1);

注意 refTopRight 和 refBottomLeft 分别是右上角和左下角的参考像素。这两个值决定了渐变的方向和幅度。

实际效果:Planar 模式在天空、墙面、水面等渐变区域,比 DC 能省 20%–30% 的码率。我在一个 4K 风景序列上测过,Planar 被选中的概率大约 15%,但贡献的编码效率提升很明显。

3.7 帧内编码优化策略

35 种模式全算一遍,计算量太大。实际编码器必须做优化。我常用的策略有这几个:

  1. RMD 粗选:用 SATD(Hadamard 变换后的绝对差和)快速排序,只保留 Top 3–8 个模式。
  2. MPM 机制:从相邻 CU 的模式中继承 3 个最可能模式,加入候选列表。MPM 命中率通常超过 60%。
  3. 模式分组跳过:如果 DC 和 Planar 的 SATD 已经很小,跳过大部分角度模式。
  4. CU 尺寸自适应:大 CU(32×32、64×64)角度模式可以少算几个,小 CU(4×4、8×8)必须算全。
优化手段 节省时间 码率损失 推荐场景
RMD 保留 Top 3 ~40% < 0.5% 实时编码
MPM 优先 ~20% < 0.2% 所有场景
大 CU 角度裁剪 ~15% ~1% 低延迟应用
全模式 + RD 精算 0% 0% 离线编码、最高质量
我的建议:别一上来就搞全模式。先跑 RMD + MPM,看看码率损失能不能接受。大部分场景下,Top 5 模式 + MPM 就能达到全模式 99% 的效率。剩下的 1%,不值得花 40% 的时间去追。

3.8 避坑指南

最后说几个我实际项目中遇到的坑:

  • 参考像素不可用:图像边界或 slice 边界处,参考像素可能不存在。处理方式是填充最近的可用像素,或者用 DC 值代替。
  • 角度模式索引越界:当 offset 为负且绝对值很大时,idx 可能小于 0。一定要做 clamp。
  • Planar 模式的参考像素顺序:H.265 标准里 refTopRight 和 refBottomLeft 的获取方式有严格定义,别搞反了。
  • DC 模式的边界滤波:对于 4×4 块,DC 预测值在边界处要做平滑滤波,否则会有块效应。

嗯,帧内预测这部分内容不少,但核心就三件事:方向选对、插值算准、候选剪够。把这三点吃透了,H.265 的帧内编码你就掌握了七八成。