JPEG文件除了图像数据之外,还保存了与图片相关的各种信息,这些信息通过不同类型的TAG存储在文件中。
TAG
JPEG通过TAG标记压缩书记之外的信息。所有的TAG都包含一个TAG类型,TAG类型大小为两个字节,位于一个TAG的最前面。TAG类型的第一个字节一定为0xFF
以下是部分常见的TAG类型
TAG类型 | 数值 | 备注 |
---|---|---|
SOF0 ~ SOF3 SOF5 ~ SOF7 SOF8 ~ SOF11 SOF13 ~ SOF15 |
FF C0 ~ FF C3 FF C5 ~ FF C7 FF C8 ~ FF CB FF CD ~ FF CF |
不同的编码模式对应不同的SOF,详见JPEG标准Table B.1。本文以常见的SOF0为研究对象 |
DHT | FF C4 | Define Huffman table(s),定义了解码所需的哈夫曼表 |
RSTm | FF D0 ~ FF D7 | 遇到时重置DC系数,数字会递增,具体含义不清楚 |
SOI | FF D8 | Start of Image,表示图像文件的开始 |
EOI | FF D9 | End of Image,表示图像文件结束 |
SOS | FF DA | Start of scan,表示这一个TAG后就是压缩的图像数据,记录了DHT、DQT与图像不同部分的对应关系 |
DQT | FF DB | Define quantization table(s),定义了解码所需的量化表 |
APP0 ~ APP15 | FF E0 ~ FF EF | 应用保存的图片相关信息(如相机信息等) |
压缩数据中也是存在TAG的,虽然大部分TAG都在文件开头,但是也有少部分是例外。如EOI就在文件的末尾,RSTm会出现在压缩数据当中。
那么问题来了,如果压缩数据中有一个字节本身就是0xFF怎么办?JPEG的做法是在0xFF后面再加一个字节0x00,用于表示这不是一个TAG。
因为不知道解码需要哪些TAG,我在尝试写JPEG解码器的时候耗费了大量的时间在研究TAG上。总结出对于常见的JPEG图片解码需要的TAG:
1.SOI和EOI:用于确定文件的开头和结尾
2.DQT和DHT:保存了解码时需要用到的哈夫曼表和量化表
3.SOS:保存了图片不同部分的需要用哪个哈夫曼表
4.SOF:图片的长和宽、采样精度等、使用哪个量化表都保存在SOF中
5.RST:部分图片存在RST,遇到RST时要重置DC系数才能得到正确的图像
哈夫曼表与量化表
这里有个小坑,我原先一直以为DQT和DHT都是一个TAG对应一个表,后来发现一个TAG可以不只一个表
哈夫曼表
哈夫曼表的存储格式如下:
名称 | 长度(bit) | 备注 |
---|---|---|
$Lh$ | 16 | 表示这一个TAG的长度(包括TAG类型的两个字节) |
$Tc$ | 4 | Table class,0=DC表,1=AC表 |
$Th$ | 4 | Huffman table destination identifier,表示该哈夫曼表的id |
$L_i$ | 8 | 表示这一长度的编码个数 |
$V_{i,j}$ | 8 | 表示编码前的原始数据 |
一个DHT中的哈夫曼表个数可以通过长度Lh算出:
$$
Lh = 2 + \sum_{t=1}^{n} (17+m_t)
$$
其中
$$
m_t=\sum_{1}^{16} L_i
$$
除了TAG类型和Lh一个TAG只有一个外,其余的都是每个哈夫曼表都有的。
Th之后是一个长度为16字节的数组,分别对应长度从1bit到16bit的编码个数。
再之后存的是各个编码对应的原始数据(以字节为单位)。JPEG采用的范式哈夫曼编码,可以这些信息推导出数据编码前后的对应关系。
量化表
DQT的结构与DHT结构相似,比DHT还稍微简单一些
名称 | 长度(bit) | 备注 |
---|---|---|
Lq | 16 | 与Lh意义相同,表示这一TAG的长度 |
Pq | 4 | 量化表的精度,0=8bit,1=16bit |
Pq | 4 | 量化表的id |
Qk | 8 | 量化表中的数据 |
量化表大小固定为8x8,也就是一个表有64个数,DQT长度与量化表个数也有类似的关系:
$$
Pq = 2 + \sum_{t=1}^n (65 + 64 \times Pq(t))
$$
SOF
SOF(Start of Frame) TAG的结构如下:
名称 | 长度(bit) | 备注 |
---|---|---|
Lf | 16 | 这一TAG的长度 |
P | 8 | 采样精度 |
Y | 16 | 图片的高度 |
X | 16 | 图片的宽度 |
Nf | 4 | Component的个数 |
Component的结构如下:
名称 | 长度(bit) | 备注 |
---|---|---|
Ci | 8 | Compoenent的id |
Hi | 4 | 水平缩放因子 |
Vi | 4 | 垂直缩放因子 |
Tqi | 8 | 对应的量化表id |
根据我的理解,这里的Component个数相当于色度分量的个数,比如RGB和YUV都是3,灰度图像则是1.
SOS
名称 | 长度(bit) | 备注 |
---|---|---|
Ls | 16 | 这一TAG的长度 |
Ns | 8 | 一个scan内的component数量 |
Ss | 8 | 没用 |
Se | 8 | 没用 |
Ah | 4 | 没用 |
Al | 4 | 没用 |
Scan中还描述了这一Scan内不同Component中哈夫曼表和量化表的对应关系:
名称 | 长度(bit) | 备注 |
---|---|---|
Csi | 8 | 通过id选择Component |
Tdi | 4 | 通过id选择DC哈夫曼表 |
Tai | 4 | 通过id选择AC哈夫曼表 |
到这里JPEG解码所需要的几个重要TAG的结构就介绍完了,接下来就做好准备工作就可以开始解码了。
参考资料
JPEG解码系列博客:多媒体-编解码 - 随笔分类 - OnlyTime_唯有时光 - 博客园 (cnblogs.com)
JPEG标准:Microsoft Word - T081E.DOC (w3.org)
一个Rust写的JPEG解码器:MROS/jpeg_tutorial: 跟我寫 JPEG 解碼器 (Write a JPEG decoder with me) (github.com)
友情链接
我学习过程中写的JPEG图片查看器:Ryan1202/my-tiny-jpeg-viewer: A Tiny Jpeg Viewer (github.com)