Python借助Pillow批量修改图片EXIF/IPTC备注信息

陈默
陈默
发布时间:2026-11-04 | 11人浏览过
原创

深入解析并实战演示如何借助Python官方主流图像库Pillow批量读取、修改与写入图片EXIF和IPTC备注信息,详细讲解编码规范与工程化安全避坑策略。

在自动化图库归档、内容采编与数字版权资产管理中,为已有图像批量注入或更新说明、注释及摄影信息是一项基础且频繁的任务。借助 Python 官方生态中主流且稳定的图像处理库 Pillow(PIL Fork),我们可以通过内置的 getexif() 与 ExifTags API 直接读写与标准化封装 EXIF 字典(如关键的 ImageDescription 与 UserComment 标签),同时结合规范的二进制数据块处理与 Photoshop/IPTC 兼容的说明字段。本文将以严谨实用的步骤,详细讲解单张测试到生产级批量处理的完整实现路径,并深入剖析字符编码规范与重编码画质控制等关键避坑点。
Python借助Pillow批量修改图片EXIF/IPTC备注信息
图 0:Python 结合 Pillow 实现图片元数据批量改写方案总览

一、EXIF 与 IPTC 备注信息的底层结构与差异

在处理图像元数据之前,必须先厘清 EXIF 与 IPTC 在格式规范、应用场景及图像容器内部存储位置上的本质区别。很多开发者在初次尝试修改“图片备注”时,常发现写入的字段在 Windows 资源管理器中可见,但在 Photoshop、Lightroom 或专业摄影软件中显示为空,其根本原因就在于两者分属不同的元数据载体段。

以最常见的 JPEG 图像文件为例,JPEG 采用分段(Marker)数据流协议进行封装。图像文件的起始标记为 SOI (0xFFD8),结束标记为 0xFFD9。在图像像素数据正式开始之前,可以插入若干个应用专用标记段(APP Marker):

  • APP1 段 (0xFFE1 - EXIF 规范):记录拍摄器材、快门光圈、拍摄时间、GPS 定位以及相机说明。其中用于存储备注信息的两个核心标签为 0x010E(ImageDescription,图像描述)和 0x9286(UserComment,用户注释)。EXIF 属于 TIFF 标签树结构,具有严格的层级与类型定义。
  • APP13 段 (0xFFED - Photoshop 8BIM / IPTC 规范):主要用于新闻传媒、出版与资产管理。IPTC IIM(Information Interchange Model)规范以“记录(Record): 数据集(DataSet)”的形式组织,例如 2:120 代表题注/说明(Caption/Abstract),2:80 代表署名作者(Byline),2:116 代表版权声明(Copyright Notice)。
Python借助Pillow批量修改图片EXIF/IPTC备注信息
图 1:JPEG 标记段内部结构与 EXIF (APP1) / IPTC (APP13) 存储对照

在 Python 生态中,Pillow 对两者的原生支持程度存在差异:

特性维度 EXIF (APP1) IPTC (APP13)
主要用途 设备参数、通用拍摄说明、Windows/macOS 默认属性 新闻采编、图库版权管理、Adobe 全家桶工作流
Pillow 原生读取 img.getexif() / img._getexif() 完整原生解析 IptcImagePlugin.getiptcinfo(img) 原生解析字典
Pillow 原生写入 img.save(..., exif=exif_obj) 原生无缝打包 保留已有 photoshop 原始块或借助专用元数据块同步
跨平台兼容性 极佳,任何操作系统与主流看图器均能直接展示 针对专业后期、数字图库管理系统(DAM)效果更佳

二、开发准备与必要环境配置

本项目运行环境要求 Python 3.8 及以上版本。Pillow 经过多个主要版本的演进,在 8.0 之后重构了 EXIF 接口,提供了面向对象的 Image.Exif 类型,因此建议安装最新稳定版 Pillow。

# 在终端或虚拟环境中安装 Pillow
pip install --upgrade Pillow

在 Python 代码中,我们将使用以下核心模块:

