JPEG - 想法与实践/杂项
在量化之后,sxs 矩阵 g(u, v) 中的最后 s2 个数字通常非常小,我们可以选择一个数字 r = 3, 4, ..., s-1,并省略 u 或 v >= r 的那些对 (u, v),这样我们只需要处理 r2 个数字 (u, v = 0, 1, ..., r-1)。然而,这样做并没有让我们获得太多益处,因为 r 必须非常接近 s-1,而且零的实际数量并不重要(30 个零占用 8 位,12 个零占用 7 位)。在绘制过程中,我们可以通过将逆余弦变换限制在 r2 个数字来节省时间。我们在 第二部分 的(两个)绘制程序中已经做到了这一点(我们设置 r = 6)。但由于这样的程序(用于实际用途)必须用汇编语言编写,因此这样做并没有给我们带来太多益处,因为如今图片绘制速度非常快。但看到我们实际上需要多少个(或者更确切地说,需要多少个)余弦变换数字(颜色值函数展开式中的项)是有启发性的。因此,我们设计了我们的绘图程序,以便我们可以输入“项数”(数字 r)。在这张图片(使用 8x8 方格)中,项数分别是 8 和 4
-
8 项
-
4 项
请注意,文件的大小很大程度上取决于压缩之前大多数数字是零,因为每第二个数字都表示零的数量。因此,如果只有很少的零,那么大多数(每第二个)这些数字(以编码形式为 000)会不必要地占用大量空间。因此,如果我们不是在量化时除以一个大数字,而是除以一个小数字(例如 0.1),那么我们会发现文件占用空间是 BMP 格式的两倍!
在真正的 JPEG 过程中,选择 8 作为小方格的边长与计算机中 8 的作用无关,因为数字被转换为各种长度的位序列。边长不能太小,因为这样会减弱余弦变换的效果,也不能太大,因为这样计算量会太大:对于一个 sxs 方格,总的项数是 s4,因为有 s2 个点,并且每个点都有 s2 个公式。因此,如果边长加倍,那么计算量会增加四倍。选择 8 作为边长在 JPEG 过程引入时一定是最佳选择。然而,如今,随着速度的倍增,我们可以通过选择更大的边长(例如 12)来实现更好的压缩,但改变这一点为时已晚,而且收益并不显著。
对于之前提到的 280 像素的二次方图片(用于演示余弦变换),计算量是将图片分成 8x8 方格的 1225 倍。
在下面的两张图片中,我们分别使用了 20x20 和 10x10 方格的划分。该过程对于较小的划分效率不如较大的划分。两张图片都用大约相同的数字进行量化,第一张占用 13 Kb,第二张占用 22 Kb
-
20x20 方格
-
10x10 方格
让我们看看如果在下面最上面的图片中,我们在亮度部分和色度部分的量化上做出很大的差异会有什么结果。在最左边的图片中,亮度部分的质量较低,而色度部分的质量较高。因此,图案被破坏了,但颜色看起来是正确的。在最右边的图片中,情况相反:图案是正确的,但颜色不熟悉
-
原始
-
亮度部分不好
-
色度部分不好
JPEG 过程总是会引入图片的变化,但通过选择高品质,这些变化可以变得微不足道。但它们确实存在,如果你想将来能够处理图片,就不应该以 JPEG 格式保存它。有些图片比其他图片更不适合 JPEG 压缩,因为如果要使变化完全不可见,则必须将质量设置为高。但人们会说,总是有可能以 JPEG 格式保存而不会出现可见的变化。然而,这并非一定属实:这取决于 JPEG 的实现。我们的演示程序总是可以生成一个文件,该文件会导致(几乎)无瑕疵的图片,但这是因为我们以与 Y 分量相同的方式处理颜色分量——我们只用不同的数字进行量化,但我们可以不进行量化(将质量设置为 100)。在真正的 JPEG 过程中,可以减少两个颜色“图片”(颜色分量)的大小,与灰度图片(Y 分量)相比。这可以通过(例如)先将两个颜色“图片”分成 2x2 方格,并将这样一个方格视为一个像素(取四个颜色值的平均值),从而使颜色图片变为原来的四分之一大小。这是在分成 8x8 方格之前进行的,因此 Y 分量的四个 8x8 方格与颜色分量的单个 8x8 方格组合。原因是颜色通常不会在图片上快速变化,我们可以通过这种方式压缩大约 25%。该过程称为颜色分量的子采样。
接下来的两张图片是用我们(自制的,但)第二部分 中的真正的 JPEG 程序制作的,但设置不同。这张图片是通过将每第二个像素为绿色,其余像素为透明的图片覆盖到另一张图片上而制作的。由于像素到像素之间的强烈变化,这两张图片都占用了相当大的空间。在第一张图片中,颜色分量与 Y 分量以相同的方式处理,因此图片是正确的。在第二张图片中,使用了颜色分量的子采样,因此颜色值变成了平均值,因此图片更偏绿色
-
无子采样
-
子采样
请注意,并非所有 JPEG 压缩程序都允许在颜色分量的子采样和非子采样之间进行选择。
对于灰度图片,我们只有 Y 分量,但由于 Cb 和 Cr 分量(在量化之后)的贡献相对于 Y 分量很小,因此图片的灰度版本几乎占用的空间与彩色版本一样多——通常超过 90%。
当图片只有一种颜色时,压缩率应该达到最大值。对于我们的演示程序来说,情况就是这样:这样一个 1000x1000 像素的图片的数据部分只占用了 14 字节。但是,当我们使用真正的 JPEG 过程时,数据部分将占用 15000 字节——我们将在 第二部分 中看到原因。
一些图像格式可以包含透明度,例如 GIF 和 PNG,但 BMP 和 JPEG 不行。GIF 特别适合图形表示,而 PNG 适合在简单背景上叠加对象的图片。它们都是无损的,但 GIF 图片只能包含 256 种不同的颜色(在标题中指定),而且尽管压缩效果很好,但转换为 PNG 文件的照片通常会占用 BMP 文件的 75%。对于 JPEG,在一个常见问题解答文章中,你可以看到以下对“我可以制作透明的 JPEG 吗?”这个问题的答案:“不行。JPEG 不支持透明度,并且在短期内也不太可能支持透明度。事实证明,在 JPEG 中添加透明度并非易事;如果你想了解详细信息,请继续阅读”。然后,我们被告知,在 GIF 图片中,透明度是通过让一个未使用的颜色值标记出透明区域来引入的,但这种方法不能用于 JPEG。它可以用于 BMP,其中 16777216 种可能的颜色中很容易遗漏一种来标记透明区域;但不能用于 JPEG,因为 JPEG 中的颜色值是不精确的。透明度将为每个点占用一位,这个新的分量可以进行与三个 YCbCr 分量相同的处理。然而,这种方法被拒绝的理由是 JPEG 过程不适合处理急剧过渡:如果要令人满意地再现围绕孔洞的边界(通过孔洞可以看到强烈偏离的颜色),那么透明度分量的余弦变换数字只能用小的数字进行量化,这样文件就会占用相当大的空间。这是真的,但图片仍然会比 PNG 格式占用更小的空间,而且,透明度通常只是暂时使用。很容易对 JPEG 文件进行排列,使其能够支持透明度。
然而,由于余弦变换和透明度分量的量化并没有带来多少益处,因此应省略这些操作,并将透明度位的写入方式如下:我们依次从左到右、从右到左沿水平线移动,使像素相邻,在此位序列中,我们将每个不间断的 0 或 1 区间替换为 0 或 1 的数量(这些数量的总和等于宽度乘以高度)。然后对得到的自然数序列进行编码,可以在颜色数据之前写入文件。通过这种方法,透明区域将与原始图像完全一致。在左侧图像中,黑色被设置为透明,图像叠加在蓝色背景上,形成了右侧图像,尽管此图像质量很低,但透明区域保持不变。
-
原始
-
透明度
在图像中引入透明度的过程可以通过 BMP 格式的图像进行,例如。BMP 格式目前不支持透明度,但我们可以用一张同样是 BMP 格式的单色图像作为伴随图像来确定透明区域。单色图像只包含两种不同的颜色,通常是黑色和白色。两种颜色的 RGB 值在头文件中声明(或者更确切地说是头文件用必要的字节延长来包含此信息),并且数据 - 每个点的 1 位 - 与普通 BMP 文件中的 RGB 值以相同的方式写入:逐行写入,但将每 8 位块转换为一个字节(并且字节行的长度能被 4 整除)。这种方法得到了 *Windows* 位图绘制过程的支持:如果我们让带颜色的图像中的透明区域为黑色,并让它在黑白单色图像中为白色区域,那么 *Windows* 具有直接将两个文件的数据传输到屏幕的程序,从而生成一个透明区域为空的图像,这样我们就可以通过它看到底层 - 例如桌面。