跳到主要内容
浏览器
导航
图片浏览器

EXIF 元数据解析

说明 EXIF 元数据在 JPEG 与 TIFF 中的存放位置、TIFF 头的字节序规则与 IFD 条目结构、常用标签与 GPS 子目录、Orientation 对显示方向的影响,以及元数据剥离的两种方式。

发布于 4 分钟阅读 1386 字

一张数码照片除了像素本身,往往还带有一组描述拍摄条件的附属数据:时间、曝光参数、设备标识,有时还有经纬度。这些数据按 EXIF 规范组织,物理上以 TIFF 结构存放。在图片浏览器这类应用中,「读图」因此常常包含两个彼此独立的任务:解码像素,以及解析这段结构化元数据。

EXIF 的数据模型并不复杂,但解析实现容易在几个细节上出错:字节序、偏移基准、内联值与偏移值的区分。以下按结构顺序梳理这些规则。

EXIF 在文件中的位置

在 JPEG 中,EXIF 存放在 APP1 标记段(0xFFE1)内。该段以 Exif\0\0 六个字节开头,其后紧跟一个完整的 TIFF 结构。JPEG 标记段的长度字段为 2 字节,单段能承载的数据量有限,因此写在其中的缩略图通常被限制在较小尺寸。标记段的组织方式可参见渐进式 JPEG 与交错 PNG 的编码原理。

在 TIFF 文件中,EXIF 不是附加段,而是文件本身的一部分:TIFF 头之后就是 IFD 链,EXIF 专用标签直接写在这些 IFD 中。PNG 与 WebP 没有原生的 EXIF 结构,通常以私有块或 RIFF 块的形式嵌入同一份 TIFF 结构,因此解析逻辑可以复用。

TIFF 头与字节序

TIFF 结构以一个 8 字节文件头开始:2 字节字节序标记、2 字节魔数(值为 42)、4 字节给出起始 IFD 的偏移。字节序标记决定后续所有多字节整数的解释方式:

  • II(0x4949):小端序,低位字节在前
  • MM(0x4D4D):大端序,高位字节在前

结构内部的所有偏移都相对于 TIFF 头的起点,而不是相对于文件起点。这一点在解析嵌入 APP1 段中的 EXIF 时尤其容易出错,因为 TIFF 头并不在文件开头。

import struct

def read_tiff_header(buf: bytes):
    # buf 必须从 TIFF 头开始
    endian = "<" if buf[:2] == b"II" else ">"
    magic, first_ifd = struct.unpack_from(endian + "HI", buf, 2)
    if magic != 42:
        raise ValueError("not a TIFF structure")
    return endian, first_ifd

IFD 与条目结构

IFD(Image File Directory)由三部分构成:2 字节的条目数、若干条 12 字节的条目、以及 4 字节的下一 IFD 偏移(为 0 表示链结束)。每个条目的字段含义如下:

struct ifd_entry {              /* 固定 12 字节 */
    uint16_t tag;               /* 标签号,如 0x0112 表示方向 */
    uint16_t type;              /* 类型:1=BYTE 2=ASCII 3=SHORT 4=LONG 5=RATIONAL ... */
    uint32_t count;             /* 值的个数 */
    uint32_t value_or_offset;   /* 值本身,或指向值的偏移 */
};

值能否内联存放取决于类型与数量:若「类型字节数 × 数量」不超过 4,则直接写在这 4 个字节内;否则这 4 个字节存放偏移,指向实际数据。读取时必须先按字节序把 value_or_offset 解释为数值或偏移,再走对应分支,否则会读到错误内容。

常用标签

标签名称含义
0x010EImageDescription图像描述文本
0x0112Orientation显示方向,取值 1–8
0x011AXResolution水平分辨率
0x011BYResolution垂直分辨率
0x0128ResolutionUnit分辨率单位
0x0132DateTime修改时间
0x8769ExifIFDPointer指向 EXIF 子 IFD
0x8825GPSInfoIFDPointer指向 GPS 子 IFD

子 IFD:EXIF 与 GPS

主 IFD 中出现 0x8769 或 0x8825 时,其值是一个偏移,指向另外两个 IFD。它们的结构与主 IFD 相同,只是标签号属于不同命名空间。GPS 子 IFD 的标签集中在 0x0000–0x001F,记录纬度、经度、海拔、时间戳等。

经纬度以三个 RATIONAL 表示度、分、秒,并配合 GPSLatitudeRef、GPSLongitudeRef(值为 N/S 或 E/W)确定符号。解析时的一个常见问题是精度:秒值可能带有多位小数,若用单精度浮点参与后续计算,位数损失会导致坐标偏移。

Orientation 与显示方向

Orientation 描述存储的像素排列与正确观看方向之间的旋转或镜像关系,取值为 1 到 8:

值含义
1不变换
2水平镜像
3旋转 180°
4垂直镜像
5水平镜像后顺时针旋转 270°(转置)
6顺时针旋转 90°
7水平镜像后顺时针旋转 90°
8顺时针旋转 270°

多数解码器按存储顺序输出像素,不会自动应用该标签,方向由显示层负责。若显示层忽略它,竖拍照片就会横置。提供自动旋转选项的实现,通常是在解码后按像素重排,或把对应的旋转矩阵并入后续的绘制变换;后者不改动像素数据,代价是若干位置需要感知当前变换。

元数据与隐私

EXIF 可能包含设备序列号、镜头信息与 GPS 坐标。文件被上传或转发时,这些字段会一并发布,而拍摄者往往并不知情。剥离方式主要有两类:删除承载元数据的整个标记段,或逐个改写选定标签。

前者实现简单,但会连带移除 Orientation,因此需要在剥离前把方向应用到像素上,否则照片会变为横置。后者保留原有结构,只清空或替换敏感字段,代价是要完整解析并重写 IFD 链,还要处理偏移变化带来的连锁修改。

小结

解析 EXIF 的核心是把一段 TIFF 结构读对:先确定字节序,再以 TIFF 头为基准解释偏移,然后区分内联值与偏移值,随后处理子 IFD 与标签语义。存放位置随容器而变,数据模型却始终是同一套 IFD 结构,因此一段解析代码可以在 JPEG、TIFF 与 WebP 之间复用。

图片浏览器 · 相关文章