JPG的压缩原理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jacke121/article/details/79948328

JPG的压缩原理

整理了两篇文章:http://www.360doc.com/content/17/0901/18/41193811_683881904.shtml和

      原文:https://blog.csdn.net/asdzheng/article/details/51779038

本篇文章翻译自谷歌出的优化视频里面的光头佬(Colt McAnlis),原文地址需翻墙, 以下正文:

JPG格式是1992年出现的最先进的图片压缩技术之一。此后,它就成为互联网图片的主力。这当然和JPG背后的技术有关,JPG的工作原理异常复杂,它需要深入理解人眼是如何调整对色彩和图像边缘的感知。

在研究这些东西前(你也一样,如果你在阅读此文),我想把JPG的编码原理分解成几部分,这样我们就可以更好的理解怎么制作更小的JPG文件。

概要

JPG的压缩方案被分成几个步骤,下图很好的把它们展示出来,我们接下来会对每个步骤作详细介绍。

1.png

色彩空间转换

有损数据压缩的一个关键原则是:人类的感知能力没有计算机的精确。科学证明,人的眼睛只能区分1000万种不同的颜色。然而,还有很多东西会影响人眼对色彩的感知,明显的突出色彩错觉,又或你应该还记得去年这件衣服把互联网都炸开了。这里的重点是人眼可以很好的控制它所感知的颜色。

量化是有损压缩种的一种形式,不过JPG使用的是一种不同的方式:颜色模型。一个色彩空间是一个特定的颜色组织,它的颜色模型代表了这些颜色表示的数学公式。(例如三颜色RGB,或者CMYK)

这个过程的强大之处是,你可以把一个颜色模型转换成另外一个,这意味着你可以将一个给定颜色的数学表达式转成一组完全不同的值。

例如,下面这种的颜色,它可以用RGB和CMYK两张不同的颜色模型指替,它们对于人眼来说是一样的颜色,但却可以被表示成不同的数值集。

2.png

将JPG从RGB颜色模型转换成Y,Cb,Cr颜色模型,包括亮度(Y)、色度蓝(CB)和色度红(CR)。这样做的原因,是心理视觉实验(又名:大脑处理如何处理眼睛看到的信息)表明,人眼对亮度色度更敏感,这意味着我们会忽略掉较大的色度变化,而不影响我们对图片的感知。因此,我们可以在人眼察觉前对CbCr通道使劲的压缩。

3.png

缩减取样

YCbCr颜色空间其中一个有趣的细节是,Cb/Cr通道有比较少的颗粒度细节,也就是说它们包含的信息没有Y通道的多。所以JPG的算法就把Cb和Cr通道的大小缩减到原来的1/4(注意,在具体的处理上会有些细微的差别,但我这里就不展开了…),这种做法就叫做缩减取样

有一件很重要的事需要说明,缩减取样是一个有损压缩过程(压缩过后你无法恢复回原来的颜色,只能得到一个近视的值),但对人类视觉皮层的可视化组件的整体影响是最小的。由于我们没有对亮度(Y)做处理,只对CbCr通道做缩减,所以对人的视觉系统的影响较低。

4.png

图像分成8x8像素块

从现在开始,在JPG上做的所有操作都是基于8x8像素块。这样做是因为我们通常认为8x8像素块里没有太多的差异,即使是很复杂的图片,局部区域的像素也往往相似,这种相似性有助于之后我们对它压缩。

在这里我们需要注意一点,我们要介绍JPG编码的常见”神器”之一。”色彩渗透”是沿着锋利边缘的颜色可以”渗透”到另一边。这是因为色度通道,它代表像素的颜色,平均到单个颜色需要4个像素1块,而有些块穿过了锋利边缘。

离散余弦变换

到现在为止,这些步骤都平淡无奇。色彩空间,缩减取样,分块处理在图片数据压缩领域都是小儿科。但是现在…真正的数学要上场了。

DTC(离散余弦转换)的关键是,它假设任何一个数字信号都可以被一个余弦组合函数重建。

例如,如果我们有下面这个坐标图:

5.png

