JPEG是联合图象专家组(Joint Picture Expert Group)的英文缩写,是国际标准化组织(ISO)和CCITT联合制定的静态图象的压缩编码标准。
而我们通常说的JPEG指的是以JPEG格式压缩的图片(即文件后缀为.jpeg
.jpe
)。经过JPEG重新编码的图片,文件压缩率可以达到90%以上,而且图片本身还具有较好的图片质量。这也是JPEG成为目前互联网上被用来存储和传输图片应用最广泛的格式的一个重要原因。
JPEG本身只有描述如何将一个视频/图片转换为字节的数据流(streaming),但并没有说明这些字节如何在任何特定的存储媒体上被封存起来。
这里要对JPEG做一个补充说明,很多人把JPEG标准和JPEG文件格式理解成一个东西。然而实际并不是这样的,JPEG标准主要还是围绕编解码的部分(如DCT变换、量化、哈夫曼树等等),虽然在JPEG标准中也定义了“JPEG Interchange Format (JIF)”的文件存储格式,但是因为Encoder和Decoder完整实现JIF很困难,且JIF标准也存在一些缺陷,因此JIF并没有被推广开来。倒是后来出现的“JPEG File Interchange Format (JFIF)” 和 “Exchange image file Format(Exif)” 等新的存储格式成为了主流。Exif 也好 JFIF 也罢,他们都是遵循 JIF标准的,两者只是在JIF的基础上增加了一些各自的Marker.
相比于BMP文件结构,JPEG文件结构要复杂得多。由于Exif和JFIF格式的都是遵循JIF的标准,在存储格式上沿袭了统一的 JPEG Marker
+ Compressed Data
的方式。整个文件根据不同的Marker划分成不同的标记段
。每个Marker的长度为固定的 2 Byte.
Marker名称 | Marker内容 | 说明 |
---|---|---|
SOI | 0xFFD8 | Start Of Image |
SOF0 | 0xFFC0 | Start Of Frame 0 |
SOF2 | 0xFFC2 | Start of Frame 2 |
DHT | 0xFFC4 | Define Huffman Table(s) |
DQT | 0xFFDB | Define Quantization Table(s) |
DRI | 0xFFDD | Define Restart Interval |
SOS | 0xFFDA | Start of Scan |
RST0~RST7 | 0xFFD0 ~ 0xFFD7 | Restart |
APP0~APP15 | 0xFFE0 ~ 0xFFEF | Application-sepcific |
COM | 0xFFFE | Comment |
EOI | 0xFFD9 | End of Image |
上面这张表列举了JPEG 的主要 Marker。文件按照Marker的划分成不同的标记段
,每个标记段
结构轮廓一致,如下图所示。Detail Data
部分的结构根据不同的Marker的定义而进行不同的细分。
[ JPEG数据段结构 ]
SOI (0xFFD8) 和 EOI (0xFFD9) 作为JPEG文件的起止标志,不参照上图的数据划分。 所有的JPEG文件的头两个字节一定是 0xFFD8 最后两个字节是 0xFFD9
下图展示了JPEG两种文件结构的基本样式
[ Exif文件结构与JFIF文件结构比较 ]
可以看出两者基本上是一致的,最大的差异还是APP1
与APP0
以及他们的扩展标记段APP2
与JFXX-APP0
。
[ JFIF APP0标记段结构 ]
JFIF的结构相对比较简单,从APP0
标记码起始地址偏移18个字节后,即可得到对应的缩略图数据数据的地址,这里是图像数据是未压缩过的,这与BMP位图的图像数据格式是一致的。
[ Exif APP1 标记段结构 ]
相比JFIF的结构,Exif APP1标记段的内容就复杂多了。下面我们就对APP1标记段的信息进行详细的说明。
0xFFE1
[ TIFF Header 结构 ]
TIFF Header 一共8个字节
头两个字节表示 Byte align
II
表示数字存储遵循 intel 的字节序,即小端存储MM
表示数据存储遵循 Motorola 的字节序,即大端存储不同的存储字节序的选择主要是因为不同厂商的不同的数码产品的差异引起。大部分的数码相机使用 Intel 的字节序,也有些奇葩的产品的,比如Sony 的大部分产品都是使用Intel字节序的,唯独D700. (求D700心理阴影面积) 重要:字节序直接影响到数据内容,所以在解析Exif数据前必须检查文件的Byte align
中间两个字节表示 Tag Mark,是固定值,
最后四个字节表示到 IFD0(Image File Directory)的偏移。
根据 TIFF Header 的后四个字节,我们可以找到第一个 IFD(Image File Directory)。IFD的数据结构如下表所示。每个IFD结构中存在多个Directory Entry,每个Entry记录着图片的一条属性信息,比如拍摄时间、拍摄机器、图片尺寸等等。
[ Image File Directory 结构示意图 ]
IFD结构的头两个字节存储 entry 的个数,如上表中 directory entry 的个数为 9个
每一个 Directory Entry 的长度固定为12个字节,分为4个部分
TTTT
对应的内容为 Exif Tag
FFFF
对应的内容为 Directory Entry存储内容的类型(Component Type)NNNNNNNN
存储的是 Directory Entry 对应的Component的数量DDDDDDDD
存储的可能是Entry对应的值,Entry对应的值的长度超过四个字节,那么DDDDDDDD
存储的是对应的值的偏移地址(该偏移寻址的基址也是TIFFHEADER的起始位置)。[ Component Type信息对照表 ]
因为 Directory Entry 只有12个字节,用于数据存储的只有最后的4字节,无法存储过长的字符串或总长度超过4字节的数据信息。所以对于总长度超过4字节的信息,实际存储在IFD的Data area 中,在Directory Entry的最后四个字节中存储该信息的偏移地址。
总长度计算公式 总长度 = Component 数量 * 每个Component的字节数
Component的数量在 Directory的第三部分NNNNNNNN
中存储;
每个Component的字节数根据Directory Entry 第二部分的值Component Type
,并结合「Component Type信息对照表」可以查询到。
Exif格式的缩略图存储有主要有两种类型,一种为是以JPEG形式的存储的,一种是以TIFF的形式存储的,TIFF又分为RGB和YCbCr两种形式,严格的说一共是三种形式。其中JPEG形式和RGB的TIFF格式可以直接查看,而YCbCr的TIFF格式需要进行颜色空间的转换后才能正常查看。
缩略图的信息存储在 APP Data标记段的最后部分,缩略图的存储格式、起始地址和缩略图长度是由IFD1
部分中Directory Entry的值来决定。
缩略图的存储格式由IFD1
中的Exif Tag
为0x0103
(含义为Compression) 的Directory Entry的值来决定
StripOffset
表示缩略图偏移位置;
StripByteCounts
表示缩略图长度;
PhotometricInterpretation
= 1时,是RGB形式的TIFF格式存储;
PhotometricInterpretation
= 6时,是YCbCr形式的TIFF格式存储;
起始的偏移地址由IFD1
段的Exif Tag
为0x0201
(JpegIFOffset)的决定;
缩略图的长度由IFD1
中的Exif Tag
为0x0202
(JpegIFByteCount)来决定。
Exif Tag
分别是0x0111
(StripOffset) 表示缩略图偏移位置;0x0117
(StripByteCounts) 表示缩略图长度;0x0106
(PhotometricInterpretation).值为1时是RGB形式的TIFF格式存储;值为6时,是YCbCr形式的TIFF格式存储;上面已经将Exif APP1结构的做了逐一介绍,下面我们绘制成了一张图来展示Exif IFD的树形结构
[ Exif IFD树形结构 ]
IFD0
~IFDn
根据 Offset to next IFD 来指向下一个 IFD,形成了一个单链表。在解析该链表时,我们只要根据 “Offset to next IFD”是否为0x00000000
来判断当前节点是不是最后一个节点。IFD0
中的Exif Tag为0x8769(EXIF_OFFSET)的Entry的值为 Exif Sub IFD的偏移地址。Exif Sub IFD
中的Exif Tag为0xa005
(InteropOffset)的Entry值为 Interoperability IFD的偏移地址。比较过 JFIF 格式APP0 和 Exif APP1的结构后,Exif的结构明显比JFIF的要复杂,但是也因此具有更大的灵活度,可以存储更多的信息(可以访问上面Exif Tag列表的链接,就可以感受到Exif信息庞大),这也是当前越来越多的数码产品使用Exif来存储的图片的原因之一。JFIF APP0的结构信息是线性顺序排列的,解析相对简单,这里我们主要以Exif APP1的结构即解析为例展开说明。查看Exif信息的工具有很多,一般系统自带的工具就可以查看。
打开图片后点击菜单栏 工具-> 显示检查器 -> 选择 Exif 即可
[ Mac自带的图片查看其查看Exif信息 ]
ExifTool是Phil Harvey以Perl写成的免费开源软件,可读写及处理图像、视频及音频的metadata,例如Exif、IPTC、XMP、JFIF、GeoTIFF、ICC Profile。它是跨平台的,可作为命令列或Perl函式库使用。
exiftool 的安装
$ brew install exiftool
exiftool 的使用
$ exiftool image_42x42.jpg
输出部分信息如下
exiftool 会将解析完的信息都展示出来,中间的解析过程都是不可见的,为了更好的理解Exif的结构,我写了一些代码来提取APP1
标记段的内容,并初步解析了含义。
实验的图片是以一张手机拍摄的图片,原始尺寸为 5480x4110。
[ 原始图片的截图 ]
由于尺寸太大不便于文件格式的分析,所以通过Photoshop对图片进行裁剪并缩小为 42x42的小图片(该操作并不会影响图片本身的Exif信息)。
[ 实验素材图 ]
使用vim打开该文件,并转换成十六进制的格式查看。根据前面介绍的APP1的格式,可以对文件做如下划分
[ 十六进制的图片数据 ]
上图绿色和橙色高亮的部分是 Directory Entry的数据段,做了颜色标注之后,方便理解。蓝色高亮部分0000 03b4
是IFD的Offset to next IFD
信息,该部分往后便是 Data Area of IFD0
部分;这里已经可以看到一些字符串信息,如手机型号,拍摄时间,图像处理软件等
[ IFD0 信息提取 ]
根据 Exif Offset
可以获取到 Exif Sub IFD
的信息如下
[ Exif Sub IFD 信息提取 ]
根据IFD0
的Offset to next IFD
可以读取 IFD1
的信息如下
[ IFD1 信息提取 ]
可以看到IFD1
的Offset to next IFD
是0x0000 0000
,表示该节点为最后一个IFD
节点,利用其中的Compression
,JPEG IF Offset
,JPEG IF Byte Count
信息将对应位置的内容读出来,并单独写入文件即实现了从 Exif中提取缩略图的功能。下图exifThumbnail_001.jpg
是按照这里说的方法从Exif中提取出的缩略图。
[ 从Exif中提取的缩略图信息 ]
这是两个文件的大小
-rw-r--r--@ 1 shaoling staff 13429 10 12 17:49 image_42x42.jpg
-rw-r--r--@ 1 shaoling staff 1034 10 25 18:42 exifThumbnail_001.jpg
获取的缩略图exifThumbnail_001.jpg
在展示效果上与实验图片image_42x42.jpg
在视觉效果上并没有肉眼可见的差距。
实验图片image_42x42.jpg 大小为 13429字节,提取出的缩略图文件exifThumbnail.jpg大小只有 1034字节;可见实验图片包含了很多其他的信息。这里我们可以使用exiv2
这个工具来帮助分析。
$ exiv2 -pS image_42x42.jpg
$ exiv2 -pS exifThumbnail_001.jpg
得到如下结果
可以看到image_42x42.jpg中APP段的信息(APP1 + APP13 + APP1 + APP2 + APP14)长度就12205字节,占了文件大小的 90.89%。
汇总成表格后可以明显的看到两者的差异主要是因为APP段的应用数据差异导致的,与图像本身相关的数据两者的差异并不大。
Tips: 使用 exiftool 来获取exif的缩略图的方法
$ exiftool -b -ThumbnailImage image_42x42.jpg > thumbnail.jpg
作者简介:shaoling, 天天P图AND工程师
文章后记: 天天P图是由腾讯公司开发的业内领先的图像处理,相机美拍的APP。欢迎扫码或搜索关注我们的微信公众号:“天天P图攻城狮”,那上面将陆续公开分享我们的技术实践,期待一起交流学习!