背景介绍
摄影爱好者拍摄的照片中,隐藏着丰富的元数据——EXIF信息。它记录了拍摄时间、相机型号、光圈/快门等参数,甚至包含GPS位置。这些信息能帮助我们还原拍摄场景、分析参数设置,但手动查看十分繁琐。本文将带大家用Python打造一个本地图片EXIF信息查看器,通过Tkinter GUI和exifread库,实现图片EXIF数据的可视化解析。
思路分析
要实现这个工具,我们需要解决四个核心问题:
- GUI交互:用Tkinter构建界面,支持“选择图片”按钮、文件选择对话框和信息展示区域。
- EXIF读取:借助
exifread库解析图片的元数据标签(如拍摄时间、GPS坐标)。 - 数据转换:将原始EXIF数据(如度分秒格式的GPS坐标)转换为人类可读的格式(如十进制经纬度、带单位的时间)。
- 错误处理:应对图片无EXIF数据、格式不兼容或解析失败的情况。
代码实现
下面是完整的代码实现,包含详细注释。首先确保安装依赖:
pip install exifread pillow
完整代码
import tkinter as tk
from tkinter import filedialog
import exifread
from PIL import Image
import os
from datetime import datetime
def parse_gps(tags):
"""解析GPS信息:纬度、经度、海拔(度分秒转十进制)"""
latitude = None
longitude = None
altitude = None
# 处理纬度(GPSLatitude + GPSLatitudeRef)
if "GPS GPSLatitude" in tags and "GPS GPSLatitudeRef" in tags:
lat = tags["GPS GPSLatitude"].values # 度、分、秒(Ratio对象)
lat_ref = str(tags["GPS GPSLatitudeRef"]).strip() # N/S
# 度分秒转十进制:度 + 分/60 + 秒/3600
deg = lat[0].num / lat[0].den
min = lat[1].num / lat[1].den
sec = lat[2].num / lat[2].den
lat_dec = deg + min/60 + sec/3600
latitude = f"{lat_dec:.4f}° {lat_ref}"
# 处理经度(GPSLongitude + GPSLongitudeRef)
if "GPS GPSLongitude" in tags and "GPS GPSLongitudeRef" in tags:
lon = tags["GPS GPSLongitude"].values # 度、分、秒(Ratio对象)
lon_ref = str(tags["GPS GPSLongitudeRef"]).strip() # E/W
deg = lon[0].num / lon[0].den
min = lon[1].num / lon[1].den
sec = lon[2].num / lon[2].den
lon_dec = deg + min/60 + sec/3600
longitude = f"{lon_dec:.4f}° {lon_ref}"
# 处理海拔(GPSAltitude)
if "GPS GPSAltitude" in tags:
alt = tags["GPS GPSAltitude"].values[0] # Ratio对象
alt_m = alt.num / alt.den # 转换为米
altitude = f"{alt_m:.1f}m"
return latitude, longitude, altitude
def parse_exif(file_path, text_area):
"""解析图片EXIF信息并显示"""
try:
# 清空之前的内容
text_area.delete(1.0, tk.END)
# 1. 文件基本信息
file_name = os.path.basename(file_path)
file_size = os.path.getsize(file_path) / (1024 * 1024) # 转换为MB
# 获取分辨率(借助PIL)
img = Image.open(file_path)
width, height = img.size
text_area.insert(tk.END, "【文件基本信息】\n")
text_area.insert(tk.END, f"文件名:{file_name}\n")
text_area.insert(tk.END, f"文件大小:{file_size:.2f}MB\n")
text_area.insert(tk.END, f"分辨率:{width}×{height}\n\n")
# 2. 读取EXIF数据(二进制模式打开)
with open(file_path, 'rb') as f:
tags = exifread.process_file(f, details=False) # details=False提高速度
# 3. 拍摄设备与参数
text_area.insert(tk.END, "【拍摄设备与参数】\n")
# 拍摄时间(EXIF DateTimeOriginal)
if "EXIF DateTimeOriginal" in tags:
time_str = str(tags["EXIF DateTimeOriginal"]).strip()
# 转换为标准时间格式:2023:08:20 09:45:32 → 2023-08-20 09:45:32
time_obj = datetime.strptime(time_str, "%Y:%m:%d %H:%M:%S")
text_area.insert(tk.END, f"拍摄时间:{time_obj.strftime('%Y-%m-%d %H:%M:%S')}\n")
else:
text_area.insert(tk.END, "拍摄时间:未找到\n")
# 相机型号(Image Model)
if "Image Model" in tags:
text_area.insert(tk.END, f"相机型号:{str(tags['Image Model']).strip()}\n")
else:
text_area.insert(tk.END, "相机型号:未找到\n")
# 镜头型号(EXIF LensModel)
if "EXIF LensModel" in tags:
text_area.insert(tk.END, f"镜头型号:{str(tags['EXIF LensModel']).strip()}\n")
else:
text_area.insert(tk.END, "镜头型号:未找到\n")
# 光圈值(EXIF FNumber)
if "EXIF FNumber" in tags:
f_num = tags["EXIF FNumber"].values[0] # 分数对象(如 56/10 → f/5.6)
f = f_num.num / f_num.den
text_area.insert(tk.END, f"光圈值:f/{f:.1f}\n")
else:
text_area.insert(tk.END, "光圈值:未找到\n")
# 快门速度(EXIF ExposureTime)
if "EXIF ExposureTime" in tags:
exp_time = tags["EXIF ExposureTime"].values[0]
if exp_time.num == 1: # 如 1/250 → 250s?不,1/250的num是1,den是250 → 1/250s
text_area.insert(tk.END, f"快门速度:1/{exp_time.den}s\n")
else: # 如 5/1000 → 1/200s(den/num=200)
s = exp_time.den / exp_time.num
text_area.insert(tk.END, f"快门速度:1/{s}s\n")
else:
text_area.insert(tk.END, "快门速度:未找到\n")
# ISO感光度(EXIF ISOSpeedRatings)
if "EXIF ISOSpeedRatings" in tags:
text_area.insert(tk.END, f"ISO感光度:{tags['EXIF ISOSpeedRatings'].values[0]}\n")
else:
text_area.insert(tk.END, "ISO感光度:未找到\n")
# 焦距(EXIF FocalLength)
if "EXIF FocalLength" in tags:
focal = tags["EXIF FocalLength"].values[0]
f = focal.num / focal.den
text_area.insert(tk.END, f"焦距:{f:.1f}mm\n")
else:
text_area.insert(tk.END, "焦距:未找到\n")
text_area.insert(tk.END, "\n")
# 4. GPS位置信息
text_area.insert(tk.END, "【GPS位置信息(若存在)】\n")
latitude, longitude, altitude = parse_gps(tags)
if latitude:
text_area.insert(tk.END, f"纬度:{latitude}\n")
else:
text_area.insert(tk.END, "纬度:未找到\n")
if longitude:
text_area.insert(tk.END, f"经度:{longitude}\n")
else:
text_area.insert(tk.END, "经度:未找到\n")
if altitude:
text_area.insert(tk.END, f"海拔高度:{altitude}\n")
else:
text_area.insert(tk.END, "海拔高度:未找到\n")
except Exception as e:
text_area.insert(tk.END, f"解析出错:{str(e)}\n")
def select_image():
"""选择图片文件并解析EXIF"""
file_path = filedialog.askopenfilename(
title="选择图片文件",
filetypes=[("图片文件", "*.jpg *.jpeg *.png *.bmp")]
)
if file_path:
parse_exif(file_path, text_area)
# 主窗口设置
root = tk.Tk()
root.title("图片EXIF信息查看器")
root.geometry("800x600")
# 文本显示区域
text_area = tk.Text(root, wrap=tk.WORD, font=("SimHei", 10))
text_area.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 选择图片按钮
btn_select = tk.Button(root, text="选择图片", command=select_image)
btn_select.pack(pady=10)
if __name__ == "__main__":
root.mainloop()
代码解析
- GUI搭建:使用Tkinter的
Text组件展示信息,Button触发文件选择,filedialog实现本地文件浏览。 - EXIF读取:通过
exifread.process_file()以二进制模式读取图片的EXIF标签,details=False跳过冗余信息,提高解析速度。 - 数据转换:
- 时间格式化:将EXIF的
DateTimeOriginal(如2023:08:20 09:45:32)转换为YYYY-MM-DD HH:MM:SS格式。 - GPS坐标转换:将度分秒格式(如
30°21'34.2" N)转换为十进制(如30.3595° N),公式为度 + 分/60 + 秒/3600。 - 参数格式化:光圈、快门、焦距等参数从分数形式(如
56/10)转换为人类可读的格式(如f/5.6、1/250s)。
- 时间格式化:将EXIF的
- 错误处理:用
try-except捕获解析过程中的异常(如文件损坏、无EXIF数据),保证程序健壮性。
总结
通过这个项目,我们实践了GUI设计、文件操作、第三方库调用和数据格式化的综合能力:
– 从Tkinter的界面搭建,到exifread的元数据解析,再到GPS坐标的转换,每一步都锻炼了Python开发的核心技能。
– 工具实用性强:摄影爱好者可快速分析参数,普通用户可还原拍摄地点,是学习GUI和元数据处理的绝佳案例。
如果你想扩展功能,可尝试:
– 支持批量解析图片的EXIF信息。
– 可视化GPS轨迹(结合地图API)。
– 导出EXIF信息为JSON/CSV格式。
快来运行代码,看看你的照片隐藏了哪些故事吧! 📷
(注:代码兼容JPG、PNG等格式的图片,但PNG的EXIF支持度低于JPG,建议用JPG图片测试。)