Pillow ExifTags 模块

下面给你一个 Pillow(PIL Fork)中 ExifTags 模块 的完整入门指南,包含 EXIF 数据读取、写入、解析 GPS、时间戳、相机信息、批量处理、隐私清理、地图定位可视化等高级应用。
(基于 Pillow ≥ 9.0,Python 3.8+)


1. 安装 Pillow

pip install --upgrade pillow

2. 基本导入

from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS

3. EXIF 是什么?

EXIF(Exchangeable Image File Format)是数码相机在照片中嵌入的元数据,包含:

类别示例
相机信息品牌、型号、光圈、快门、ISO
拍摄参数曝光时间、焦距、白平衡
时间地点拍摄时间、GPS 经纬度
缩略图嵌入的小预览图

4. 读取所有 EXIF 数据

img = Image.open("photo.jpg")
exif_data = img.getexif()

if exif_data is None:
    print("无 EXIF 数据")
else:
    for tag_id, value in exif_data.items():
        tag_name = TAGS.get(tag_id, tag_id)
        print(f"{tag_name}: {value}")

5. 常用字段解析(推荐)

def get_exif_info(img_path):
    img = Image.open(img_path)
    exif = img.getexif()
    if not exif:
        return "无 EXIF"

    info = {}

    # 常用字段
    for tag_id in exif:
        tag = TAGS.get(tag_id, tag_id)
        value = exif.get(tag_id)

        if tag == "Make":
            info["相机品牌"] = value
        elif tag == "Model":
            info["相机型号"] = value
        elif tag == "DateTime":
            info["拍摄时间"] = value
        elif tag == "FNumber":
            info["光圈"] = f"f/{value}"
        elif tag == "ExposureTime":
            info["快门速度"] = f"1/{int(1/value)}s" if value < 1 else f"{value}s"
        elif tag == "ISOSpeedRatings":
            info["ISO"] = value
        elif tag == "FocalLength":
            info["焦距"] = f"{value}mm"
        elif tag == "GPSInfo":
            gps_info = {}
            for gps_tag_id in exif.get_ifd(tag_id):
                gps_tag = GPSTAGS.get(gps_tag_id, gps_tag_id)
                gps_info[gps_tag] = exif.get_ifd(tag_id)[gps_tag_id]
            info["GPS"] = gps_info

    return info

# 使用
info = get_exif_info("photo.jpg")
for k, v in info.items():
    print(f"{k}: {v}")

6. 解析 GPS 经纬度

def gps_to_decimal(gps_coords, gps_ref):
    """将 EXIF GPS 转换为十进制"""
    d, m, s = gps_coords
    decimal = d + m/60 + s/3600
    if gps_ref in ['S', 'W']:
        decimal = -decimal
    return decimal

def get_gps_location(img_path):
    img = Image.open(img_path)
    exif = img.getexif()
    if not exif or 34853 not in exif.get_ifd(0x00A5):
        return None

    gps = exif.get_ifd(0x8825)  # GPS IFD
    lat = gps.get(2)   # GPSLatitude
    lat_ref = gps.get(1)  # GPSLatitudeRef
    lon = gps.get(4)   # GPSLongitude
    lon_ref = gps.get(3)  # GPSLongitudeRef

    if lat and lat_ref and lon and lon_ref:
        latitude = gps_to_decimal(lat, lat_ref)
        longitude = gps_to_decimal(lon, lon_ref)
        return latitude, longitude
    return None

# 使用
loc = get_gps_location("photo.jpg")
if loc:
    print(f"拍摄地点: 纬度 {loc[0]:.6f}, 经度 {loc[1]:.6f}")
    print(f"Google 地图: https://maps.google.com/?q={loc[0]},{loc[1]}")

7. 写入 / 修改 EXIF

from datetime import datetime

def add_exif_tags(img_path, output_path):
    img = Image.open(img_path)
    exif = img.getexif()

    # 修改拍摄时间
    now = datetime.now().strftime("%Y:%m:%d %H:%M:%S")
    exif[0x9003] = now  # DateTimeOriginal
    exif[0x0132] = now  # DateTime

    # 添加自定义标签
    exif[0x9286] = "Pillow 写入测试"  # UserComment

    # 保存
    img.save(output_path, exif=exif)
    print(f"已保存: {output_path}")

add_exif_tags("input.jpg", "output_with_exif.jpg")

8. 清理 EXIF(保护隐私)

def remove_exif(img_path, output_path):
    img = Image.open(img_path)
    data = list(img.getdata())
    img_no_exif = Image.new(img.mode, img.size)
    img_no_exif.putdata(data)
    img_no_exif.save(output_path)
    print(f"EXIF 已清除: {output_path}")