你可以看到它实际上是cos(x)+cos(2x)+cos(4x)相加的结果

6.png

或许更好的展示这个过程,是在一个平面上给定一组余弦函数来真实的解码一张图片。为了展示这个过程,我贴了互联网上最惊人之一的GIF图片:在一个平面上用余弦函数来编码一块8x8像素。

7.gif

上面是一张正在重建的图片(最左边那个区域)。每一帧我们都使用右侧面版新的基准值,来乘一个权重值(右侧区域的文字)来产生图片(中间区域)。

如你所见,靠着带权重的不同余弦值相加,我们可以重构出我们的原图。(这多屌啊~)

这个的基础背景知识是离散余弦变换原理。核心思想是,在不同频率上,8x8块都可以由一组权重余弦变化的和代表。这里的重点是算出要输入的余弦值,和它们的权重。

想要得出”使用什么余弦值”是很容易的,经过大量的测试之后,选出一组最好的余弦值,它们是我们的基础函数,下图是形象化的过程。

8.png

而”它们怎么被加权在一起”的问题也很简单,直接套用下面的公式。

9.png

我就不介绍这些值得含义了,你可以在这个维基页面查看它们含义。

每个色彩通道的8x8像素块都会用上面这个公式和基础函数生成新的一个8x8矩阵,表示在这个过程中使用的权重。下面是一张表示这个过程的图片:

10.png

这个矩阵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用了一个可量化的预矩阵。这种可量化矩阵不需要成为数据流的一部分,它们可成为编码的一部分。

这个例子展示了对于每一张图片使用的量化因子矩阵:

11.png

现在我们使用Q和G矩阵,来计算我们的量化DCT系数矩阵:

12.png

例如,用G矩阵的[0,0]=-415.37和Q[0,0]=16这两个数字:

13.png

得到最后的矩阵是:

14.png

你看现在这个矩阵多变成多简单,它现在包含了大量的小整数或者0,这会有助于压缩。

简单点说,我们把这个过程分别应用在Y,CbCr通道,这样我们需要两个不同的矩阵,一个是Y通道,另一个是C通道:

15.png

量化压缩有两种重要的方式: 
1. 限制可用范围的权重,减少数字的位数和替换它们。 
2. 将大部分的权重变成同一个数字或者0,提高第三步压缩,熵编码。

这样的量化方式主要来源于JPEG。因为图像的右下方往往有最大的量化因子,JPEG倾向于将这些图像组合。量化因子矩阵可以通过改变JPEG的’质量值’直接控制。(稍后介绍)

压缩

现在我们可以回到整数世界了,将目光转移到对颜色块做有损压缩阶段。当看我们的转化数据,你应该会注意到一些有趣的现象:

16.png

从左上角到右下角,0出现的数量急速上升。但主行和主列的顺序并不理想,因为这些0都交织在一起,而不是放在一起。

相反,我们可以从左上角开始以Z字形来回穿梭直到右下角的方式遍历整个矩阵。

17.png

我们的亮度矩阵的顺序变成:

−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符号用来当作质量参数。

20.gif

当alpha或Q[x,y]越大(当alpha的值越大那么q参数的值就越小),更多信息是丢失,文件就越小。

所以,如果你想要靠更多的视觉假象来得到一张更小的图片,那么你可以在到处图片阶段设置一个更小的质量值。

19.png

注意上图,那张质量为1的图片里,我们可以清晰的看到在成块阶段和量化阶段的痕迹。

更重要的是质量参数大小取决于具体的图片。由于每张图片都是唯一的,它们都有不同的展示效果,那么Q值也是唯一的。

总结

由于你已经理解了JPG的算法工作原理,一些事情就变得清晰了:

  1. 要获取每张图片正确的质量值,重要是在视觉效果和文件大小之间做权衡
  2. 因为这个过程是基于块的,伪影发生在块效应或”振铃效应”
  3. 因为处理的块彼此不互相混合,JPG通常会忽略压缩大片相似块。顺便说一下,WebP格式比较擅长解决这种问题。

如果你想要自己处理这么多东西,那么这个1000行不到文件可以帮你搞定这些问题。


