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) |
| GPS | exif.get_ifd(0x8825) |
| 写入 | img.save(..., exif=exif) |
| 清除 | Image.new().putdata() |
需要我帮你实现 照片时间线生成、自动按地点分类、隐私扫描工具、EXIF 编辑 GUI 等完整项目吗?直接说需求,我给你完整可运行代码!