用 Pillow 在上传前清除图片 EXIF 的 GPS 与设备信息
用 Pillow 在上传前清除图片 EXIF 的 GPS 与设备信息
问题场景
很多手机照片在 JPEG 的 EXIF 中记录 GPS 坐标、机身序列号、镜头型号、拍摄时间,以及厂商私有的 MakerNote。业务把原图直接上传到对象存储后,这些字段会跟着进入 CDN、日志或第三方处理链路。即使页面不展示,下载原图仍可能读到。常见需求是:用户选择图片后,在上传前生成一份干净副本,只保留像素和必要色彩信息。
EXIF 可以理解为若干 IFD(Image File Directory)。GPS 信息由 IFD0 的 GPSInfo 指针(tag 0x8825)指向 GPS IFD;厂商私有的 MakerNote(tag 0x927C)通常位于 Exif SubIFD(指针 0x8769)中。删除指针即可让对应 IFD 不再被写出,但更稳妥的隐私做法是重建像素副本,让新文件根本不携带旧 EXIF。
最小实现:像素重建,不继承元数据
依赖 Pillow:
python -m pip install Pillow
strip_exif.py:
from PIL import Image, ImageOps
def strip_metadata(src: str, dst: str, quality: int = 90) -> None:
with Image.open(src) as im:
# 先按 EXIF Orientation 旋转像素,再把 EXIF 丢掉,避免方向错误
im = ImageOps.exif_transpose(im)
im.load()
# 如业务需要色彩一致性,可保留 ICC;它不是 GPS/设备信息
icc = im.info.get('icc_profile')
# 调色板/CMYK 先转为常规模式,避免重建后保存失败
if im.mode == 'P':
im = im.convert('RGBA')
elif im.mode == 'CMYK':
im = im.convert('RGB')
# 新建图像并只复制像素,不复制 info/exif/xmp
clean = Image.new(im.mode, im.size)
clean.paste(im)
lower = dst.lower()
if lower.endswith(('.jpg', '.jpeg')):
if clean.mode not in ('RGB', 'L'):
clean = clean.convert('RGB')
clean.save(
dst,
format='JPEG',
quality=quality,
optimize=True,
**({'icc_profile': icc} if icc else {}),
)
elif lower.endswith('.png'):
clean.save(dst, format='PNG', optimize=True)
else:
clean.save(dst)
if __name__ == '__main__':
import sys
strip_metadata(sys.argv[1], sys.argv[2])
运行:
python strip_exif.py input.jpg output.jpg
这段代码的关键点:
ImageOps.exif_transpose先把方向应用到像素,否则删除 Orientation 后图片可能横竖颠倒。Image.new加paste只复制像素,不继承原图的info、EXIF、XMP;保存时也不传exif=。- 保留 ICC 是可选项,目的是减少广色域图片的颜色偏差;ICC 不含 GPS。若要求零附加元数据,可以去掉 ICC。
- JPEG 保存会重新编码,属于有损流程;
quality只是占位参数,按业务画质要求调整,不能声称无损。
如果必须保留部分 EXIF:定向删除
有些业务需要保留曝光参数,只想删除 GPS 与 MakerNote。可以在读取 EXIF 后删指针:
from PIL import Image, ImageOps
with Image.open('input.jpg') as im:
im = ImageOps.exif_transpose(im)
exif = im.getexif()
exif.pop(0x8825, None) # GPSInfo IFD
exif.pop(0x8769, None) # Exif SubIFD,MakerNote 常在其中
exif.pop(0x927C, None) # 顶层 MakerNote(如有)
exif.pop(0x010F, None) # Make
exif.pop(0x0110, None) # Model
exif.pop(0x0131, None) # Software
im.save('output.jpg', exif=exif, quality=90)
注意:0x8769 一旦删除,Exif SubIFD 里的 MakerNote、镜头、曝光等会一起消失;这不是只删一个字段。不同厂商还可能把设备信息放在 IFD0 的其他标签里,逐项删除容易漏。隐私优先时,仍建议使用前面的像素重建方案。
用 exiftool 验证
先检查原图,确认确实存在 GPS 与设备标签:
exiftool -json -GPS:all -MakerNote:all -Make -Model -SerialNumber -Software input.jpg
再检查输出副本:
exiftool -json -GPS:all -MakerNote:all -Make -Model -SerialNumber -Software output.jpg
如果输出对象里只剩 SourceFile,没有 GPSLatitude、GPSLongitude、Make、Model、MakerNote* 等键,说明目标标签没有被写出。还可以看剩余 EXIF:
exiftool -EXIF:all output.jpg
保留 ICC 时,exiftool output.jpg 可能仍显示 ICC_Profile,这属于色彩配置,不是 GPS 或设备信息。若要验证完全无 EXIF,以 -EXIF:all 无输出为准。
边界与限制
- 本文示例以 JPEG 为主。PNG、WebP 也可能带 EXIF/XMP;思路相同,但保存参数和验证标签要按格式调整。HEIC 需要额外插件,不在示例范围内。
- 重新保存 JPEG 会重新编码,可能损失画质;对原图归档场景,可考虑用 exiftool 做无损元数据删除,再上传副本。
- 动图(如 GIF/WebP 动画)用
Image.open默认只处理当前帧,不能直接套用上述流程。 - 超大图片会占用较多内存;生产环境应限制上传尺寸,并处理 Pillow 的解压炸弹警告。
- 删除元数据不等于内容安全:图片像素中仍可能有屏幕、证件、人脸等敏感信息,需要配合业务审核。
- 上传链路还应使用服务端校验,不能只依赖前端处理;客户端生成的副本仍可能被绕过。
- 点赞
- 收藏
- 关注作者
评论(0)