前言
一直想学习下视频编解码的相关的知识,但它有点过于复杂了,学习曲线陡峭,非常劝退。作为入门基础,先学习学习 jpeg 解码,这样可以对图像压缩技术有个初步的了解,也可以减少对视频编解码技术的恐惧心理。
一、JPEG 简介
2.1 JPEG 的含义
JEPG,即 Joint Photographic Experts Group,它有好几个含义
下面详细解释这几种不同的含义:
1. 组织名称
Joint Photographic Experts Group (联合图像专家组)
- JPEG 最初是一个组织名称,由 ISO 和 IEC 两个国际标准化机构下属的专家工作组组成。
- 他们负责制定静态图片压缩的国际标准。
- 所以,JPEG 最初专指这个联合专家组。
2. 一个标准
JPEG 标准(如 ISO/IEC 10918-1)
- 由 Joint Photographic Experts Group 这个组织制定的数据压缩标准,正式称为 ISO/IEC 10918-1。
- 它规定了静态数字图像(主要是照片)压缩和解压的方法。
- 这个标准包括压缩算法、文件结构、格式兼容性等技术细节。
- “JPEG标准”本身也是一个术语。
3. 一种图像格式(文件格式/文件扩展名)
JPEG 文件格式(通常为 .jpg 或 .jpeg 文件)
- 基于 JPEG 标准压缩算法的具体实现和文件封装方式。
- 这些文件广泛用于数码相机、互联网、手机、图像软件等,扩展名可以是
.jpg
或.jpeg
。 - 这里的 JPEG 已经成为某种“图片文件”的代称。
扩展
除了“JPEG”,你还可能遇到:
- JPEG 2000,是同一组织后续制定的更先进的图片压缩标准;
- JPEG XR,又一个相关的图片标准;
- 不同的“JPEG”工具、库或软件也常用此名。
2.2 JFIF
1. JPEG 文件和 JFIF 的关系
- 我们日常看到的 .jpg 或 .jpeg 图片文件,实际上并不是直接保存的 JPEG 压缩数据,而是用一种封装格式来把图像数据、元数据一并存放起来。
- 这种常见的封装格式,名称叫做 JFIF,全称 JPEG File Interchange Format(JPEG 文件交换格式,ITU-T.871)。
2. JFIF(JPEG File Interchange Format)
定义
- JFIF 是一种基于 JPEG 标准的文件封装格式,目的是规范 JPEG 压缩数据在不同设备和平台上交换、保存、识别的方式。
- 整个图片数据分为多个“段”(segment),包括压缩的图像数据,也可以包含缩略图、分辨率等信息。
作用
- JPEG 标准只定义了压缩算法和数据流,并未规定文件怎么保存、元数据怎么加等问题。
- JFIF 作为实际应用中的解决方案,让不同系统之间能正确识别 JPEG 图片。
文件扩展名
- 虽然底层是 JFIF 格式,扩展名通常还是用 .jpg 或 .jpeg,所以一般用户根本不需要关心“JFIF”这三个字母。
3. 常见的疑惑
- 有些软件会在文件属性里把图片类型写作“JFIF”而不是“JPEG”,其实文件本质上就是 JPEG 压缩的、按 JFIF 封装的图片,没有本质不同。
- 另外还有一个比较少见的 Exif 封装格式(主要用于数码相机图片,能保存拍摄设备、时间等信息),也是建立在 JPEG 标准上的一种结构。
二、JPEG 编码算法和流程
了解 rgb 数据是如何通过 jpeg 算法进行压缩的,以便后续进行拟操作进行解码。
2.1 RGB 转 YUV
图片中每个像素我们常用 RGB 来表示,但 jpeg 则使用 YUV 进行编码(YUV了解的话,看这里 YUV 文件读取、显示、缩放、裁剪等操作教程)。因此第一步通常是将 rgb 数据转换为 yuv,公式如下:
Y
=
0.299
∗
R
+
0.587
∗
G
+
0.114
∗
B
C
b
=
−
0.1687
∗
R
−
0.3313
∗
G
+
0.5
∗
B
+
128
C
r
=
0.5
∗
R
−
0.4187
∗
G
−
0.0813
∗
B
+
128
Y = 0.299 * R + 0.587 * G + 0.114 * B \\ Cb = -0.1687 * R - 0.3313 * G + 0.5 *B + 128 \\ Cr = 0.5 * R - 0.4187 * G - 0.0813 * B + 128 \\
Y=0.299∗R+0.587∗G+0.114∗BCb=−0.1687∗R−0.3313∗G+0.5∗B+128Cr=0.5∗R−0.4187∗G−0.0813∗B+128
2.2 色度子采样
其实就是 YUV 文件读取、显示、缩放、裁剪等操作教程 这里提到的 “Chroma subsampling”。
压缩效果:
4:4:4:无压缩(100%数据)
4:2:2:色度数据减少50%,总体约33%压缩
4:2:0:色度数据减少75%,总体约50%压缩
通过转换 yuv 格式,进行初步的数据压缩。
2.3 电平位移
由于JPEG每个通道只允许8位,所以 𝑌、𝐶𝑏 和 𝐶𝑟 分量的取值范围都是0-255。在进入编码过程的下一步之前,这些数值必须经过电平平移,将它们的中心移到0,也就是将范围变为 [−128,128]。
这一步可以让数据的平均值在 0 附近,这样数据中就会出现很多 0,而 0 是一种很容易被压缩的数据。
2.4 对最小编码单元(MCU)进行离散余弦变换(DCT)
在JPEG压缩中,并不是直接对每个像素(即YCbCr分量)做编码,而是先将这些像素分块(一般每8x8个像素为一块,称为最小编码单元MCU)。
然后,对每个8x8分块的数据应用离散余弦变换(DCT)。离散余弦变换的作用,是把图像数据从“空间域”(像素明暗的直接表示)转为“频率域”(用不同的“空间频率”来描述图像——比如:一块区域整体明亮、细节变化剧烈或平缓等都属于不同频率内容)。就像音频可以用高音、低音成分组合,图像分块也可以用低频(大致明暗)和高频(精细细节)成分之和来描述。JPEG采用DCT,因为它计算简单,压缩效率高,并且非常适合人眼的视觉特性。得到的DCT系数,低频分量通常能保留大部分图像信息,高频分量(对应细微变化)很多可以近似为零,这也是压缩的关键。
1. MCU 扩展
图像被细分为8x8像素的小块的过程中,如果图像尺寸不是8的倍数,那么图像边缘会被“扩展”到最近的8的倍数。例如,如果图像大小为317x457,则会被转换为320x464。这个扩展通常是通过复制最右边的一列像素来水平扩展,通过复制最下边的一行像素来垂直扩展。
2. DCT 公式
DCT 公式如下,要理解它可以参考 这个视频(But what is the Fourier Transform? A visual introduction.)
F
D
C
T
:
S
v
u
=
1
4
C
u
C
v
∑
x
=
0
7
∑
y
=
0
7
s
y
x
cos
(
2
x
+
1
)
u
π
16
cos
(
2
y
+
1
)
v
π
16
\mathsf{FDCT}: S_{vu} = \frac{1}{4} C_u C_v \sum_{x=0}^{7} \sum_{y=0}^{7} s_{yx} \cos{\frac{(2x+1)u\pi}{16}} \cos{\frac{(2y+1)v\pi}{16}}
FDCT:Svu=41CuCvx=0∑7y=0∑7syxcos16(2x+1)uπcos16(2y+1)vπ
I
D
C
T
:
s
y
x
=
1
4
∑
u
=
0
7
∑
v
=
0
7
C
u
C
v
S
v
u
cos
(
2
x
+
1
)
u
π
16
cos
(
2
y
+
1
)
v
π
16
\mathsf{IDCT}: s_{yx} = \frac{1}{4} \sum_{u=0}^{7} \sum_{v=0}^{7} C_u C_v S_{vu} \cos{\frac{(2x+1)u\pi}{16}} \cos{\frac{(2y+1)v\pi}{16}}
IDCT:syx=41u=0∑7v=0∑7CuCvSvucos16(2x+1)uπcos16(2y+1)vπ
- 𝑢 是水平方向的空间频率
- 𝑣 是垂直方向的空间频率
- 𝐶𝑢, 𝐶𝑣=
- √1/2,如果u,v = 0
- 1,其他
- 𝑠𝑦𝑥 表示(x, y)坐标处的像素值
- 𝑆𝑣𝑢 表示(u, v)坐标处的DCT系数
公式看起来很吓人,但在代码中其实就是一个 for 循环,不用害怕。我们继续看一个具体的例子,下面是一个示例 MCU
[
64
56
56
57
70
84
84
59
66
64
35
36
87
45
21
58
66
66
66
59
35
87
26
104
35
75
76
45
81
37
34
35
45
96
125
107
31
15
107
90
88
89
88
78
64
57
85
81
62
59
68
113
144
104
66
73
107
121
89
21
35
64
65
65
]
% <![CDATA[ \begin{bmatrix} 64 & 56 & 56 & 57 & 70 & 84 & 84 & 59\\ 66 & 64 & 35 & 36 & 87 & 45 & 21 & 58\\ 66 & 66 & 66 & 59 & 35 & 87 & 26 & 104\\ 35 & 75 & 76 & 45 & 81 & 37 & 34 & 35\\ 45 & 96 & 125 & 107 & 31 & 15 & 107 & 90\\ 88 & 89 & 88 & 78 & 64 & 57 & 85 & 81\\ 62 & 59 & 68 & 113 & 144 & 104 & 66 & 73\\ 107 & 121 & 89 & 21 & 35 & 64 & 65 & 65 \end{bmatrix} %]]>
64666635458862107566466759689591215635667612588688957365945107781132170873581316414435844587371557104648421263410785666559581043590817365
我们先进行电位频移(减去 128),得到
[
−
64
−
72
−
72
−
71
−
58
−
44
−
44
−
69
−
62
−
64
−
93
−
92
−
41
−
83
−
107
−
70
−
62
−
62
−
62
−
69
−
93
−
41
−
102
−
24
−
93
−
53
−
52
−
83
−
47
−
91
−
94
−
93
−
83
−
32
−
3
−
21
−
97
−
113
−
21
−
38
−
40
−
39
−
40
−
50
−
64
−
71
−
43
−
47
−
66
−
69
−
60
−
15
16
−
24
−
62
−
55
−
21
−
7
−
39
−
107
−
93
−
64
−
63
−
63
]
% <![CDATA[ \begin{bmatrix} -64 & -72 & -72 & -71 & -58 & -44 & -44 & -69\\ -62 & -64 & -93 & -92 & -41 & -83 & -107 & -70\\ -62 & -62 & -62 & -69 & -93 & -41 & -102 & -24\\ -93 & -53 & -52 & -83 & -47 & -91 & -94 & -93\\ -83 & -32 & -3 & -21 & -97 & -113 & -21 & -38\\ -40 & -39 & -40 & -50 & -64 & -71 & -43 & -47\\ -66 & -69 & -60 & -15 & 16 & -24 & -62 & -55\\ -21 & -7 & -39 & -107 & -93 & -64 & -63 & -63 \end{bmatrix} %]]>
−64−62−62−93−83−40−66−21−72−64−62−53−32−39−69−7−72−93−62−52−3−40−60−39−71−92−69−83−21−50−15−107−58−41−93−47−97−6416−93−44−83−41−91−113−71−24−64−44−107−102−94−21−43−62−63−69−70−24−93−38−47−55−63
接着,进行 FDCT 变换,得到
[
−
477.63
24.47
6.93
−
25.49
−
6.13
−
27.83
−
0.57
6.89
−
65.84
−
22.93
−
4.66
15.25
16.3
−
12.69
12.2
−
7.67
7.72
−
5.29
14.03
74.8
3.88
−
15.81
13.35
−
1.86
44.54
−
25.13
−
24.48
−
14.24
3.35
47.02
−
33.93
13.8
−
13.63
22.85
22.83
−
31.1
−
53.13
22
−
22.31
20.27
11.12
−
32.74
−
64.88
40.32
17.61
−
11.14
11.72
−
2.59
10.47
6.93
62.85
−
8.64
−
30.16
17.07
26.22
−
22.7
42.47
−
31.38
−
4.03
−
35.84
0.41
29.19
10.36
−
27.19
]
% <![CDATA[ \begin{bmatrix} -477.63 & 24.47 & 6.93 & -25.49 & -6.13 & -27.83 & -0.57 & 6.89\\ -65.84 & -22.93 & -4.66 & 15.25 & 16.3 & -12.69 & 12.2 & -7.67\\ 7.72 & -5.29 & 14.03 & 74.8 & 3.88 & -15.81 & 13.35 & -1.86\\ 44.54 & -25.13 & -24.48 & -14.24 & 3.35 & 47.02 & -33.93 & 13.8\\ -13.63 & 22.85 & 22.83 & -31.1 & -53.13 & 22 & -22.31 & 20.27\\ 11.12 & -32.74 & -64.88 & 40.32 & 17.61 & -11.14 & 11.72 & -2.59\\ 10.47 & 6.93 & 62.85 & -8.64 & -30.16 & 17.07 & 26.22 & -22.7\\ 42.47 & -31.38 & -4.03 & -35.84 & 0.41 & 29.19 & 10.36 & -27.19 \end{bmatrix} %]]>
−477.63−65.847.7244.54−13.6311.1210.4742.4724.47−22.93−5.29−25.1322.85−32.746.93−31.386.93−4.6614.03−24.4822.83−64.8862.85−4.03−25.4915.2574.8−14.24−31.140.32−8.64−35.84−6.1316.33.883.35−53.1317.61−30.160.41−27.83−12.69−15.8147.0222−11.1417.0729.19−0.5712.213.35−33.93−22.3111.7226.2210.366.89−7.67−1.8613.820.27−2.59−22.7−27.19
2.5 量化(Quantization)
在进行空间频率变换(如DCT,离散余弦变换)之后,可以看到左上角的系数幅值通常较大,尤其是第一个系数(直流分量,DC系数)最大。左上角的这些条目对应于图像的低频变化,也就是我们人眼最容易察觉到的变化;而右下角的系数代表高频变化,对于人眼来说,这些细微变化不易察觉。
JPEG 通过舍弃大量高频分量来实现压缩,如果我们将这些高频部分直接设置为 0,对整体效果影响不大。因此对于每个 MCU,我们都会进行量化处理。
量化过程中,要用到量化矩阵(quantization matrix),它是一个 8x8 的系数矩阵,每个系数表示在整体MCU中对应位置元素的重要性。某个位置的系数越大,说明视觉上越不敏感,所以用于量化的常数值越大(压缩得越厉害,信息损失也越多);反之越小说明越重要,保留更多信息。
我们在示例中使用 JPEG 标准(ITU-T.81, Annex-K, Table-K.1,第143页)中建议的,用于亮度(luminance),质量为50%的量化表:
[
16
11
10
16
124
140
151
161
12
12
14
19
126
158
160
155
14
13
16
24
140
157
169
156
14
17
22
29
151
187
180
162
18
22
37
56
168
109
103
177
24
35
55
64
181
104
113
192
49
64
78
87
103
121
120
101
72
92
95
98
112
100
103
199
]
% <![CDATA[ \begin{bmatrix} 16 & 11 & 10 & 16 & 124 & 140 & 151 & 161\\ 12 & 12 & 14 & 19 & 126 & 158 & 160 & 155\\ 14 & 13 & 16 & 24 & 140 & 157 & 169 & 156\\ 14 & 17 & 22 & 29 & 151 & 187 & 180 & 162\\ 18 & 22 & 37 & 56 & 168 & 109 & 103 & 177\\ 24 & 35 & 55 & 64 & 181 & 104 & 113 & 192\\ 49 & 64 & 78 & 87 & 103 & 121 & 120 & 101\\ 72 & 92 & 95 & 98 & 112 & 100 & 103 & 199 \end{bmatrix} %]]>
1612141418244972111213172235649210141622375578951619242956648798124126140151168181103112140158157187109104121100151160169180103113120103161155156162177192101199
如果将这些量化值除以 2(减小一半),重建后的图像通常与源图像几乎难以区分。质量等级大致是这样的:
标准表(K.1) → 50%质量
标准表 ÷ 2 → 接近100%质量
标准表 × 2 → 接近25%质量
我们使用下面公式对 MCU 数据进行量化
A
′
=
∑
i
=
0
7
∑
j
=
0
7
r
o
u
n
d
(
a
i
,
j
Q
i
,
j
)
A^{\prime} = \sum_{i=0}^{7} \sum_{j=0}^{7} round(\frac{a_{i,j}}{Q_{i,j}})
A′=i=0∑7j=0∑7round(Qi,jai,j)
- A ′ A' A′表示量化后的最小编码单元(MCU)。
- a 表示变换后的 MCU(通常指 DCT 变换或者其它类似变换结果的8x8矩阵)。
- Q 是量化矩阵。
经过量化后,得到
[
−
30
2
1
−
2
0
0
0
0
−
5
−
2
0
1
0
0
0
0
0
0
0
1
0
0
0
0
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
0
0
0
]
% <![CDATA[ \begin{bmatrix} -30 & 2 & 1 & -2 & 0 & 0 & 0 & 0\\ -5 & -2 & 0 & 1 & 0 & 0 & 0 & 0\\ 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0\\ 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 & 0 & 0 & 0 \end{bmatrix} %]]>
−30−50100002−200000010000000−2110000000000000000000000000000000000000
很 cool,左下角数据基本都是 0 了,那么接下来对这些很多 0 的数据进行进一步压缩。
2.6 熵编码(Entropy Coding)
熵编码这一步是通过“之字形遍历”(Zig-Zag)将8x8 MCU块中的64个系数按特定顺序排列,把相同频率的系数分组到一起。然后,对得到的64个一维向量进行游程编码(RLE,Run-Length Encoding)。接着,将游程编码的结果再用哈夫曼编码进行压缩,以进一步减少数据所占的字节数。这一步用实际例子来讲会比单纯文字描述更直观。
1. Zig-Zag 遍历
我们要对MCU(8×8的块)进行之字形遍历,把它转化为一个64个元素的一维向量。在之字形遍历中,矩阵元素的访问顺序如下:
[
1
2
6
7
15
16
28
29
3
5
8
14
17
27
30
43
4
9
13
18
26
31
42
44
10
12
19
25
32
41
45
54
11
20
24
33
40
46
53
55
21
23
34
39
47
52
56
61
22
35
38
48
51
57
60
62
36
37
49
50
58
59
63
64
]
% <![CDATA[ \begin{bmatrix} 1 & 2 & 6 & 7 & 15 & 16 & 28 & 29\\ 3 & 5 & 8 & 14 & 17 & 27 & 30 & 43\\ 4 & 9 & 13 & 18 & 26 & 31 & 42 & 44\\ 10 & 12 & 19 & 25 & 32 & 41 & 45 & 54\\ 11 & 20 & 24 & 33 & 40 & 46 & 53 & 55\\ 21 & 23 & 34 & 39 & 47 & 52 & 56 & 61\\ 22 & 35 & 38 & 48 & 51 & 57 & 60 & 62\\ 36 & 37 & 49 & 50 & 58 & 59 & 63 & 64 \end{bmatrix} %]]>
13410112122362591220233537681319243438497141825333948501517263240475158162731414652575928304245535660632943445455616264
矩阵中每一数字表示该位置在遍历中的序号。
让我们回到前面编码的那个示例MCU。它之字形遍历的结果是一组如下的数:
-30, 2, -5, 0, -2, 1, -2, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
在这个步骤中,最重要的一点是:通过之字形遍历,MCU中的元素被分组,使得对图像数据影响大的低频分量集中排列,而无关紧要或几乎没有影响的高频分量也集中在一起。
2 游程编码(Run-Length Encoding)
这里需要注意的是,第一个系数 -30,也叫直流(DC)系数,不会像剩下的63个称为交流(AC)系数的那样用行程长度编码进行编码。具体原因现在很难解释,但在接下来的步骤会变得清晰。现在你只需要记住,行程长度编码这一步只应用于那63个AC系数。
如你所见,这些系数里有很多零。为了节省空间,我们不用保存这64个元素的原始流,而是仅保存每一个非零数前面有多少个零。例如,第一个2前面没有零,所以它被编码为(0,2)。同样,第二个1前面有两个零,所以被编码为(2,1)。
在最后一个非零值(比如1)后面的系数,如果都是零,可以用一个惯例来表示“剩下全是零”。这个约定用的是对偶(0,0),也叫EOB(字节结束,End Of Bytes)。
还需注意的是,如果在某个非零值前有超过16个零,比如说有23个零,我们不能直接存23。例如(23 zeros,58)应该被编码为(15,0),(8,58),而不是(23,58)。其中(15,0)和(0,0)一样是个特殊值,表示连续16个零。为什么要这样做?其原因在下一步对这些AC系数进行哈夫曼编码时会变得明显。
所以,对AC系数进行行程长度编码后得到的结果如下:
(0,2),(0,−5),(1,−2),(0,1),(0,−2),(2,1),(3,1),(3,1),(0,0)
3 对游程编码数据进行霍夫曼编码
霍夫曼编码是一种无损数据压缩技术,可以将一系列输入符号(字节、字符等)编码为可变长度代码(与其他技术中的固定长度代码相对),其中输入中频繁使用的符号被分配较短的代码,而使用较少的符号被分配较长的代码。这种可变长度编码的好处在于,更频繁的符号出现次数比不太频繁的符号多,使得存储编码数据所需的字节数比使用固定长度代码要少。
可变长度代码是通过分析输入流中每个符号出现的频率或概率来获得的。此外,霍夫曼编码保证没有代码是更长代码的前缀(也就是说,在霍夫曼编码方案中不能有两个代码分别是111和1110)。
执行频率分析和构建编码霍夫曼表的确切步骤可以在JPEG规范中找到。在实践中,霍夫曼表中的条目存储在JFIF/EXIF文件中,从中构建符号的霍夫曼树。但在这一系列文章的其余部分,我们将使用JPEG规范(ITU-T.81,附录K,第K.3.1节,表K.3,第149页)中提供的示例表。
4. DC 系数编码
正如上一节提到的,DC系数的编码和AC系数有所不同。MCU(最小编码单元)的DC系数通常绝对值最大,决定了该块整体的数值。此外,连续MCU的DC系数之间是有关联的;它们之间的变化通常比较平缓。因此,JPEG在编码时,并不是直接存储每个DC系数的值(那样需要更多字节),而是只存储相邻MCU之间DC系数的差值(注意,对于不同的颜色通道,每个通道都各自这样处理DC系数)。公式如下:
D C i + 1 = D C i + difference DC_{i+1} = DC_i + \text{difference} DCi+1=DCi+difference
即第(i+1)块的DC系数,等于第i块DC系数加上它们的差值。对于第0块,差值总是0(因为没有前一个块)。
接下来,每个差值都有一个“数值类别”(value category),它表示存储该差值需要的最小比特数。
-
为什么DC系数编码不同?
幅度特性:DC系数通常是8×8块中最大的系数
空间相关性:相邻图像块的DC系数变化通常很小
冗余性:直接存储完整DC值会造成存储浪费 -
差分编码(DPCM - Differential Pulse Code Modulation)
原始DC序列: [-30, -28, -31, -29, ...]
差值序列: [-30, +2, -3, +2, ...]
↑ ↑ ↑ ↑
第一个 差值 差值 差值
- “数值类别”是什么?
差值(difference)也可能有正有负,大多数都集中在数值较小的区间。JPEG不会直接把差值写二进制,而是分成不同的“类别”(Category):0类、1类、2类……每类需要使用的比特数不同,例如:
- Category 0: 只包含0
- Category 1: -1, 1 (共两种,只需1比特)
- Category 2: -3,-2,2,3 (共4种,需2比特)
- Category 3: -7,-6,-5,-4,4,5,6,7 (共8种,需3比特)
- 以此类推
比如差值-30落在哪个类别(一般会给出一张表辅助查找),这样就知道只用多少比特表示-30这个差值,无需用完整的整型字节。
-
具体流程
计算每个块DC与前一块DC的差值(第一个块用0)。
取差值的“类别”(即最小几位数能描述它)。
先把类别编号(作为符号)哈夫曼编码,再用该类别对应比特记录差值本身。 -
目的
利用块之间的相似性和差值接近0的特性,把大数变成小数,有助于后续哈夫曼编码进一步压缩。
假设 DC 是 -30,-30位于区间[−31, 31],所以属于类别5(category 5),它的比特表示是00001,因此,这个差值将被编码为(5, 00001)。
假设我们的例子中的MCU来自**亮度(Y)**通道,现在我们来参考JPEG标准中用于编码DC亮度系数的哈夫曼代码(ITU-T.81标准,附录K,K.3.1节,表K.3,第149页):
查表发现,类别5的哈夫曼码是110。
因此,最终会被写入JFIF/EXIF文件的比特流是:11000001。
5. AC 系数编码
现在我们已经知道如何编码DC系数了,接下来看看AC系数的编码。前面我们已经得到AC系数的行程长度编码(Run-Length Encoding):
(0,2),(0,−5),(1,−2),(0,1),(0,−2),(2,1),(3,1),(3,1),(0,0)
对于每一对AC系数,都要判断它前面有多少个连续的零(即“零游程”),它的“值类别”(value category),以及该系数的比特表达方式。前面有多少连续零(zero-run)用4位二进制(RRRR)来表示,AC系数的值类别(SSSS)也用4位表示。正是因为如此,JPEG标准限制每次最大只允许16个连续的零,因为4位二进制最大能表示16。
所以,对于AC系数,哈夫曼码的查表依据就是这对 (RRRR,SSSS)。
那么,每个AC系数最终呈现为: (系数的哈夫曼码,取值的比特表示)
假定这些AC系数来自亮度(Y)通道,我们将依次对它们进行编码。需要使用的哈夫曼表可在JPEG标准文件(ITU-T.81, 附录K, K.3.1节, 表K.5, 第150页)中查找,因表格内容较长这里只介绍查找方法。
我们从第一个系数 (0,2) 开始。它前面有0个零(RRRR=0),值类别为2(SSSS=2),所以在表中查(0,2),哈夫曼码为01。查值类别表可知,“2”的比特表示是10,因此写入文件的是0110。
再举一个例子,(0,−5):RRRR=0,SSSS=3。查表得到哈夫曼码100,“-5”的比特表示为010,最终写入文件是100010。
同理,其他系数结果如下:
(1,−2) : 11100101
(0,1) : 001
(0,−2) : 0101
(2,1) : 110111
(3,1) : 1110101
(3,1) : 1110101
(0,0) : 1010(EOB,表示剩下全是零)
将该MCU所有AC系数拼起来,最终比特流是: 0110 100010 11100101 001 0101 110111 1110101 1110101 1010
如果加上DC系数的编码,整个MCU的最终比特流是:
11000001 0110 100010 11100101 001 0101 110111 1110101 1110101 1010
- AC系数编码结构
每个AC系数编码 = (RRRR, SSSS) + 位表示
↓ ↓
霍夫曼代码 实际数值
-
RRRR和SSSS的含义
RRRR (4位):前导零的个数(0-15)
SSSS (4位):数值类别(0-15)
组合 (RRRR,SSSS):用于查找霍夫曼代码 -
具体编码示例分析
第一个系数 (0,2):
原始对: (0,2)
RRRR = 0 (前面0个零)
SSSS = 2 (数值2属于类别2)
霍夫曼代码: 01 (查表得到)
位表示: 10 (数值2的位表示)
最终: 0110
第二个系数 (0,-5):
原始对: (0,-5)
RRRR = 0 (前面0个零)
SSSS = 3 (数值-5属于类别3)
霍夫曼代码: 100 (查表得到)
位表示: 010 (数值-5的位表示)
最终: 100010
- 特殊系数处理
EOB标记 (0,0):
RRRR = 0, SSSS = 0
霍夫曼代码: 1010
含义: "后面全是零"
- 完整MCU编码结构
完整MCU位流 = DC系数编码 + AC系数编码
11000001 + 0110 100010 11100101 001 0101 110111 1110101 1110101 1010
↑ ↑
DC系数 所有AC系数
2.7 编码总结
JPEG整套压缩流程,其实就是对“整张图片”分块处理(每个MCU),每块重复做DCT变换、量化、Z字型排序、行程长度编码、哈夫曼编码……,再把所有块的最终编码结果合并起来,生成整个图像的数据。最终存成JFIF格式的图片文件。
这样,任何支持JPEG的应用或库(如 libjpeg),只需按JFIF规范解码流程,就能把这些“压缩代码”完整还原、显示出原来的这张图片。