JPEG的文件格式
JPEG文件大体上可以分成以下两个部分:标记码(Tag)加压缩数据。先介绍标记码部分。
标记码部分给出了JPEG图象的所有信息(有点类似于BMP中的头信息,但要复杂的多),如图象的宽、高、Huffman表、量化表等等。标记码有很多,但绝大多数的JPEG文件只包含几种。标记码的结构为:
SOI
DQT
DRI
SOF0
DHT
SOS
…
EOI
标记码由两个字节组成,高字节为0XFF,每个标记码之前可以填上个数不限的填充字节0XFF。
下面介绍一些常用的标记码的结构及其含义。
(1)SOI(Start of Image)
标记结构 字节数
0XFF 1
0XD8 1
可作为JPEG格式的判据(JFIF还需要APP0的配合)
(2)APP0(Application)
标记结构 字节数 意义
0XFF 1
0XE0 1
Lp 2 APP0标记码长度,不包括前两个字节0XFF,0XE0
Identifier 5 JFIF识别码 0X4A,0X46,0X49,0X46,0X00
Version 2 JFIF版本号 可为0X0101或者0X0102
Units 1 单位,等于零时表示未指定,为1表示英寸,为2表示
厘米
Xdensity 2 水平分辨率
Ydensity 2 垂直分辨率
Xthumbnail 1 水平点数
Ythumbnail 1 垂直点数
RGB0 3 RGB的值
RGB1 3 RGB的值
…
RGBn 3 RGB的值,n=Xthumbnail*Ythumbnail
APP0是JPEG保留给Application所使用的标记码,而JFIF将文件的相关信息定义在此标记中。
(3)DQT(Define Quantization Table)
标记结构 字节数 意义
0XFF 1
0XDB 1
Lq 2 DQT标记码长度,不包括前两个字节0XFF,0XDB
(Pq,Tq) 1 高四位Pq为量化表的数据精确度,Pq=0时,Q0~Qn的
值为8位,Pq=1时,Qt的值为16位,Tq表示量化表的
编号,为0~3。在基本系统中,Pq=0,Tq=0~1,也就是
说最多有两个量化表。
Q0 1或2 量化表的值,Pq=0时;为一个字节,Pq=1时,为两个
字节
Q1 1或2 量化表的值,Pq=0时;为一个字节,Pq=1时,为两个
字节
…
Qn 1或2 量化表的值,Pq=0时,为一个字节;Pq=1时,为两个
字节。n的值为0~63,表示量化表中64个值(之字形排
列)
(4)DRI(Define Restart Interval)
此标记需要用到最小编码单元(MCU,Minimum Coding Unit)的概念。前面提到,Y分量数据重要,UV分量的数据相对不重要,所以可以只取UV的一部分,以增加压缩比。目前支持JPEG格式的软件通常提供两种取样方式YUV411和YUV422,其含义是YUV三个分量的数据取样比例。举例来说,如果Y取四个数据单元,即水平取样因子Hy乘以垂直取样因子Vy的值为4,而U和V各取一个数据单元,即Hu×Vu=1,Hv×Vv=1。那么这种部分取样就称为YUV411。如图9.7所示:
图9.7 YUV411的示意图 | 图9.8 YUV111的排列顺序 |
易知YUV411有50%的压缩比(原来有12个数据单元,现在有6个数据单元),YUV422有33%的压缩比(原来有12个数据单元,现在有8个数据单元)。
那么你可能会想,YUV911,YUV1611压缩比不是更高嘛?但是要考虑到图象质量的因素。所以JPEG标准规定了最小编码单元MCU,要求Hy×Vy+Hu×Vu+Hv×Vv≤10。
MCU中块的排列方式与H,V的值有密切关系,如图9.8、图9.9、图9.10所示。
图9.9 YUV211的排列顺序
图9.10 YUV411的排列顺序
标记结构 字节数 意义
0XFF 1
0XDD 1
Lr 2 DRI标记码长度,不包括前两个字节0XFF,0XDD
Ri 2 重入间隔的MCU个数,Ri必须是一MCU行中MCU
个数的整数,最后一个零头不一定刚好是Ri个MCU。
每个重入间隔各自独立编码。
(5)SOF(Start of Frame) 在基本系统中,只处理SOF0
标记结构 字节数 意义
0XFF 1
0XC0 1
Lf 2 SOF标记码长度,不包括前两个字节0XFF,0XC0
P 1 基本系统中,为0X08
Y 2 图象高度
X 2 图象宽度
Nf 1 Frame中的成分个数,一般为1或3,1代表灰度图,3
代表真彩图
C1 1 成分编号1
(H1,V1) 1 第一个水平和垂直采样因子
Tq1 1 该量化表编号
C2 1 成分编号2
(H2,V2) 1 第二个水平和垂直采样因子
Tq2 1 该量化表编号
…
Cn 1 成分编号n
(Hn,Vn) 1 第n个水平和垂直采样因子
Tqn 1 该量化表编号
(6)DHT(Define Huffman Table)
标记结构 字节数 意义
0XFF 1
0XC4 1
Lh 2 DHT标记码长度,不包括前两个字节0XFF,0XC4
(Tc,Th) 1
L1 1
L2 1
…
L16 1
V1 1
V2 1
…
Vt 1
Tc为高4位,Th为低4位。在基本系统中,Tc为0或1,为0时,指DC所用的Huffman表,为1时,指AC所用的Huffman表。Th表示Huffman表的编号,在基本系统中,其值为0或1。所以,在基本系统中,最多有4个Huffman表,如下所示:
Tc Th Huffman表编号(2×Tc+Th)
0 0
1 1
0 2
1 1 3
Ln表示每个n比特的Huffman码字的个数,n=1~16
Vt表示每个Huffman码字所对应的值,也就是我们前面所讲的符号1,对DC来说该值为(Size),对AC来说该值为(RunLength,Size)。
t=L1+L2+…L16
(7)SOS(Start of Scan)
标记结构 字节数 意义
0XFF 1
0XDA 1
Ls 2 DHT标记码长度,不包括前两个字节0XFF,0XDA
Ns 1
Cs1 1
(Td1,Ta1) 1
Cs2 1
(Td2,Ta2) 1
…
CsNs 1
(TdNs,TaNs) 1
Ss 1
Se 1
(Ah,Al) 1
Ns为Scan中成分的个数,在基本系统中,Ns=Nf(Frame中成分个数)。CSNs为在Scan中成分的编号。TdNs为高4位,TaNs为低4位,分别表示DC和AC编码表的编号。在基本系统中Ss=0,Se=63,Ah=0,Al=0。
(8)EOI(End of Image) 结束标志
标记结构 字节数 意义
0XFF 1
0XD9 1
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=677394
+++++++++++++++++++++++++++++++++++++++++++++++++
1 简介
微处理机中的存放顺序有正序(big endian)和逆序(little endian)之分。正序存放就是高字节存放在前低字节在后,而逆序存放就是低字节在前高字节在后。例如,十六进制数为A02B,正序存放就是A02B,逆序存放就是2BA0。摩托罗拉(Motorola)公司的微处理器使用正序存放,而英特尔(Intel)公司的微处理器使用逆序。JPEG文件中的字节是按照正序排列的。
JPEG委员会在制定JPEG标准时,定义了许多标记(marker)用来区分和识别图像数据及其相关信息,但笔者没有找到JPEG委员会对JPEG文件交换格式的明确定义。直到1998年12月从分析网上具体的JPG图像来看,使用比较广泛的还是JPEG文件交换格式(JPEG File Interchange Format,JFIF)版本号为1.02。这是1992年9月由在C-Cube Microsystems公司工作的Eric Hamilton提出的。此外还有TIFF JPEG等格式,但由于这种格式比较复杂,因此大多数应用程序都支持JFIF文件交换格式。
JPEG文件使用的颜色空间是CCIR 601推荐标准进行的彩色空间(参看第7章)。在这个彩色空间中,每个分量、每个像素的电平规定为255级,用8位代码表示。从RGB转换成YCbCr空间时,使用下面的精确的转换关系:
Y = 256 * E'y
Cb = 256 * [E'Cb] + 128
Cr = 256 * [E'Cr] + 128
其中亮度电平E'y和色差电平E'Cb和E'Cb分别是CCIR 601定义的参数。由于E'y的范围是0~1,E'Cb和E'Cb的范围是-0.5~+0.5,因此Y, Cb和Cr的最大值必须要箝到255。于是RGB和YCbCr之间的转换关系需要按照下面的方法计算。
(1) 从RGB转换成YCbCr
YCbCr(256级)分量可直接从用8位表示的RGB分量计算得到:
Y = 0.299 R + 0.587 G + 0.114 B
Cb = - 0.1687R - 0.3313G + 0.5 B + 128
Cr = 0.5 R - 0.4187G - 0.0813 B + 128
需要注意的是不是所有图像文件格式都按照R0,G0,B0,…… Rn,Gn,Bn的次序存储样本数据,因此在RGB文件转换成JFIF文件时需要首先验证RGB的次序。
(2) 从YCbCr转换成RGB
RGB分量可直接从YCbCr(256级)分量计算得到:
R = Y + 1.402 (Cr-128)
G = Y - 0.34414 (Cb-128) - 0.71414 (Cr-128)
B = Y + 1.772 (Cb-128)
在JFIF文件格式中,图像样本的存放顺序是从左到右和从上到下。这就是说JFIF文件中的第一个图像样本是图像左上角的样本。
2 文件结构
JFIF文件格式直接使用JPEG标准为应用程序定义的许多标记,因此JFIF格式成了事实上JPEG文件交换格式标准。JPEG的每个标记都是由2个字节组成,其前一个字节是固定值0xFF。每个标记之前还可以添加数目不限的0xFF填充字节(fill byte)。下面是其中的8个标记:
1. SOI 0xD8 图像开始
2. APP0 0xE0 JFIF应用数据块
3. APPn 0xE1 - 0xEF 其他的应用数据块(n, 1~15)
4. DQT 0xDB 量化表
5. SOF0 0xC0 帧开始
6. DHT 0xC4 霍夫曼(Huffman)表
7. SOS 0xDA 扫描线开始
8. EOI 0xD9 图像结束
为使读者对JPEG定义的标记一目了然,现将JPEG的标记码列于表6-05,并保留英文解释。
表6-05 JPEG定义的标记
Symbol (符号) | Code Assignment (标记代码) | Description (说明) |
Start Of Frame markers, non-hierarchical Huffman coding | ||
SOF0 | 0xFFC0 | Baseline DCT |
SOF1 | 0xFFC1 | Extended sequential DCT |
SOF2 | 0xFFC2 | Progressive DCT |
SOF3 | 0xFFC3 | Spatial (sequential) lossless |
Start Of Frame markers, hierarchical Huffman coding | ||
SOF5 | 0xFFC5 | Differential sequential DCT |
SOF6 | 0xFFC6 | Differential progressive DCT |
SOF7 | 0xFFC7 | Differential spatial lossless |
Start Of Frame markers, non-hierarchical arithmetic coding | ||
JPG | 0xFFC8 | Reserved for JPEG extensions |
SOF9 | 0xFFC9 | Extended sequential DCT |
SOF10 | 0xFFCA | Progressive DCT |
SOF11 | 0xFFCB | Spatial (sequential) Lossless |
Start Of Frame markers, hierarchical arithmetic coding | ||
SOF13 | 0xFFCD | Differential sequential DCT |
SOF14 | 0xFFCE | Differential progressive DCT |
SOF15 | 0xFFCF | Differential spatial Lossless |
Huffman table specification | ||
DHT | 0xFFC4 | Define Huffman table(s) |
arithmetic coding conditioning specification | ||
DAC | 0xFFCC | Define arithmetic conditioning table |
Restart interval termination | ||
RSTm | 0xFFD0~0xFFD7 | Restart with modulo 8 counter m |
Other marker | ||
SOI | 0xFFD8 | Start of image |
EOI | 0xFFD9 | End of image |
SOS | 0xFFDA | Start of scan |
DQT | 0xFFDB | Define quantization table(s) |
DNL | 0xFFDC | Define number of lines |
DRI | 0xFFDD | Define restart interval |
DHP | 0xFFDE | Define hierarchical progression |
EXP | 0xFFDF | Expand reference image(s) |
APPn | 0xFFE0~0xFFEF | Reserved for application use |
JPGn | 0xFFF0~0xFFFD | Reserved for JPEG extension |
COM | 0xFFFE | Comment |
Reserved markers | ||
TEM | 0xFF01 | For temporary use in arithmetic coding |
RES | 0xFF02~0xFFBF | Reserved |
JPEG文件由下面的8个部分组成:
(1) 图像开始SOI(Start of Image)标记
(2) APP0标记(Marker)
① APP0长度(length)
② 标识符(identifier)
③ 版本号(version)
④ X和Y的密度单位(units=0:无单位;units=1:点数/英寸;units=2:点数/厘米)
⑤ X方向像素密度(X density)
⑥ Y方向像素密度(Y density)
⑦ 缩略图水平像素数目(thumbnail horizontal pixels)
⑧ 缩略图垂直像素数目(thumbnail vertical pixels)
⑨ 缩略图RGB位图(thumbnail RGB bitmap)
(3) APPn标记(Markers),其中n=1~15(任选)
① APPn长度(length)
② 由于详细信息(application specific information)
(4) 一个或者多个量化表DQT(difine quantization table)
① 量化表长度(quantization table length)
② 量化表数目(quantization table number)
③ 量化表(quantization table)
(5) 帧图像开始SOF0(Start of Frame)
① 帧开始长度(start of frame length)
② 精度(precision),每个颜色分量每个像素的位数(bits per pixel per color component)
③ 图像高度(image height)
④ 图像宽度(image width)
⑤ 颜色分量数(number of color components)
⑥ 对每个颜色分量(for each component)
o ID
o 垂直方向的样本因子(vertical sample factor)
o 水平方向的样本因子(horizontal sample factor)
o 量化表号(quantization table#)
(6) 一个或者多个霍夫曼表DHT(Difine Huffman Table)
① 霍夫曼表的长度(Huffman table length)
② 类型、AC或者DC(Type, AC or DC)
③ 索引(Index)
④ 位表(bits table)
⑤ 值表(value table)
(7) 扫描开始SOS(Start of Scan)
① 扫描开始长度(start of scan length)
② 颜色分量数(number of color components)
③ 每个颜色分量
o ID
o 交流系数表号(AC table #)
o 直流系数表号(DC table #)
④ 压缩图像数据(compressed image data)
(8) 图像结束EOI(End of Image)
表6-06表示了APP0域的详细结构。有兴趣的读者可通过UltraEdit或者PC TOOLS等工具软件打开一个JPG图像文件,对APP0的结构进行分析和验证。
表6-06 JFIF格式中APP0域的详细结构
偏移 | 长度 | 内容 | 块的名称 | 说明 |
0 | 2 byte | 0xFFD8 | (Start of Image,SOI) | 图像开始 |
2 | 2 byte | 0xFFE0 | APP0(JFIF application segment) | JFIF应用数据块 |
4 | 2 bytes |
| length of APP0 block | APP0块的长度 |
6 | 5 bytes |
| "JFIF"+"0" | 识别APP0标记 |
11 | 1 byte |
| <Major version> | 主要版本号(如版本1.02中的1) |
12 | 1 byte |
| <Minor version> | 次要版本号(如版本1.02中的02) |
13 | 1 byte |
| <Units for the X | X和Y的密度单位 units=0:无单位 units=1:点数/英寸 units=2:点数/厘米 |
14 | 2 bytes |
| <Xdensity> | 水平方向像素密度 |
16 | 2 bytes |
| <Ydensity> | 垂直方向像素密度 |
18 | 1 byte |
| <Xthumbnail> | 缩略图水平像素数目 |
19 | 1 byte |
| <Ythumbnail> | 缩略图垂直像素数目 |
| 3n |
| < Thumbnail RGB bitmap> | 缩略RGB位图(n为缩略图的像素数) |
|
|
| Optional JFIF extension APP0 marker segment(s) | 任选的JFIF扩展APP0标记段 |
| …… |
| …… |
|
| 2 byte | 0xFFD9 | (EOI) end-of-file | 图像文件结束标记 |
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=675344
JPEG 压缩简介
-------------
1. 色彩模型
JPEG 的图片使用的是 YCrCb 颜色模型, 而不是计算机上最常用的 RGB. 关于色
彩模型, 这里不多阐述. 只是说明, YCrCb 模型更适合图形压缩. 因为人眼对图片上
的亮度 Y 的变化远比色度 C 的变化敏感. 我们完全可以每个点保存一个 8bit 的亮
度值, 每 2x2 个点保存一个 Cr Cb 值, 而图象在肉眼中的感觉不会起太大的变化.
所以, 原来用 RGB 模型, 4 个点需要 4x3=12 字节. 而现在仅需要 4+2=6 字节; 平
均每个点占 12bit. 当然 JPEG 格式里允许每个点的 C 值都记录下来; 不过 MPEG 里
都是按 12bit 一个点来存放的, 我们简写为 YUV12.
[R G B] -> [Y Cb Cr] 转换
-------------------------
(R,G,B 都是 8bit unsigned)
| Y | | 0.299 0.587 0.114 | | R | | 0 |
| Cb | = |- 0.1687 - 0.3313 0.5 | * | G | + |128|
| Cr | | 0.5 - 0.4187 - 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,Cb,Cr] -> [R,G,B] 转换
-------------------------
R = Y + 1.402 *(Cr-128)
G = Y - 0.34414*(Cb-128) - 0.71414*(Cr-128)
B = Y + 1.772 *(Cb-128)
一般, C 值 (包括 Cb Cr) 应该是一个有符号的数字, 但这里被处理过了, 方法
是加上了 128. JPEG 里的数据都是无符号 8bit 的.
2. DCT (离散余弦变换)
JPEG 里, 要对数据压缩, 先要做一次 DCT 变换. DCT 变换的原理, 涉及到数学
知识, 这里我们不必深究. 反正和傅立叶变换(学过高数的都知道) 是差不多了. 经过
这个变换, 就把图片里点和点间的规律呈现出来了, 更方便压缩.JPEG 里是对每 8x8
个点为一个单位处理的. 所以如果原始图片的长宽不是 8 的倍数, 都需要先补成 8
的倍数, 好一块块的处理. 另外, 记得刚才我说的 Cr Cb 都是 2x2 记录一次吗? 所
以大多数情况, 是要补成 16x16 的整数块.按从左到右, 从上到下的次序排列 (和我
们写字的次序一样). JPEG 里是对 Y Cr Cb 分别做 DCT 变换的.
JPEG 编码时使用的是 Forward DCT (FDCT) 解码时使用的 Inverse DCT (IDCT)
下面给出公式:
FDCT:
c(u,v) 7 7 2*x+1 2*y+1
F(u,v) = --------- * sum sum f(x,y) * cos (------- *u*PI)* cos (------ *v*PI)
4 x=0 y=0 16 16
u,v = 0,1,...,7
{ 1/2 当 u=v=0 时
c(u,v) = {
{ 1 其他情况
IDCT:
1 7 7 2*x+1 2*y+1
f(x,y) = --- * sum sum c(u,v)*F(u,v)*cos (------- *u*PI)* cos (------ *v*PI)
4 u=0 v=0 16 16
x,y=0,1...7
这个步骤很花时间, 另外有种 AA&N 优化算法, 大家可以去 inet 自己找一下.
在 Intel 主页上可以找到 AA&N IDCT 的 MMX 优化代码.
3. 重排列 DCT 结果
DCT 将一个 8x8 的数组变换成另一个 8x8 的数组. 但是内存里所有数据都是线
形存放的, 如果我们一行行的存放这 64 个数字, 每行的结尾的点和下行开始的点就
没有什么关系, 所以 JPEG 规定按如下次序整理 64 个数字.
0, 1, 5, 6,14,15,27,28,
2, 4, 7,13,16,26,29,42,
3, 8,12,17,25,30,41,43,
9,11,18,24,31,40,44,53,
10,19,23,32,39,45,52,54,
20,22,33,38,46,51,55,60,
21,34,37,47,50,56,59,61,
35,36,48,49,57,58,62,63
这样数列里的相邻点在图片上也是相邻的了.
4. 量化
对于前面得到的 64 个空间频率振幅值, 我们将对它们作幅度分层量化操作.方
法就是分别除以量化表里对应值并四舍五入.
for (i = 0 ; i<=63; i++ )
vector[i] = (int) (vector[i] / quantization_table[i] + 0.5)
下面有张 JPEG 标准量化表. (按上面同样的弯曲次序排列)
16 11 10 16 24 40 51 61
12 12 14 19 26 58 60 55
14 13 16 24 40 57 69 56
14 17 22 29 51 87 80 62
18 22 37 56 68 109 103 77
24 35 55 64 81 104 113 92
49 64 78 87 103 121 120 101
72 92 95 98 112 100 103 99
这张表依据心理视觉阀制作, 对 8bit 的亮度和色度的图象的处理效果不错.
当然我们可以使用任意的量化表. 量化表是定义在 jpeg 的 DQT 标记后. 一般
为 Y 值定义一个, 为 C 值定义一个.
量化表是控制 JPEG 压缩比的关键. 这个步骤除掉了一些高频量, 损失了很高
细节. 但事实上人眼对高空间频率远没有低频敏感.所以处理后的视觉损失很小.
另一个重要原因是所有的图片的点与点之间会有一个色彩过渡的过程. 大量的图象
信息被包含在低空间频率中. 经过量化处理后, 在高空间频率段, 将出现大量连续
的零.
5. 0 RLC 编码
现在我们矢量中有许多连续的 0. 我们可以使用 RLC 来压缩掉这些 0. 这里我们
将跳过第一个矢量 (后面将解释为什么) 因为它的编码比较特别. 假设有一组矢量
(64 个的后 63 个) 是
57,45,0,0,0,0,23,0,-30,-16,0,0,1,0,0,0, 0 , 0 ,0 , 0,..,0
经过 RLC 压缩后就是
(0,57) ; (0,45) ; (4,23) ; (1,-30) ; (0,-16) ; (2,1) ; EOB
EOB 是一个结束标记, 表示后面都是 0 了. 实际上, 我们用 (0,0) 表示 EOB
但是, 如果这组数字不以 0 结束, 那么就不需要 EOB.
由于后面 huffman 编码的要求, 每组数字前一个表示 0 的数量的必须是 4 bit,
就是说, 只能是 0~15, 所以我们实际这样编码:
(0,57) ; (15,0) (2,3) ; (4,2) ; (15,0) (15,0) (1,895) , (0,0)
注意 (15,0) 表示了 16 个连续的 0.
6. huffman 编码
为了提高储存效率, JPEG 里并不直接保存数值, 而是将数值按位数分成 16 组:
数值 组 实际保存值
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 00000,..,01111,10000,..,11111
-63,..,-32,32,..,63 6 .
-127,..,-64,64,..,127 7 .
-255,..,-128,128,..,255 8 .
-511,..,-256,256,..,511 9 .
-1023,..,-512,512,..,1023 10 .
-2047,..,-1024,1024,..,2047 11 .
-4095,..,-2048,2048,..,4095 12 .
-8191,..,-4096,4096,..,8191 13 .
-16383,..,-8192,8192,..,16383 14 .
-32767,..,-16384,16384,..,32767 15 .
还是来看前面的例子:
(0,57) ; (0,45) ; (4,23) ; (1,-30) ; (0,-8) ; (2,1) ; (0,0)
只处理每对数右边的那个:
57 是第 6 组的, 实际保存值为 111001 , 所以被编码为 (6,111001)
45 , 同样的操作, 编码为 (6,101101)
23 -> (5,10111)
-30 -> (5,00001)
-8 -> (4,0111)
1 -> (1,1)
前面的那串数字就变成了:
(0,6), 111001 ; (0,6), 101101 ; (4,5), 10111; (1,5), 00001; (0,4) , 0111 ;
(2,1), 1 ; (0,0)
括号里的数值正好合成一个字节. 后面被编码的数字表示范围是 -32767..32767.
合成的字节里, 高 4 位是前续 0 的个数, 低 4 位描述了后面数字的位数.
继续刚才的例子, 如果 06 的 huffman 编码为 111000
69 = (4,5) --- 1111111110011001
21 = (1,5) --- 11111110110
4 = (0,4) --- 1011
33 = (2,1) --- 11011
0 = EOB = (0,0) --- 1010
那么最后对于前面的例子表示的 63 个系数 (记得我们将第一个跳过了吗?) 按位流
写入 JPG 文件中就是这样的:
111000 111001 111000 101101 1111111110011001 10111 11111110110 00001
1011 0111 11011 1 1010
DC 的编码
---------
记得刚才我们跳过了每组 64 个数据的第一个吧, DC 就是指的这个数字 (后面 63
个简称 AC) 代入前面的 FDCT 公式可以得到
c(0,0) 7 7
DC = F(0,0) = --------- * sum sum f(x,y) * cos 0 * cos 0 其中 c(0,0) = 1/2
4 x=0 y=0
1 7 7
= --- * sum sum f(x,y)
8 x=0 y=0
即一块图象样本的平均值. 就是说, 它包含了原始 8x8 图象块里的很多能量. (通常
会得到一个很大的数值)
JPEG 的作者指出连续块的 DC 率之间有很紧密的联系, 因此他们决定对 8x8 块的
DC 值的差别进行编码. (Y, Cb, Cr 分别有自己的 DC)
Diff = DC(i) - DC(i-1)
所以这一块的 DC(i) 就是: DC(i) = DC(i-1) + Diff
JPG 从 0 开始对 DC 编码, 所以 DC(0)=0. 然后再将当前 Diff 值加在上一个值上得
到当前值.
下面再来看看上面那个例子: (记住我们保存的 DC 是和上一块 DC 的差值 Diff)
例如上面例子中, Diff 是 -511, 就编码成
(9, 000000000)
如果 9 的 Huffman 编码是 1111110 (在 JPG 文件中, 一般有两个 Huffman 表, 一
个是 DC 用, 一个是 AC 用) 那么在 JPG 文件中, DC 的 2 进制表示为
1111110 000000000
它将放在 63 个 AC 的前面, 上面上个例子的最终 BIT 流如下:
1111110 000000000 111000 111001 111000 101101 1111111110011001 10111
11111110110 00001 1011 0111 11011 1 1010
下面简单叙述一下针对一个数据单元的图片 Y 的解码
-----------------------------------------------
在整个图片解码的开始, 你需要先初始化 DC 值为 0.
1) 先解码 DC:
a) 取得一个 Huffman 码 (使用 Huffman DC 表)
b) Huffman解码, 看看后面的数据位数 N
c) 取得 N 位, 计算 Diff 值
d) DC + = Diff
e) 写入 DC 值: " vector[0]=DC "
2) 解码 63 个 AC:
------- 循环处理每个 AC 直到 EOB 或者处理到 64 个 AC
a) 取得一个 Huffman 码 (使用 Huffman AC 表)
b) Huffman 解码, 得到 (前面 0 数量, 组号)
[记住: 如果是(0,0) 就是 EOB 了]
c) 取得 N 位(组号) 计算 AC
d) 写入相应数量的 0
e) 接下来写入 AC
-----------------
下一步的解码
------------
上一步我们得到了 64 个矢量. 下面我们还需要做一些解码工作:
1) 反量化 64 个矢量 : "for (i=0;i<=63;i++) vector[i]*=quant[i]"
2) 重排列 64 个矢量到 8x8 的块中
3) 对 8x8 的块作 IDCT
对 8x8 块的 (Y,Cb,Cr) 重复上面的操作 [Huffman 解码, 步骤 1), 2), 3)]
4) 将所有的有符号的 8bit 数加上 128
5) 转换 YCbCr 到 RGB
JPG 文件(Byte 级)里怎样组织图片信息
-----------------------------------
注意 JPEG/JFIF 文件格式使用 Motorola 格式, 而不是 Intel 格式, 就是说, 如果
是一个字的话, 高字节在前, 低字节在后.
JPG 文件是由一个个段 (segments) 构成的. 每个段长度 <=65535. 每个段从一个标
记字开始. 标记字都是 0xff 打头的, 以非 0 字节和 0xFF 结束. 例如 'FFDA' ,
'FFC4', 'FFC0'. 每个标记有它特定意义, 这是由第2字节指明的. 例如, SOS (Start
Of Scan = 'FFDA') 指明了你应该开始解码. 另一个标记 DQT (Define Quantization
Table = 0xFFDB) 就是说它后面有 64 字节的 quantization 表
在处理 JPG 文件时, 如果你碰到一个 0xFF, 而它后面的字节不是 0, 并且这个字节
没有意义. 那么你遇到的 0xFF 字节必须被忽略. (一些 JPG 里, 常用用 0xFF 做某
些填充用途) 如果你在做 huffman 编码时碰巧产生了一个 0xFF, 那么就用 0xFF
0x00 代替. 就是说在 jpeg 图形解码时碰到 FF00 就把它当作 FF 处理.
另外在 huffman 编码区域结束时, 碰到几个 bit 没有用的时候, 应该用 1 去填充.
然后后面跟 FF.
下面是几个重要的标记
--------------------
SOI = Start Of Image = 'FFD8'
这个标记只在文件开始出现一次
EOI = End Of Image = 'FFD9'
JPG 文件都以 FFD9 结束
RSTi = FFDi ( i = 0..7) [ RST0 = FFD0, RST7=FFD7]
= 复位标记
通常穿插在数据流里, 我想是担心 JPG 解码出问题吧(应该配合 DRI 使用). 不过很
多 JPG 都不使用它
(SOS --- RST0 --- RST1 -- RST2 --...
...-- RST6 --- RST7 -- RST0 --...)
----
标记
----
下面是必须处理的标记
SOF0 = Start Of Frame 0 = FFC0
SOS = Start Of Scan = FFDA
APP0 = it's the marker used to identify a JPG file which uses the JFIF
specification = FFE0
COM = Comment = FFFE
DNL = Define Number of Lines = FFDC
DRI = Define Restart Interval = FFDD
DQT = Define Quantization Table = FFDB
DHT = Define Huffman Table = FFC4
JPG 文件中 Haffman 表的储存
---------------------------
JPEG 里定义了一张表来描述 Haffman 树. 定义在 DHT 标记后面. 注意: Haffman
代码的长度限制在 16bit 内.
一般一个 JPG 文件里会有 2 类 Haffman 表: 一个用于 DC 一个用于 AC (实际有 4
个表, 亮度的 DC,AC 两个, 色度的 DC,AC 两个)
这张表是这样保存的:
1) 16 字节:
第 i 字节表示了 i 位长的 Huffman 代码的个数 (i= 1 到 16)
2) 这表的长度 (字节数) = 这 16 个数字之和
现在你可以想象这张表怎么存放的吧? 对应字节就是对应 Haffman 代码等价数字. 我
不多解释, 这需要你先了解 Haffman 算法. 这里只举一个例子:
Haffman 表的表头是 0,2,3,1,1,1,0,1,0,0,0,0,0,0,0,0
就是说长度为 1 的代码没有
长度为 2 的代码为 00
01
长度为 3 的代码是 100
101
110
长度为 4 的代码是 1110
长度为 5 的代码是 11110
长度为 6 的代码是 111110
长度为 7 的代码没有 (如果有一个的话应该是 1111110)
长度为 8 的代码是 11111100
.....
后面都没有了.
如果表下面的数据是
45 57 29 17 23 25 34 28
就是说
45 = 00
57 = 01
29 = 100
17 = 101
23 = 110
等等...
如果你懂 Haffman 编码, 这些不难理解
采样系数
--------
下面讲解的都是真彩 JPG 的解码, 灰度 JPG 的解码很简单, 因为图形中只有亮度信
息. 而彩色图形由 (Y, Cr, Cb) 构成, 前面提到过, Y 通常是每点采样一次, 而 Cr,
Cb 一般是 2x2 点采样一次, 当然也有的 JPG 是逐点采样, 或者每两点采样 (横向
两点, 纵向一点) 采样系数均被定义成对比最高采样系数的相对值.
一般情况 (即: Y 逐点采样, Cr Cb 每 2x2 点一次) 下: Y 有最高的采样率, 横向采
样系数HY=2 纵向采样系数 VY=2; Cb 的横向采样系数 HCb=1, 纵向采样系数 VCb=1;
同样 HCr=1, VCr=1
在 Jpeg 里, 8x8 个原始数据, 经过 RLC, Huffman 编码后的一串数据流称为一个
Data Unit (DU) JPG 里按 DU 为单位的编码次序如下:
1) for (counter_y=1;counter_y<=VY;counter_y++)
for (counter_x=1;counter_x<=HY;counter_x++)
{ 对 Y 的 Data Unit 编码 }
2) for (counter_y=1;counter_y<=VCb ;counter_y++)
for (counter_x=1;counter_x<=HCb;counter_x++)
{ 对 Cb 的 Data Unit 编码 }
3) for (counter_y=1;counter_y<=VCr;counter_y++)
for (counter_x=1;counter_x<=HCr;counter_x++)
{ 对 Cr 的 Data Unit 编码 }
按我上面的例子: (HY=2, VY=2 ; HCb=VCb =1, HCr,VCr=1) 就是这样一个次序
YDU,YDU,YDU,YDU,CbDU,CrDU
这些就描述了一块 16x16 的图形. 16x16 = (Hmax*8 x Vmax*8) 这里 Hmax=HY=2
Vmax=VY=2
一个 (Hmax*8,Vmax*8) 的块被称作 MCU (Minimun Coded Unix) 前面例子中一个
MCU = YDU,YDU,YDU,YDU,CbDU,CrDU
如果 HY =1, VY=1
HCb=1, VCb=1
HCr=1, VCr=1
这样 (Hmax=1,Vmax=1), MCU 只有 8x8 大, MCU = YDU,CbDU,CrDU
对于灰度 JPG, MCU 只有一个 DU (MCU = YDU)
JPG 文件里, 图象的每个组成部分的采样系数定义在 SOF0 (FFC0) 标记后
简单说一下 JPG 文件的解码
-------------------------
解码程序闲从 JPG 文件中读出采样系数, 这样就知道了 MCU 的大小, 算出整个图象
有几个 MCU. 解码程序再循环逐个对 MCU 解码, 一直到检查到 EOI 标记. 对于每个
MCU, 按正规的次序解出每个 DU, 然后组合, 转换成 (R,G,B) 就 OK 了
附:JPEG 文件格式
~~~~~~~~~~~~~~~~
- 文件头 (2 bytes): $ff, $d8 (SOI) (JPEG 文件标识)
- 任意数量的段 , 见后面
- 文件结束 (2 bytes): $ff, $d9 (EOI)
段的格式:
~~~~~~~~~
- header (4 bytes):
$ff 段标识
n 段的类型 (1 byte)
sh, sl 该段长度, 包括这两个字节, 但是不包括前面的 $ff 和 n.
注意: 长度不是 intel 次序, 而是 Motorola 的, 高字节在前,
低字节在后!
- 该段的内容, 最多 65533 字节
注意:
- 有一些无参数的段 (下面那些前面注明星号的)
这些段没有长度描述 (而且没有内容), 只有 $ff 和类型字节.
- 每一个段结束到下一个 $ff 间的数据都是合法的, 必须被忽略掉.
段的类型:
~~~~~~~~~
*TEM = $01 可以忽略掉
SOF0 = $c0 帧开始 (baseline JPEG), 细节附后
SOF1 = $c1 dito
SOF2 = $c2 通常不支持
SOF3 = $c3 通常不支持
SOF5 = $c5 通常不支持
SOF6 = $c6 通常不支持
SOF7 = $c7 通常不支持
SOF9 = $c9 arithmetic 编码(Huffman 的一种扩展算法), 通常不支持
SOF10 = $ca 通常不支持
SOF11 = $cb 通常不支持
SOF13 = $cd 通常不支持
SOF14 = $ce 通常不支持
SOF14 = $ce 通常不支持
SOF15 = $cf 通常不支持
DHT = $c4 定义 Huffman Table, 细节附后
JPG = $c8 未定义/保留 (引起解码错误)
DAC = $cc 定义 Arithmetic Table, 通常不支持
*RST0 = $d0 RSTn 用于 resync, 通常被忽略
*RST1 = $d1
*RST2 = $d2
*RST3 = $d3
*RST4 = $d4
*RST5 = $d5
*RST6 = $d6
*RST7 = $d7
SOI = $d8 图片开始
EOI = $d9 图片结束
SOS = $da 扫描行开始, 细节附后
DQT = $db 定义 Quantization Table, 细节附后
DNL = $dc 通常不支持, 忽略
DRI = $dd 定义重新开始间隔, 细节附后
DHP = $de 忽略 (跳过)
EXP = $df 忽略 (跳过)
APP0 = $e0 JFIF APP0 segment marker (细节略)
APP15 = $ef 忽略
JPG0 = $f0 忽略 (跳过)
JPG13 = $fd 忽略 (跳过)
COM = $fe 注释, 细节附后
其它的段类型都保留必须跳过
SOF0: Start Of Frame 0:
~~~~~~~~~~~~~~~~~~~~~~~
- $ff, $c0 (SOF0)
- 长度 (高字节, 低字节), 8+components*3
- 数据精度 (1 byte) 每个样本位数, 通常是 8 (大多数软件不支持 12 和 16)
- 图片高度 (高字节, 低字节), 如果不支持 DNL 就必须 >0
- 图片宽度 (高字节, 低字节), 如果不支持 DNL 就必须 >0
- components 数量(1 byte), 灰度图是 1, YCbCr/YIQ 彩色图是 3, CMYK 彩色图
是 4
- 每个 component: 3 bytes
- component id (1 = Y, 2 = Cb, 3 = Cr, 4 = I, 5 = Q)
- 采样系数 (bit 0-3 vert., 4-7 hor.)
- quantization table 数
DRI: Define Restart Interval:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- $ff, $dd (DRI)
- 长度 (高字节, 低字节), 必须是 4
- MCU 块的单元中的重新开始间隔 (高字节, 低字节),
意思是说, 每 n 个 MCU 块就有一个 RSTn 标记.
第一个标记是 RST0, 然后是 RST1 等, RST7 后再从 RST0 重复
DQT: Define Quantization Table:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- $ff, $db (DQT)
- 长度 (高字节, 低字节)
- QT 信息 (1 byte):
bit 0..3: QT 号(0..3, 否则错误)
bit 4..7: QT 精度, 0 = 8 bit, 否则 16 bit
- n 字节的 QT, n = 64*(精度+1)
评论:
- 一个单独的 DQT 段可以包含多个 QT, 每个都有自己的信息字节
- 当精度=1 (16 bit), 每个字都是高位在前低位在后
DAC: Define Arithmetic Table:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
法律原因, 现在的软件不支持 arithmetic 编码.
不能生产使用 arithmetic 编码的 JPEG 文件
DHT: Define Huffman Table:
~~~~~~~~~~~~~~~~~~~~~~~~~~
- $ff, $c4 (DHT)
- 长度 (高字节, 低字节)
- HT 信息 (1 byte):
bit 0..3: HT 号 (0..3, 否则错误)
bit 4 : HT 类型, 0 = DC table, 1 = AC table
bit 5..7: 必须是 0
- 16 bytes: 长度是 1..16 代码的符号数. 这 16 个数的和应该 <=256
- n bytes: 一个包含了按递增次序代码长度排列的符号表
(n = 代码总数)
评论:
- 一个单独的 DHT 段可以包含多个 HT, 每个都有自己的信息字节
COM: 注释:
~~~~~~~~~~
- $ff, $fe (COM)
- 注释长度 (高字节, 低字节) = L+2
- 注释为长度为 L 的字符流
SOS: Start Of Scan:
~~~~~~~~~~~~~~~~~~~
- $ff, $da (SOS)
- 长度 (高字节, 低字节), 必须是 6+2*(扫描行内组件的数量)
- 扫描行内组件的数量 (1 byte), 必须 >= 1 , <=4 (否则是错的) 通常是 3
- 每个组件(部分): 2 bytes
- component id (1 = Y, 2 = Cb, 3 = Cr, 4 = I, 5 = Q), 见 SOF0
- 使用的 Huffman 表00-1-14:
- bit 0..3: AC table (0..3)
- bit 4..7: DC table (0..3)
- 忽略 3 bytes (???)