import os
from pathlib import Path
from PIL import Image, ExifTags, IptcImagePlugin

三、单张图片读取与写入 EXIF 备注信息

在批量处理前,我们先通过单张图片彻底搞清楚 Pillow 是如何读写 EXIF 字典的。

1. 读取已有 EXIF 备注信息

通过 img.getexif() 可以获得一个可迭代并支持字典操作的 Image.Exif 实例。我们可以利用 ExifTags.TAGS 将十六进制标签映射为人性化的英文字符串:

def read_image_exif_comments(image_path: str):
    with Image.open(image_path) as img:
        exif = img.getexif()
        if not exif:
            print("该图片未包含任何 EXIF 标记段。")
            return
        
        # 读取 0x010E (ImageDescription)
        desc = exif.get(0x010E, "未设置")
        # 读取 0x9286 (UserComment)
        user_comment = exif.get(0x9286, "未设置")
        
        print(f"文件名: {Path(image_path).name}")
        print(f"图像描述 (ImageDescription, 0x010E): {desc}")
        print(f"用户注释 (UserComment, 0x9286): {user_comment}")

2. 核心细节:UserComment 的 8 字节编码头协议

修改 ImageDescription(0x010E)非常直观,直接赋予 UTF-8 字符串即可。但是对于 UserComment(0x9286),EXIF 2.3 规范明确规定:该字段的前 8 个字节是字符编码标识头,随后的字节流才是真正的文本数据。如果直接写入裸中文文本,在很多操作系统和看图软件中会直接呈现为乱码。

Python借助Pillow批量修改图片EXIF/IPTC备注信息
图 2:EXIF UserComment 字段的前 8 字节协议标识与有效载荷排布机制

规范中常见的编码头定义如下:

  • b'UNICODE ':8 字节头。后续有效载荷采用 UTF-8 或 UTF-16 字节流。这是目前支持中文最稳健的方案。
  • b'ASCII ':8 字节头。后续有效载荷采用 7-bit ASCII 字符串,仅适合英文数字。
  • b' ':未定义(Undefined)模式。

因此,构建可兼容全平台的 UserComment 二进制包的函数如下:

def encode_user_comment(comment_text: str) -> bytes:
    """
    将中文字符串编码为符合 EXIF 规范的 UserComment 二进制数据
    采用 8 字节 UNICODE 标识头 + UTF-8 编码内容
    """
    header = b'UNICODE '
    payload = comment_text.encode('utf-8')
    return header + payload

3. 单张图片完整改写示范

def update_single_image_exif(src_path: str, dst_path: str, description: str, comment: str):
    """
    更新单张图片的 EXIF 备注信息并保存
    """
    with Image.open(src_path) as img:
        # 获取或创建 EXIF 对象
        exif = img.getexif()
        
        # 写入 0x010E (ImageDescription - 图像描述)
        exif[0x010E] = description
        
        # 写入 0x9286 (UserComment - 规范打包的注释)
        exif[0x9286] = encode_user_comment(comment)
        
        # 提取色彩描述文件 (ICC Profile),防止保存后色偏
        icc_profile = img.info.get('icc_profile')
        
        # 安全保存,指定高质量与保留色彩
        img.save(
            dst_path,
            exif=exif,
            quality=95,
            subsampling=0,
            icc_profile=icc_profile
        )
        print(f"已成功写入元数据并保存至: {dst_path}")

四、IPTC 信息的读取与协同处理机制

当需要对接 Adobe Lightroom、Photoshop 或新闻通信社图库系统时,仅修改 EXIF 可能不足以满足需求。Pillow 内置了 IptcImagePlugin,可直接解析 JPEG 的 APP13 Photoshop 8BIM 资源块:

def read_iptc_data(image_path: str):
    with Image.open(image_path) as img:
        # getiptcinfo 返回形如 { (2, 120): b'题注内容', (2, 80): b'作者' } 的字典
        iptc = IptcImagePlugin.getiptcinfo(img)
        if not iptc:
            print("该图片未包含 IPTC 元数据。")
            return
        
        # (2, 120) 为 Caption/Abstract 题注
        caption_bytes = iptc.get((2, 120), b"")
        try:
            caption = caption_bytes.decode('utf-8')
        except UnicodeDecodeError:
            caption = caption_bytes.decode('gbk', errors='ignore')
            
        print(f"IPTC 题注: {caption}")
工程经验提示:Pillow 与 IPTC 协同策略
Pillow 原生对 EXIF 的读写是完全对称的,而对于 IPTC 8BIM 复杂资源树,Pillow 主要擅长解码读取。如果在批量重写过程中需要最大程度保留原有 IPTC 块,可以在 img.save() 时通过 photoshop=img.info.get('photoshop') 将原有的 Photoshop APP13 数据块原封不动传递给新图片。

五、批量修改图片备注信息的完整工程实现

在批量处理场景下,必须考虑目录扫描、递归支持、格式过滤、异常隔离、画质保障以及进度统计。下面给出一套可直接落地运行的完整工程化脚本。

Python借助Pillow批量修改图片EXIF/IPTC备注信息
图 3:生产级图片元数据批量修改流水线逻辑架构

完整批处理代码实现

# -*- coding: utf-8 -*-
"""
batch_exif_modifier.py
功能:借助 Pillow 批量为指定目录下的所有图片写入/修改 EXIF 备注与描述
"""
import os
import sys
from pathlib import Path
from PIL import Image, ExifTags

def build_user_comment(text: str) -> bytes:
    """构建符合 EXIF 规范的 UserComment 二进制头"""
    return b'UNICODE\x00' + text.encode('utf-8')

def batch_process_metadata(
    input_dir: str,
    output_dir: str,
    description_template: str,
    comment_template: str,
    recursive: bool = True
):
    """
    批量修改图片 EXIF 备注信息
    :param input_dir: 原始图片所在目录
    :param output_dir: 输出保存目录(与原目录隔离,保障安全)
    :param description_template: 图像描述模板,支持 {filename} 占位符
    :param comment_template: 用户注释模板,支持 {filename} 占位符
    :param recursive: 是否递归遍历子文件夹
    """
    in_path = Path(input_dir).resolve()
    out_path = Path(output_dir).resolve()
    
    if not in_path.exists() or not in_path.is_dir():
        raise ValueError(f"输入路径不存在或不是文件夹: {input_dir}")
    
    # 支持的图片扩展名(JPEG/TIFF 最为标准)
    supported_extensions = {'.jpg', '.jpeg', '.jpe', '.tif', '.tiff'}
    
    # 获取扫描模式
    pattern = "**/*" if recursive else "*"
    all_files = [p for p in in_path.glob(pattern) if p.suffix.lower() in supported_extensions]
    
    print(f"=== 开始批量元数据处理 ===")
    print(f"扫描目录: {in_path}")
    print(f"输出目录: {out_path}")
    print(f"共发现符合格式图片: {len(all_files)} 张\n")
    
    success_count = 0
    skipped_count = 0
    error_count = 0
    
    for idx, file_path in enumerate(all_files, start=1):
        # 维持原有的子目录相对层级
        relative_path = file_path.relative_to(in_path)
        target_file_path = out_path / relative_path
        
        # 确保目标上级目录存在
        target_file_path.parent.mkdir(parents=True, exist_ok=True)
        
        try:
            with Image.open(file_path) as img:
                # 1. 提取或初始化 EXIF
                exif = img.getexif()
                
                # 2. 组装个性化备注内容
                filename_stem = file_path.stem
                current_desc = description_template.format(filename=filename_stem)
                current_comm = comment_template.format(filename=filename_stem)
                
                # 3. 赋值标准 EXIF 标签
                exif[0x010E] = current_desc  # ImageDescription
                exif[0x9286] = build_user_comment(current_comm)  # UserComment
                
                # 4. 保留原始 ICC 色彩空间配置与 Photoshop 块
                save_kwargs = {
                    "exif": exif,
                    "quality": 95,
                    "subsampling": 0
                }
                if 'icc_profile' in img.info:
                    save_kwargs['icc_profile'] = img.info['icc_profile']
                if 'photoshop' in img.info:
                    save_kwargs['photoshop'] = img.info['photoshop']
                    
                # 5. 写入目标路径
                img.save(target_file_path, **save_kwargs)
                
                success_count += 1
                print(f"[{idx}/{len(all_files)}] 成功: {relative_path}")
                
        except (OSError, SyntaxError) as e:
            error_count += 1
            print(f"[{idx}/{len(all_files)}] 失败 (图片破损或格式不支持): {relative_path}, 错误: {e}")
        except Exception as e:
            error_count += 1
            print(f"[{idx}/{len(all_files)}] 未知异常: {relative_path}, 错误: {e}")
            
    print(f"\n=== 处理完毕统计 ===")
    print(f"成功: {success_count} | 失败: {error_count} | 跳过: {skipped_count}")
    print(f"所有处理后图片已安全保存至: {out_path}")