JPG格式的图片体积相对较小,是因为它采用了一系列的压缩算法,压缩图片弊端就是和原始的图片相比,它牺牲掉了一些画面细节,这些丢失的细节或许可被人的肉眼看出,或许以人的肉眼难以发现,对于这种通过牺牲画面的精细程度来达到缩小体积的目的的压缩算法,我们称之为“有损压缩”或者“破坏性压缩”。

JPEG和JPG的关系

你可能会有这样的疑惑,JPEG和JPG看起来如此相像,它们到底是不是同一种图片格式?JPEG和JPG之间的关系到底是怎样的?在回答这个问题之前,我们首先要了解,JPEG的来头。

JPEG,全称为“Joint Photographic Experts Group”,翻译成中文,则是“联合图像专家小组”,这是一个成立于1986年的组织,1992年,该组织发布了“JPEG标准”,这是一种针对图像的压缩而制定的标准。

使用JPEG标准压缩的图片文件,被称为“JPEG文件”,这种文件的扩展名通常是JPG、JPEG、JPE、JFIF以及JIF,在这些文件格式中,以JPG的使用最为广泛。

如果这里JPEG指的是联合图像专家小组,那JPEG与JPG则是制定压缩标准的组织与采用该组织制定的压缩标准压缩成的图片的一种的格式的关系;

如果JPEG指的是JPEG压缩标准,那JPEG与JPG则是一种图像的压缩标准与采用该标准压缩成的图片的一种格式的关系;

如果JPEG指的是一张图片文件的后缀名,那JPEG与JPG的关系则是采用JPEG标准压缩的图片的两种不同的格式。

色彩空间转换

要压缩图片,首先要知道这个图片中都包含了些什么内容,在对图片的内容进行分解时,第一步就要进行色彩空间转换。

所谓的色彩空间,指的是描述图像的颜色的一组数值,比较常见的色彩空间有RGB、CMYK。

RGB,即是分别用三组数值,来表示红、绿、蓝,而红、绿、蓝三种颜色经过不同程度的配比,就会显示出不同的颜色。通常RGB的色彩模型用于显示屏的显示。

CMYK,即是分别用四组数值,来表示青色、品红、黄色和黑色,而青色、品红、黄色和黑色四种颜色经过不同程度的配比,就会显示出不同的颜色。通常CMYK的色彩模型用于印刷。

在JPEG压缩图像过程中,是怎么用数值来表示图像内容的呢?事实上,JPEG量化图像的颜色时并非采用RGB模式,也非CMYK模式,而是YCbCr模式,其中,Y表示的是亮度,Cb表示的是彩度(蓝),Cr表示的是彩度(红)。那么问题来了,为什么JPEG在压缩图像时,不采用RGB和CMYK的色彩模型,而偏偏采用YCbCr这种看似奇葩的模式呢?这还要从人眼的工作机制谈起。

我们的眼睛之所以能感知图像,是因为人眼内含有视锥细胞和视杆细胞,其中,视锥细胞具有感知颜色的能力,而视杆细胞具有感知亮度的能力,通常,我们的眼睛中,视杆细胞数量相对较多,所以人眼对亮度的敏感程度要高于对色彩的敏感程度。就像你熄灯时,你可以在暗光下渐渐地看清周围的事物,而对周围事物的颜色,你可能就不那么敏感了。

JPEG正是利用了人眼的这一特性,在压缩图像时,将亮度和颜色分开处理。

由于人眼对亮度很敏感,所以JPEG不会对亮度做太多改变,而人眼对颜色不甚敏感(科学研究表明,人眼大概可以区分出1000万种不同的颜色,这种感知能力相比于电脑,就没那么精确了),所以在人眼开始察觉色彩不对了之前,JPEG对颜色进行压缩处理,这样就算图像损失了部分细节,人眼也不太容易捕捉得到。

JPEG在压缩图像时所进行的色彩空间转换,指的就是将RGB转换为YCbCr。

缩减取样

