4. 变换与量化:从频域视角看压缩

变换与量化,说白了就是视频编码里「丢信息」最核心的两个环节。很多刚入行的同学总觉得这一步就是算算DCT、除个系数,没什么技术含量。嗯,我当年也这么想,直到第一次调编码器时发现——同样的码率,画面质量能差出一大截。

这一章我们就把DCT变换、整数实现、量化参数和率失真优化这几个点串起来讲。你会发现,它们其实是一套完整的「信息取舍」策略。

4.1 DCT变换原理:为什么是频域?

先问一个问题:为什么非要把像素值转到频域?

答案其实很简单——人眼对高频细节不敏感。图像里大部分能量都集中在低频区域,高频部分往往可以丢掉而不影响主观质量。DCT(离散余弦变换)就是干这个的:把空间域的像素块变成频域的系数块。

以H.264/HEVC里最常用的4x4 DCT为例,变换公式长这样:

Y = A * X * A^T

其中 A 是变换矩阵,A[i][j] = C(i) * cos((j + 0.5) * π * i / N)
C(0) = sqrt(1/N),C(i>0) = sqrt(2/N)

我个人习惯把DCT理解成「能量集中器」。一个平坦的4x4块,经过DCT后,左上角的DC系数占了总能量的90%以上。剩下的AC系数,很多都接近0。

关键结论:DCT本身不压缩数据,它只是把信息重新排列,让后续的量化能更高效地丢掉不重要的部分。

4.2 整数DCT实现:浮点转整数的坑

理论上的DCT是浮点运算,但编码器里不可能用浮点——速度太慢,而且不同平台结果不一致。所以实际编码器都用整数DCT。

整数DCT的核心思路是:把浮点变换矩阵放大、取整,然后在反变换时缩回去。以H.264的4x4整数DCT为例:

// 变换矩阵(放大后取整)
int A[4][4] = {
    {1, 1,  1,  1},
    {2, 1, -1, -2},
    {1, -1, -1, 1},
    {1, -2, 2, -1}
};

// 正变换:Y = A * X * A^T
// 反变换:X' = A^T * Y' * A
// 注意:正反变换后需要右移补偿缩放因子

这里有个坑——我曾经在移植编码器到ARM平台时,发现反变换后的像素值总是偏大。查了半天,原来是右移补偿的位数算错了。整数DCT的缩放补偿必须严格匹配,差一个bit画面就花了。

避坑指南:整数DCT的精度取决于变换矩阵的位数和右移策略。H.264的4x4 DCT使用6位右移,HEVC的4x4 DCT使用7位。不要随意改,否则会出现漂移误差。

4.3 量化参数与量化矩阵

量化才是真正丢信息的地方。DCT系数除以量化步长再取整,高频系数被砍成0,这就是压缩的本质。

量化参数QP是编码器里最常用的控制手段。QP每增加6,量化步长翻倍,码率大约减半。但画面质量也会明显下降。

QP值 量化步长 典型码率(1080p) 主观质量
0 1 ~50 Mbps 无损/近无损
18 ~4 ~20 Mbps 视觉无损
28 ~16 ~8 Mbps 良好
38 ~64 ~2 Mbps 可接受
51 ~256 ~0.5 Mbps 明显块效应

量化矩阵则是更精细的控制。不同频率的系数用不同的量化步长——低频用细量化,高频用粗量化。JPEG里就有默认的量化表,视频编码标准里也支持自定义量化矩阵。

我的经验:在低码率场景下(比如视频会议),我习惯把高频量化步长再加大20%-30%。虽然PSNR会掉一点,但主观上画面更干净,没有那种「高频噪声」的颗粒感。

4.4 率失真优化基础:编码器的「经济学」

率失真优化(RDO)是编码器里最「聪明」的部分。它解决的是一个问题:在给定的码率下,如何让失真最小?

说白了,就是做选择题。编码器有无数种方式去编码一个块——不同的预测模式、不同的变换大小、不同的量化参数。RDO帮我们选出「性价比」最高的那个。

经典的率失真代价函数:

J = D + λ * R

其中:
J  = 率失真代价(越小越好)
D  = 失真(通常用SSD或SAD)
R  = 码率(编码该块需要的比特数)
λ  = 拉格朗日乘子(控制码率和失真的权衡)

λ的取值很关键。λ越大,编码器越倾向于省码率(哪怕失真大一点);λ越小,编码器越倾向于保质量(哪怕多花点码率)。

我记得在做一个移动端编码器项目时,发现默认的λ公式在低码率下表现很差——画面全是模糊的块。后来我把λ乘以一个0.8的系数,主观质量立刻上来了。代价是码率多了15%,但用户反馈好太多了。

核心思路:RDO不是追求「绝对最优」,而是在编码复杂度可接受的前提下,找到「足够好」的编码方案。实际编码器里,RDO只对部分模式做全搜索,其他模式用快速算法近似。

4.5 变换与量化的整体流程

把上面几个点串起来,一个典型的变换量化流程是这样的:

  1. 残差生成:原始块减去预测块,得到残差块
  2. DCT变换:残差块做整数DCT,得到频域系数
  3. 量化:系数除以量化步长(可能带量化矩阵),取整
  4. 扫描与熵编码:量化后的系数按Zig-zag顺序扫描,然后做熵编码
  5. 反量化与反变换:解码端重建残差,用于后续帧的参考

这个流程里,每一步都有调优空间。比如DCT可以用快速算法(蝶形变换),量化可以用死区量化(Dead-zone)来减少小系数的编码开销,扫描顺序可以根据系数分布动态调整。

变换与量化核心流程 残差生成 整数DCT变换 量化 熵编码 反量化 反DCT变换 关键调优点 • DCT:快速蝶形算法、变换块大小选择(4x4/8x8/16x16/32x32) • 量化:QP自适应调整、死区量化、量化矩阵定制 • RDO:λ参数调优、快速模式决策、码率控制与RDO联动

4.6 实战中的调优建议

最后分享几个我在项目中积累的经验:

  • QP不要全局统一:纹理复杂的区域用更细的量化(低QP),平坦区域用更粗的量化(高QP)。这叫自适应量化,几乎所有商用编码器都支持。
  • 量化矩阵不是摆设:默认矩阵是通用的,但针对特定内容(比如屏幕内容、监控视频)定制量化矩阵,能省10%-20%的码率。
  • RDO的λ要跟码率控制联动:很多编码器把λ设成QP的固定函数,但实际场景下,λ应该根据缓冲区状态动态调整。
  • 小心整数精度:整数DCT的反变换必须做饱和处理(clip到像素范围),否则会出现颜色溢出。我曾经见过一个bug,反变换后像素值跑到300多,画面直接花了。

一句话总结:变换和量化是编码器里「性价比」最高的调优点。花一周时间调好量化策略,效果可能比花一个月改运动估计还要明显。