经过 DCT 以后,每个 8×88\times8 的样本块都变成了一个 8×88\times8 的系数矩阵。左上角的系数表示低频信息,越靠近右下角,表示的频率越高。

不过,DCT 本身并没有减少数据量。变换前有 64 个样本,变换后仍然有 64 个系数;而且这些系数通常还是带小数的。如果直接把它们全部保存下来,文件甚至可能比原始图像更大。

DCT 真正的作用,是把原本散落在各个像素中的信息重新集中起来:自然图像中的能量通常集中在少数低频系数上,许多高频系数都很小。接下来只要降低这些系数的精度,就有机会把其中一大部分变成 0。这个过程称为量化(quantization)。

量化是怎么做的

所谓的量化,就是用一个较为粗略的数代替原来精确的 DCT 系数。比如,我们计划只用 10 的倍数来表示系数:

,20,10,0,10,20,\cdots, -20, -10, 0, 10, 20, \cdots

那么

13101720710102030\begin{gathered} 13 \to 10 \\ 17 \to 20 \\ -7 \to -10 \\ 1 \to 0 \\ 2 \to 0 \\ -3 \to 0 \\ \cdots \end{gathered}

换句话说,量化所做的事情就是降低现有值的精度。精度降低之后,就可能产生更多的 0 值,有利于后续压缩。比如,我们可以把

,20,10,0,10,20,\cdots, -20, -10, 0, 10, 20, \cdots

记成

,2,1,0,1,2,\cdots, -2, -1, 0, 1, 2, \cdots

同时记录着量化步长,在这个例子中,量化步长就是 10。量化步长越小,可供选择的近似值越多,结果通常也越接近原系数;量化步长越大,可供选择的近似值越少,误差通常就越大,但更多系数会变成 0,这就更有利于压缩。

一个 DCT 系数块共有 64 个位置,每个位置表示的频率不同,适合采用的量化步长也不同。因此,JPEG 允许编码器为这 64 个位置分别指定步长,再按照对应位置排列成一个 8×88\times8 的矩阵,这就是量化表。量化表中的每个数,都是相同位置上 DCT 系数的量化步长。

现在令 DCT 系数矩阵为 MM,量化表为 QQ,量化后的系数矩阵为 ZZ。对每个位置分别执行前面描述的操作,就可以写成:

Zvu=round(MvuQvu)Z_{vu}=\operatorname{round}\left(\frac{M_{vu}}{Q_{vu}}\right)

其中,MvuM_{vu} 是某个 DCT 系数,QvuQ_{vu} 是它对应的量化步长,ZvuZ_{vu} 是编码器实际记录的整数。

round(24216)\operatorname{round}\left(\frac{242}{16}\right) =round(15.125)= \operatorname{round}(15.125) =15= 15

经过这一步,原来的小数系数变成了整数,绝对值较小的系数还会直接变成 0。由于高频系数本来就往往较小,再配合较大的量化步长,一个系数块的右下区域通常会出现大量连续的 0,这就给后续的编码过程带来了非常有利的条件。

量化表

量化表决定了每个频率要保留多少精度。下面是 JPEG 标准附录(T.81 Annex K、Table K.1)中给出的一张亮度量化表的示例:

QY=[1611101624405161121214192658605514131624405769561417222951878062182237566810910377243555648110411392496478871031211201017292959811210010399]Q_Y= \begin{bmatrix} 16 & 11 & 10 & 16 & 24 & 40 & 51 & 61 \\ 12 & 12 & 14 & 19 & 26 & 58 & 60 & 55 \\ 14 & 13 & 16 & 24 & 40 & 57 & 69 & 56 \\ 14 & 17 & 22 & 29 & 51 & 87 & 80 & 62 \\ 18 & 22 & 37 & 56 & 68 & 109 & 103 & 77 \\ 24 & 35 & 55 & 64 & 81 & 104 & 113 & 92 \\ 49 & 64 & 78 & 87 & 103 & 121 & 120 & 101 \\ 72 & 92 & 95 & 98 & 112 & 100 & 103 & 99 \end{bmatrix}

可以看到,左上角的量化步长总体较小,右下角的量化步长总体较大。这与人眼的感知特性有关:低频分量描述大面积的明暗和缓慢变化,一旦出现较大的误差,整块图像的亮度都会偏离;高频分量主要描述细小纹理与锐利边缘,适当降低精度通常不太容易被察觉。因此,量化表实际上控制着压缩率与图像质量之间的平衡。

同一个分量的所有 8×88\times8 块都会使用同一张量化表,但不同分量可以选择不同的表。通常,Y 分量单独使用一张亮度量化表,Cb、Cr 分量则共同使用另一张色度量化表。由于人眼对色度细节相对不敏感,色度量化表往往会采用更大的量化步长。

不过,JPEG 标准并没有固定量化表中的具体数值。编码器可以自行生成量化表,并将实际使用的表写入文件,解码器则可以按照文件中的量化表进行还原。

在很多图像处理软件中,当我们选择将图像导出为 JPEG 格式时,都会有一个“质量”参数,我们所调节的“质量”,通常就是调节了量化表中各个步长的大小。较高的质量对应较小的量化步长,较低的质量对应较大的量化步长。

量化为什么会损失信息

解码时,量化后的系数会乘回对应的量化步长:

M^vu=ZvuQvu\hat{M}_{vu}=Z_{vu}Q_{vu}

假设量化步长现在是 10,那么原系数 13 会被量化成 1:

round(1310)=1\operatorname{round}\left(\frac{13}{10}\right)=1

它在还原后则会得到:

1×10=101\times10=10

显然,还原出的 10 与原来的 13 相差了 3。这个误差就是量化造成的信息损失。

换句话说,量化其实是一种多对一的映射,也是 JPEG 压缩流程中造成信息损失的核心步骤。DCT 只是换了一种方式表达图像,若保留足够精度,它本身可以逆变换回去;量化则真正丢弃了部分精度,让后续压缩能够大幅减少数据量。

量化完成了整个压缩流程中最关键的一次数据整理,它将原本带小数的 DCT 系数,变成了许多小整数和大量的 0。这就给后续的编码过程带来了非常有利的条件。JPEG 的编码器会进一步利用这些整数的统计分布特点,把它们转换成更短的码流,从而实现更高的压缩率。

量化误差会带来什么

量化较轻时,误差通常隐藏在纹理和颜色的细微变化里,不容易直接看出来。可随着量化强度的增加,一些典型的 JPEG 伪影便会逐渐出现:

  • 平滑区域中的明暗或颜色过渡变得不自然;
  • 锐利边缘附近出现波纹,也就是所谓的振铃(ringing);
  • 相邻 8×88\times8 块之间出现明显边界,也就是所谓的块效应(blocking)。