在YCbCr模型中,Cb通道和Cr通道中所包含的信息量远远少于Y通道中包含的信息量,同时,人眼对色彩的敏感程度有限,因此,JPEG的压缩算法主要对Cb和Cr通道中的数据进行缩减取样,取样的比例可以是4:4:4(无缩减取样)、4:2:2(在水平方向2的倍数中取样)和4:2:0(在水平方向和垂直方向的2的倍数中取样),其中,以4:2:0最为常见。

离散余弦变换(DCT)

通常我们认为,在8*8像素的一块方格里,它里面的像素往往非常相似,因此,当进行到这一步时,JPEG会将图像分为一个又一个的8*8的像素块。

▲一个8*8的像素块,图片来自维基百科

每一个像素块都利用离散余弦变换来编码,法国数学家傅里叶告诉我们,几乎所有的周期函数,都可以用一系列的“弦波”来表示,也就是说,靠着带权重的一系列不同余弦值的相加,就可以重构出我们的原图。最后,每个8*8的像素块都会通过特定的函数,来生成一个新的8*8的数字矩阵。

▲一个8*8的数字矩阵,图片来自维基百科

量化

事情到这里还没算完,通过离散余弦变换所得到的数字可不能被直接压缩,他们还需要再处理一下,这就是量化。

量化的过程,实际上就是对DTC系数的一个优化过程,在一个8*8像素的区域中,每个像素点间的差异都很大时,它的弦波频率就很高,我们称之为高频区,相反地,一个8*8像素的区域中,每个像素点间的差异很小,那它的弦波频率就很低,我们称之为低频区,刚刚的DCT算法已经把哪里频率高、哪里频率低给整理出来了。

▲越接近左上,频率越低,越接近右下,频率越高。

人眼对高频区(小范围、高复杂度)的辨识能力较差,而对低频区(大范围、低复杂度)的辨识能力较好,因此JPEG就根据人眼的这一特征将高频区进行大幅的简化和压缩,量化的过程,实际上就是把频率领域上的每个成分,除以一个特定的常数,然后将计算结果四舍五入,取一个整数,JPEG会将高频区的成分通过算法,使其接近于0,然后四舍五入,取该成分的值为0,最后,我们大概会得到这样一个矩阵:

▲图片来源:维基百科

可以看到,这个矩阵中有很多连续的0,这就对压缩非常有利了。

熵编码

终于到了最后一步了,那就是压缩,仔细观察刚刚得到的最终的矩阵,可以看到,从左上角到右下角,连续的0的数量急剧上升,这种情况就要用熵编码技术,对数据进行编码。

JPEG从左上角开始,以Z字形来回穿梭,直至经历了矩阵中的所有数字,到达右下角。

▲Z字形穿梭扫描的路径,图片来自维基百科

此时的编码就变成了这样:

当剩下的数字都是0,且过早结束的编码,可以将连续的0的部分采用霍夫曼编码表示为“EOB”,最后,这串编码就成了这个样子:

现在,我们就得到了JPEG的编码了。通过一系列的处理,可以看到,图像中的信息达到了压缩和简化的目的。这就是一幅原始图像被压缩为JPEG的大概过程。

图片质量

在生成一张JPG图像文件时,你通常需要设置图像质量参数,这个参数的数值越大,图像的质量也就越高,同时图片文件的体积也就越大,相反地,数值越小,图像的质量就越低,同时图片文件的体积越小,下面是三张图片:

▲图片一

▲图片二

▲图片三

第一张图片的质量参数是100,第二张图片的质量参数是60,第三张图片的质量参数是20,很容易可以看出,第一张图片的细节较为丰富,第二张图片的画面中好像稍微有一些噪点,第三章图片的直接可以看到大块的马赛克了。

代码示例

现在你已经简单了解了JPEG算法的工作原理,在GitHub中有这样一个代码示例,其作用就是进行JPEG压缩,你可以结合这个代码示例对JPEG压缩算法进行进一步的学习和研究。

代码示例:https://github.com/richgel999/jpeg-compressor

好的,以上是IT之家针对你的提问的解答,更多常识科普,实用教程,请关注IT之家。


阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页