remove_exif("private.jpg", "clean.jpg")

9. 批量处理文件夹

import os

def batch_exif_info(folder):
    results = []
    for fname in os.listdir(folder):
        if fname.lower().endswith(('.jpg', '.jpeg', '.tiff')):
            path = os.path.join(folder, fname)
            loc = get_gps_location(path)
            info = get_exif_info(path)
            results.append({
                "file": fname,
                "location": loc,
                "camera": f"{info.get('相机品牌','')} {info.get('相机型号','')}".strip(),
                "time": info.get("拍摄时间", "")
            })
    return results

# 使用
photos = batch_exif_info("photos")
for p in photos:
    print(f"{p['file']} | {p['camera']} | {p['time']} | GPS: {p['location']}")

10. 可视化:生成带拍摄地点的地图

pip install folium
import folium

def create_map(photos_with_gps, output_html="photo_map.html"):
    m = folium.Map(location=[0, 0], zoom_start=2)
    for photo in photos_with_gps:
        if photo["location"]:
            folium.Marker(
                location=photo["location"],
                popup=f"<b>{photo['file']}</b><br>{photo['camera']}<br>{photo['time']}"
            ).add_to(m)
    m.save(output_html)
    print(f"地图已生成: {output_html}")

# 使用
photos = batch_exif_info("photos")
create_map(photos)

11. 高级技巧

11.1 提取缩略图

def extract_thumbnail(img_path, output_path):
    img = Image.open(img_path)
    if img._getexif() and 0x0103 in img._getexif():
        thumb = img._getexif()[0x0103]
        if thumb:
            thumb_img = Image.open(thumb)
            thumb_img.save(output_path)
            print(f"缩略图已保存: {output_path}")

extract_thumbnail("photo.jpg", "thumb.jpg")

11.2 自动按时间排序重命名

def rename_by_exif_time(folder):
    for fname in os.listdir(folder):
        path = os.path.join(folder, fname)
        img = Image.open(path)
        exif = img.getexif()
        if exif and 0x9003 in exif:
            timestamp = exif[0x9003].replace(":", "").replace(" ", "_")
            new_name = f"{timestamp}_{fname}"
            os.rename(path, os.path.join(folder, new_name))
            print(f"重命名: {new_name}")

rename_by_exif_time("photos")

12. 常见问题 & 解决方案

问题原因解决
getexif() 返回 None图片无 EXIF 或被压缩使用原图,避免微信/QQ 压缩
GPS 字段不存在手机未开启定位检查拍摄时 GPS 开关
时间格式错误字段是 DateTimeOriginal 而非 DateTime检查 0x9003
保存后 EXIF 丢失save() 没传 exif= 参数必须 img.save(..., exif=exif)

13. 官方文档

  • https://pillow.readthedocs.io/en/stable/reference/ExifTags.html
  • EXIF 标签表:https://exiftool.org/TagNames/EXIF.html

一键工具脚本:exif_tool.py

#!/usr/bin/env python3
import argparse
from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS

def main():
    parser = argparse.ArgumentParser(description="EXIF 工具")
    parser.add_argument("image", help="图片路径")
    parser.add_argument("-g", "--gps", action="store_true", help="显示 GPS")
    parser.add_argument("-c", "--clean", help="清除 EXIF 保存到文件")

    args = parser.parse_args()
    img = Image.open(args.image)
    exif = img.getexif()

    if args.clean:
        data = list(img.getdata())
        clean = Image.new(img.mode, img.size)
        clean.putdata(data)
        clean.save(args.clean)
        print(f"EXIF 已清除: {args.clean}")
        return

    if not exif:
        print("无 EXIF 数据")
        return

    print(f"文件: {args.image}\n")
    for tag_id in exif:
        tag = TAGS.get(tag_id, tag_id)
        value = exif.get(tag_id)
        if tag == "GPSInfo" and args.gps:
            print(f"{tag}:")
            gps = exif.get_ifd(tag_id)
            for gtag_id in gps:
                gtag = GPSTAGS.get(gtag_id, gtag_id)
                print(f"  {gtag}: {gps[gtag_id]}")
        else:
            print(f"{tag}: {value}")

if __name__ == "__main__":
    main()

使用

python exif_tool.py photo.jpg -g
python exif_tool.py photo.jpg -c clean.jpg

总结:ExifTags 核心用法

功能代码
读取img.getexif()
解析标签TAGS.get(tag_id)
GPSexif.get_ifd(0x8825)
写入img.save(..., exif=exif)
清除Image.new().putdata()

需要我帮你实现 照片时间线生成、自动按地点分类、隐私扫描工具、EXIF 编辑 GUI 等完整项目吗?直接说需求,我给你完整可运行代码!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注