五光十色——JPEG压缩原理
JPEG压缩原理
1 JPEG简介
(1)JPEG(Joint Photographic Experts Group)是联合图像专家组的英文缩写。该专家组于1986年由国际电话与电报咨询委员会(CCITT)与国际标准化组织(ISO)联合成立,负责制定静态数字图像的编码标准;
(2)JPEG标准于1993年公布,是第一个灰度及彩色静止图像压缩的国际标准;
(3)由于JPEG优良的品质,被广泛应用于互联网和数码相机领域,网站上80%的图像都采用了JPEG压缩标准。
2 压缩原理
不同的颜色模型各有不同的应用场景,例如RGB模型适合于像显示器这样的自发光图案,而在印刷行业,使用油墨打印,图案的颜色是通过在反射光线时产生的,通常使用CMYK模型,而在JPEG压缩算法中,需要把图案转换成为YCbCr模型,这里的Y表示亮度(Luminance),Cb和Cr分别表示绿色和红色的“色差值”。
有损压缩首先要做的事情就是“把重要的信息和不重要的信息分开”,YCbCr恰好能做到这一点。对于人眼来说,图像中明暗的变化更容易被感知到,这是由于人眼的构造引起的。视网膜上有两种感光细胞,能够感知亮度变化的视杆细胞,以及能够感知颜色的视锥细胞,由于视杆细胞在数量上远大于视锥细胞,所以我们更容易感知到明暗细节。既然Cb成份和Cr成份的数据比较相对不重要,就可以只取部分数据来处理,以增加压缩的比例。
JPEG采用的是YCbCr色彩系统,但RGB色彩系统是我们最常用的表示颜色的方式。想要用JPEG基本压缩法处理RGB图像,需要进行格式转换。
Y = 0.2990R+0.5870G+0.1140B;
Cb = -0.1687R-0.3313G+0.5000B;
Cr = 0.5000R-0.4187G-0.0813B;
3 算法流程
JPEG压缩编码和解码是互逆过程,其中编码过程如下图所示。后面章节中,对各个步骤进行详细说明。
对于YUV图像,Y和UV的处理是分别进行的,为便于说明,本文仅对Y分量进行处理。
图 1 JPEG编码过程
3.1 将原始图像划分为8*8小块
JPEG算法的第一步,图像被分割成大小为8*8的小块,这些小块在整个压缩过程中都是单独被处理的。
图 2 将原始图像划分为8*8小块
3.2 离散余弦变换DCT(Discrete Cosine Transform)
离散余弦变换是傅里叶变换的另外一种形式,DCT变换具有以下的性质,正是利用这些性质,我们将其用到JPEG压缩算法中:
(1)图像经DCT变换以后,DCT系数之间的相关性就会变小。而且大部分能量集中在少数的系数上,再采用适当的量化和熵编码便可以有效地压缩图像。因此,DCT变换在图像压缩中非常有用,是有损图像压缩国际标准JPEG的核心;
(2)如果原始信号是图像等相关性较大的数据的时候,我们可以发现在变换之后,系数较大的集中在左上角,而右下角的几乎都是0,其中左上角的是低频分量,右下角的是高频分量,低频系数体现的是图像中目标的轮廓和灰度分布特性,高频系数体现的是目标形状的细节信息;
(3)信息论的研究表明,正交变换不改变信源的熵值,变换前后图像的信息量并无损失,完全可以通过反变换得到原来的图像值。但图像经过正交变换后,把原来分散在原空间的图像数据在新的坐标空间中得到集中,对于大多数图像而言,大量的变换系数很小,只要删除接近于0的系数,并对较小的系数进行粗量化,而保留包含图像主要信息的系数,以此进行压缩编码。在重建图像进行解码(逆变换)时,所损失的将是些不重要的信息,几乎不会引起图像失真,图像的变换编码就是利用这些来压缩图像并得到很高的压缩比;
本步骤中, DCT将信号从时域变换到频域,得到一个8*8的频率系数矩阵。例如,某个数据块进行DCT变换前后如下图所示。
图 3 DCT变换(左侧:原始数据,右侧:频率系数矩阵)
DCT得到的在8*8的频率系数矩阵中有1个直流(DC)分量和63个交流(AC)分量。DC分量位于最左上角,越靠近右下角,AC分量对应频率越高。可以看出,低频域的系数大于高频域的系数。图像信息的大部分集中于直流系数及其附近的低频频谱上,离DC系数越来越远的高频频谱几乎不含图像信息,甚至于只含杂波。
3.3 量化(Quantization)
DCT变换是一个无损压缩变换,它实际上并不实现压缩,量化是“有损压缩”的第一步。
量化方法:事先建立一个8*8的量化表,将频率系数矩阵中的各元素除以对应量化表中的元素,并对结果舍入取整(取最接近的整数)。
JPEG对图像中Y和UV采用不同的量化表,下图给出了Y和UV标准量化表。这两张量化表也是有讲究的,不同位置的值代表了图像数据中不同频率的分量,表中的数据是人们根据人眼对不同频率的敏感程度的差别所积累下的经验制定的,一般来说人眼对于低频的分量必高频分量更加敏感,所以两张量化系数矩阵左上角的数值明显小于右下角区域。
图 4 标准量化表(左:Y,右:UV)
不同量化表对应不同的压缩/还原质量,下图直观给出了效果。各图中分别给出了DCT,量化及其逆变换的效果。可以看出,源图像与恢复后的图像数值存在差异,但该差异很难被人眼察觉。
3.4 Zigzag扫描排序
上面提到过,在8*8的频率系数矩阵中有1个直流(DC)分量和63个交流(AC)分量。此处对DC和AC分量采用不同的存储方式。
3.4.1 DC分量编码
JPEG中对DC分量采用差分编码DPCM,所记录的数据是当前块的DC系数与前一个块的DC系数之差DIFF。特殊的,第一个块存储的是DC系数的绝对值。
例如:某块中的DC分量为15,之前一块DC分量为12,则DIFF=15-12=3。
通常图像的两个相邻的8×8子块的DC系数相差很小,所以采用DPCM可以提高压缩比。
3.4.2 AC分量编码
为了保证低频分量先出现,高频分量后出现,以增加行程中连续“0”的个数,这63个元素采用了Z形(Zig-Zag)的排列方法,如下图所示。
图 5 Z形排列方法
这么做的目的只有一个,就是尽可能把0放在一起,由于0大部分集中在右下角,所以才去这种由左上角到右下角的顺序。下面的某量化后的矩阵,经过这种顺序变换变成一个整数数组。
图 6 某量化后频率系数矩阵
{-26,-3,0,-3,-2,-6,2,-4,1,-3,0,1,5,1,2,-1,1,-1,2,0,0,0,0,0,-1,-1,0,0,0,0,…,0,0}
(PS: 此处的DC系数按照绝对值处理。)
3.5 Huffman编码
JPEG压缩的最后一步是对数据进行霍夫曼编码(Huffman coding),霍夫曼编码几乎是所有压缩算法的基础,它的基本原理是根据数据中元素的使用频率,调整元素的编码长度,以得到更高的压缩比。
3.5.1 Huffman编码实例
举个简单的Huffman编码的例子:现有下面这段数据
“AABCBABBCDBBDDBAABDBBDABBBBDDEDBD”
这段数据里面包含了33个字符,每种字符出现的次数统计如下
字符 | A | B | C | D | E |
次数 | 6 | 15 | 2 | 9 | 1 |
如果我们用常见的定长编码,每个字符都是3个bit。
字符 | A | B | C | D | E |
编码 | 001 | 010 | 011 | 100 | 101 |
那么这段文字共需要3*33 = 99个bit来保存,但如果我们根据字符出现的概率,使用如下的编码
字符 | A | B | C | D | E |
编码 | 110 | 0 | 1110 | 10 | 1111 |
那么这段文字共需要3*6 + 1*15 + 4*2 + 2*9 + 4*1 = 63个bit来保存,压缩比为63%,Huffman编码一般都是使用二叉树来生成的,这样得到的编码符合前缀规则(较短的编码不能够是较长编码的前缀),比如上面这个编码,就是由下面的这颗二叉树生成的。
图 7 Huffman编码对应二叉树
3.5.2 JPEG编码实例
回到JPEG压缩上,我们现在要处理的数据是一串一维数组,举例如下:
①原始数据 | 35,7,0,0,0,-6,-2,0,0,-9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,…,0 |
对数据采用行程编码(游程编码Run-length encode, RLE)。在实际的压缩过程中,数据中的0出现的概率非常高,所以首先要做的事情,是对其中的0进行处理,把数据中的非零的数据,以及数据前面0的个数作为一个处理单元。如果其中某个单元的0的个数超过16,则需要分成每16个一组,如果最后一个单元全都是0,则使用特殊字符“EOB”表示,EOB意思就是“后面的数据全都是0”。
①原始数据 | 35,7,0,0,0,-6,-2,0,0,-9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,…,0 | |||||||
②RLE编码 | 35 | 7 | 0,0,0,-6 | -2 | 0,0,-9 | 0,0,…,0 | 0,0,8 | 0,0,…,0 |
(0,35) | (0,7) | (3,-6) | (0,-2) | (2,-9) | (15,0) | (2,8) | EOB |
接下来我们要处理的是括号里右面的数字,这个数字的取值范围在-2047~2047之间,JPEG提供了一张标准的码表用于对这些数字编码:
Value | Size | Bits | ||
0 | 0 | – | ||
-1 | 1 | 1 | 0 | 1 |
-3,-2 | 2,3 | 2 | 00,01 | 10,11 |
-7,-6,-5,-4 | 4,5,6,7 | 3 | 000,001,010,011 | 100,101,110,111 |
-15,…,-8 | 8,…,15 | 4 | 0000,…,0111 | 1000,…,1111 |
-31,…,-16 | 16,…,31 | 5 | 0 0000,…,0 1111 | 1 0000,…,1 1111 |
-63,…,-32 | 32,…,63 | 6 | 00 0000,… | …,11 1111 |
-127,…,-64 | 64,…,127 | 7 | 000 0000,… | …,111 1111 |
-255,…,-128 | 128,…,255 | 8 | 0000 0000,… | …,1111 1111 |
-511,…,-256 | 256,…,511 | 9 | 0 0000 0000,… | …,1 1111 1111 |
-1023,…,-512 | 512,…,1023 | 10 | 00 0000 0000,… | …,11 1111 1111 |
-2047,…,-1024 | 1024,…,2047 | 11 | 000 0000 0000,… | …,111 1111 1111 |
举例来说,第一个单元中的“35”这个数字,在表中的位置是长度为6的那组,所对应的bit码是“100011”,而“-6”的编码是”001″,由于这种编码附带长度信息,所以我们的数据变成了如下的格式。
①原始数据 | 35,7,0,0,0,-6,-2,0,0,-9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,…,0 | |||||||
②RLE编码 | (0,35) | (0,7) | (3,-6) | (0,-2) | (2,-9) | (15,0) | (2,8) | EOB |
③BIT编码 | (0,6, 100011) | (0,3, 111) | (3,3, 001) | (0,2, 01) | (2,4, 0110) | (15,-) | (2,4, 1000) | EOB |
括号中前两个数字分都在0~15之间,所以这两个数可以合并成一个byte,高四位是前面0的个数,后四位是后面数字的位数。
①原始数据 | 35,7,0,0,0,-6,-2,0,0,-9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,…,0 | |||||||
②RLE编码 | 35 | 7 | 0,0,0,-6 | -2 | 0,0,-9 | 0,0,…,0,8 | 0,0,…,0 | |
35 | 7 | 0,0,0,-6 | -2 | 0,0,-9 | 0,0,…,0 | 0,0,8 | 0,0,…,0 | |
(0,35) | (0,7) | (3,-6) | (0,-2) | (2,-9) | (15,0) | (2,8) | EOB | |
③BIT编码 | (0,6, 100011) | (0,3, 111) | (3,3, 001) | (0,2, 01) | (2,4, 0110) | (15,-) | (2,4, 1000) | EOB |
(0x6,100011) | (0x3,111) | (0x33,001) | (0x2,01) | (0x24,0110) | (0xF0,-) | (0x24,1000) | EOB |
对于括号前面的数字的编码,就要使用到我们提到的霍夫曼编码了,比如下面这张表,就是一张针对数据中的第一个单元,也就是直流(DC)部分的霍夫曼表,由于直流部分没有前置的0,所以取值范围在0~15之间。
Length | Value | Bits |
3 bits | 04 | 000 |
4 bits | 07 | 1110 |
5 bits | 08 | 1111 0 |
6 bits | 09 | 1111 10 |
7 bits | 0A | 1111 110 |
8 bits | 0B | 1111 1110 |
举例来说,示例中的DC部分的数据是0x06,对应的二进制编码是“100”,而对于后面的交流部分,取值范围在0~255之间,所以对应的霍夫曼表会更大一些
Length | Value | Bits |
2 bits | 01 | 00 |
3 bits | 03 | 100 |
4 bits | 00 (EOB) | 1010 |
5 bits | 05 | 1101 0 |
6 bits | 31 | 1110 10 |
… | … | … |
12 bits | 24 | 1111 1111 0100 |
15 bits | 82 | 1111 1111 1000 000 |
16 bits | 09 | 1111 1111 1000 0010 |
这样经过霍夫曼编码,并且序列化后,最终数据如下表所示。
①原始数据 | 35,7,0,0,0,-6,-2,0,0,-9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,…,0 | |||||||||||||
②RLE编码 | 35 | 7 | 0,0,0,-6 | -2 | 0,0,-9 | 0,0,…,0,8 | 0,0,…,0 | |||||||
35 | 7 | 0,0,0,-6 | -2 | 0,0,-9 | 0,0,…,0 | 0,0,8 | 0,0,…,0 | |||||||
(0,35) | (0,7) | (3,-6) | (0,-2) | (2,-9) | (15,0) | (2,8) | EOB | |||||||
③BIT编码 | (0,6, 100011) | (0,3, 111) | (3,3, 001) | (0,2, 01) | (2,4, 0110) | (15,-) | (2,4, 1000) | EOB | ||||||
(0x6,100011) | (0x3,111) | (0x33,001) | (0x2,01) | (0x24,0110) | 0xF0 | (0x24,1000) | EOB | |||||||
④霍夫曼编码 | 100 | 100011 | 100 | 111 | 1111 1111 0101 | 001 | 01 | 01 | 1111 1111 0100 | 0110 | 1111 1111 001 | 1111 1111 0100 | 1000 | 1010 |
⑤序列化 | 100100011100111111111110101001010111111111010001101111111100111111111010010001010 | |||||||||||||
91 CF FE A5 7F D1 BF CF FA 45 | ||||||||||||||
最终我们使用了10个字节的空间保存了原本长度为64的数组,至此JPEG的主要压缩算法结束,这些数据就是保存在jpg文件中的最终数据。
4 总结
(1)DCT本身并不能压缩数据,不会造成信息损失。DCT后的系数较大的集中在左上角(低频分量),而右下角的系数较小(高频分量);
(2)量化是“有损压缩”的第一步。首先,它利用人眼对亮度变化比颜色变化更敏感,因此对CbCr采用了比Y压缩比更高的量化表,然后,它利用了人眼对于低频的分量必高频分量更加敏感,量化表左上角的数值明显小于右下角区域;
(3)Zigzag扫描排序是为了保证低频分量先出现,高频分量后出现,以增加行程中连续“0”的个数;
(4)霍夫曼编码JPEG压缩算法的基础,它根据数据中元素的出现频率调整元素的编码长度,以得到更高的压缩比。
- 点赞
- 收藏
- 关注作者
评论(0)