转载请注明出处:(http://www.jianshu.com/p/934269064bfb)
本篇文章翻译自谷歌出的优化视频里面的光头佬(Colt McAnlis),原文地址需, 以下正文:
JPG格式是1992年出现的最先进的图片压缩技术之一。此后,它就成为互联网图片的主力。这当然和JPG背后的技术有关,JPG的工作原理异常复杂,它需要深入理解人眼是如何调整对色彩和图像边缘的感知。
在研究这些东西前(你也一样,如果你在阅读此文),我想把JPG的编码原理分解成几部分,这样我们就可以更好的理解怎么制作更小的JPG文件。
JPG的压缩方案被分成几个步骤,下图很好的把它们展示出来,我们接下来会对每个步骤作详细介绍。
有损数据压缩的一个关键原则是:人类的感知能力没有计算机的精确。科学证明,人的眼睛只能区分1000万种不同的颜色。然而,还有很多东西会影响人眼对色彩的感知,明显的突出色彩错觉,又或你应该还记得去年这件衣服把互联网都炸开了。这里的重点是人眼可以很好的控制它所感知的颜色。
量化是有损压缩种的一种形式,不过JPG使用的是一种不同的方式:颜色模型。一个色彩空间是一个特定的颜色组织,它的颜色模型代表了这些颜色表示的数学公式。(例如三颜色RGB,或者CMYK)
这个过程的强大之处是,你可以把一个颜色模型转换成另外一个,这意味着你可以将一个给定颜色的数学表达式转成一组完全不同的值。
例如,下面这种的颜色,它可以用RGB和CMYK两张不同的颜色模型指替,它们对于人眼来说是一样的颜色,但却可以被表示成不同的数值集。
将JPG从RGB颜色模型转换成Y,Cb,Cr颜色模型,包括亮度(Y)、色度蓝(CB)和色度红(CR)。这样做的原因,是心理视觉实验(又名:大脑处理如何处理眼睛看到的信息)表明,人眼对亮度比色度更敏感,这意味着我们会忽略掉较大的色度变化,而不影响我们对图片的感知。因此,我们可以在人眼察觉前对CbCr通道使劲的压缩。
YCbCr颜色空间其中一个有趣的细节是,Cb/Cr通道有比较少的颗粒度细节,也就是说它们包含的信息没有Y通道的多。所以JPG的算法就把Cb和Cr通道的大小缩减到原来的1/4(注意,在具体的处理上会有些细微的差别,但我这里就不展开了…),这种做法就叫做缩减取样。
有一件很重要的事需要说明,缩减取样是一个有损压缩过程(压缩过后你无法恢复回原来的颜色,只能得到一个近视的值),但对人类视觉皮层的可视化组件的整体影响是最小的。由于我们没有对亮度(Y)做处理,只对CbCr通道做缩减,所以对人的视觉系统的影响较低。
从现在开始,在JPG上做的所有操作都是基于8x8像素块。这样做是因为我们通常认为8x8像素块里没有太多的差异,即使是很复杂的图片,局部区域的像素也往往相似,这种相似性有助于之后我们对它压缩。
在这里我们需要注意一点,我们要介绍JPG编码的常见”神器”之一。”色彩渗透”是沿着锋利边缘的颜色可以”渗透”到另一边。这是因为色度通道,它代表像素的颜色,平均到单个颜色需要4个像素1块,而有些块穿过了锋利边缘。
到现在为止,这些步骤都平淡无奇。色彩空间,缩减取样,分块处理在图片数据压缩领域都是小儿科。但是现在…真正的数学要上场了。
DTC(离散余弦转换)的关键是,它假设任何一个数字信号都可以被一个余弦组合函数重建。
例如,如果我们有下面这个坐标图:
你可以看到它实际上是cos(x)+cos(2x)+cos(4x)相加的结果
或许更好的展示这个过程,是在一个平面上给定一组余弦函数来真实的解码一张图片。为了展示这个过程,我贴了互联网上最惊人之一的GIF图片:在一个平面上用余弦函数来编码一块8x8像素。
上面是一张正在重建的图片(最左边那个区域)。每一帧我们都使用右侧面版新的基准值,来乘一个权重值(右侧区域的文字)来产生图片(中间区域)。
如你所见,靠着带权重的不同余弦值相加,我们可以重构出我们的原图。(这多屌啊~)
这个的基础背景知识是离散余弦变换原理。核心思想是,在不同频率上,8x8块都可以由一组权重余弦变化的和代表。这里的重点是算出要输入的余弦值,和它们的权重。
想要得出”使用什么余弦值”是很容易的,经过大量的测试之后,选出一组最好的余弦值,它们是我们的基础函数,下图是形象化的过程。
而”它们怎么被加权在一起”的问题也很简单,直接套用下面的公式。
我就不介绍这些值得含义了,你可以在这个维基页面查看它们含义。
每个色彩通道的8x8像素块都会用上面这个公式和基础函数生成新的一个8x8矩阵,表示在这个过程中使用的权重。下面是一张表示这个过程的图片:
这个矩阵G,用来代表重建图片的基础权重(在动画右侧上方的小十进制数)。对于每个基础余弦值,我们都将它乘与矩阵里的权重,然后全部相加,得到我们最终的图片。
基于这点,我们就不再操作颜色空间而直接处理G矩阵(基础权重),之后所有的压缩操作都直接在这个矩阵上做。
这里的问题是我们现在将字节对齐的整数转成真实的数字。这样会很容易膨胀我们的信息(从1byte变成一个float(4个字节))。为了解决这问题,压缩得更极致,我们将进入量化阶段。
我们不想压缩浮点数据,它会膨胀我们的信息流,并且不高效。因此,我们要找一种方法来将权重矩阵转换到0-255这范围里。我们可以直接在矩阵里将最小/最大值(分别是-415.38和77.13)除以这个范围来得到一个在0-1的值,再将这个值乘以255得到最后的值。
例如:[34.12- -415.38] / [77.13 — -415.38] *255= 232
这样做可以,但代价是精度会显著下降。这种缩放将产生一个分布不均匀的值,其结果是会导致图片质量的下降。
JPG用了另外一种方法。不同于使用矩阵的范围来作为缩放值,JPG用了一个可量化的预矩阵。这种可量化矩阵不需要成为数据流的一部分,它们可成为编码的一部分。
这个例子展示了对于每一张图片使用的量化因子矩阵:
现在我们使用Q和G矩阵,来计算我们的量化DCT系数矩阵:
例如,用G矩阵的[0,0]=-415.37和Q[0,0]=16这两个数字:
得到最后的矩阵是:
你看现在这个矩阵多变成多简单,它现在包含了大量的小整数或者0,这会有助于压缩。
简单点说,我们把这个过程分别应用在Y,CbCr通道,这样我们需要两个不同的矩阵,一个是Y通道,另一个是C通道:
量化压缩有两种重要的方式:
1. 限制可用范围的权重,减少数字的位数和替换它们。
2. 将大部分的权重变成同一个数字或者0,提高第三步压缩,熵编码。
这样的量化方式主要来源于JPEG。因为图像的右下方往往有最大的量化因子,JPEG倾向于将这些图像组合。量化因子矩阵可以通过改变JPEG的’质量值’直接控制。(稍后介绍)
现在我们可以回到整数世界了,将目光转移到对颜色块做有损压缩阶段。当看我们的转化数据,你应该会注意到一些有趣的现象:
从左上角到右下角,0出现的数量急速上升。但主行和主列的顺序并不理想,因为这些0都交织在一起,而不是放在一起。
相反,我们可以从左上角开始以Z字形来回穿梭直到右下角的方式遍历整个矩阵。
我们的亮度矩阵的顺序变成:
−26,−3,0,−3,−2,−6,2,−4,1,−3,1,1,5,1,2,−1,1,−1,2,0,0,0,0,0,-1,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
一旦数据变成这个格式,下一步就简单了:按顺序执行RLE,然后对结果进行统计编码(霍夫曼/算术/ANS)。
Boom.你的数据块变成JPG编码了。
现在你已经理解JPG文件是怎样创建的,是该重新审视图片质量参数这个概念了,你通常会在PhotoShop导出JPG图片时会看到。(或在其他地方)
这个参数我们称之为q,是一个从1到100的整数。你应该把q当作一个测量图片质量的值:q越大表示图片的质量越高,当然文件就越大。
这个质量值用在量化阶段,用来缩放合适的量化因子。因此对于每个基准权重,量化阶段现在类似于* round(Gi,k / alpha*Qi,k)。*
其中alpha符号用来当作质量参数。
当alpha或Q[x,y]越大(当alpha的值越大那么q参数的值就越小),更多信息是丢失,文件就越小。
所以,如果你想要靠更多的视觉假象来得到一张更小的图片,那么你可以在到处图片阶段设置一个更小的质量值。
注意上图,那张质量为1的图片里,我们可以清晰的看到在成块阶段和量化阶段的痕迹。
更重要的是质量参数大小取决于具体的图片。由于每张图片都是唯一的,它们都有不同的展示效果,那么Q值也是唯一的。
由于你已经理解了JPG的算法工作原理,一些事情就变得清晰了:
如果你想要自己处理这么多东西,那么这个1000行不到文件可以帮你搞定这些问题。
PS:由于本文涉及到的数学算法比较多,本人水平有限,如有译错,请留言讨论。