# 示例调用入口
if __name__ == "__main__":
    SRC_DIR = "./raw_photos"
    DST_DIR = "./tagged_photos"
    
    # 模拟创建测试目录与执行
    Path(SRC_DIR).mkdir(parents=True, exist_ok=True)
    
    # 动态模板:支持将文件名自动拼入备注
    DESC_TPL = "数字资产档案 - 名称: {filename}"
    COMM_TPL = "归档时间: 2026年 | 项目组: 视觉媒体资源中心 | 校验编号: #{filename}"
    
    batch_process_metadata(
        input_dir=SRC_DIR,
        output_dir=DST_DIR,
        description_template=DESC_TPL,
        comment_template=COMM_TPL,
        recursive=True
    )

六、技术决策与避坑指南

在批量处理海量图像元数据时,许多隐藏问题会在特定场景下暴露。下述决策矩阵与建议能够帮助你在系统设计中防患于未然:

Python借助Pillow批量修改图片EXIF/IPTC备注信息
图 4:图片元数据批处理关键指标权衡与跨系统避坑矩阵

1. 重压缩画质控制陷阱

Pillow 的 img.save() 在重新写入 JPEG 时,默认会调用 libjpeg 对像素数据重新进行 DCT 离散余弦变换与量化压缩。如果直接使用默认参数(quality=75),多次改写元数据会导致图像画质出现肉眼可见的劣化。

应对方案:在调用 img.save() 时,务必显式传入 quality=95 和 subsampling=0(关闭色度抽样,采用 4:4:4 采样)。这可以在重编码中最大程度保护高频边缘细节与色彩饱和度。

2. 色彩偏灰与 ICC Profile 丢失

许多来自单反或手机的专业照片嵌入了 Display P3 或 Adobe RGB 色彩配置文件。如果在保存新图像时未显式传递 icc_profile=img.info.get('icc_profile'),Pillow 默认会丢弃该配置块,导致新图片在某些显示器或浏览器上呈现暗淡偏灰的视觉效果。

3. Windows 与 macOS 属性查看器差异

Windows 右键属性面板中,顶部的“标题/说明”字段读取的是 0x010E (ImageDescription),而下方的“备注”字段在不同系统版本中会优先寻找 0x9286 (UserComment) 或微软专有的 0x9C9C (XPComment)。因此,最佳工程实践是在批处理逻辑中同时对 0x010E 与 0x9286 进行赋值,以获得最佳的跨平台穿透兼容性。

4. 结果验证方法

在批处理完成后,验证改写是否生效有两种常用方式:

  1. 代码自动化回读校验:直接使用上文编写的 read_image_exif_comments() 函数,对输出目录中的随机样本进行断言检查。
  2. 专业工具交叉核对:在终端运行 exiftool -ImageDescription -UserComment -IPTC:Caption-Abstract filename.jpg,确认所有 APP1 和 APP13 段的数据完整落盘。
相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表