Python 读取图片 EXIF 信息解析拍摄地理位置与时间
背景介绍
在数字摄影中,大多数相机和手机在保存照片时会自动嵌入 EXIF(Exchangeable Image File Format)数据。这些数据通常包含拍摄时间、相机型号、光圈快门参数,以及最重要的——GPS 经纬度信息。
了解如何提取这些信息对于隐私保护、取证分析或地理标记管理具有重要意义。本文将演示如何使用 Python 脚本读取图片中的 GPS 元数据,并将其转换为具体的物理地址。
环境准备
首先,需要安装必要的 Python 库:
exifread:用于解析图片的 EXIF 标签。requests:用于调用地图 API。json:处理返回的 JSON 数据。re:正则表达式匹配。
安装命令如下:
pip install exifread requests
核心原理
1. 读取 EXIF 信息
EXIF 数据存储在图片的二进制文件中。使用 exifread 库可以方便地遍历这些标签。其中,GPS 相关的标签通常以 GPS: 开头,例如 GPS:GPSLatitudeRef(纬度方向)、GPS:GPSLatitude(纬度值)等。
注意:GPS 坐标通常以度分秒(DMS)格式存储,如 [24, 24, 35] 表示 24 度 24 分 35 秒。为了进行地理编码,通常需要将其转换为十进制度数(Decimal Degrees)。
2. 经纬度转换
将 DMS 转换为十进制的公式为:
$$ \text{Decimal} = \text{Degrees} + \frac{\text{Minutes}}{60} + \frac{\text{Seconds}}{3600} $$
3. 地理编码(Geocoding)
获取经纬度后,可以通过第三方地图服务(如百度地图、高德地图)的 API 接口,将坐标反转为具体的街道地址。
代码实现
步骤一:解析图片 GPS 信息
以下函数负责读取图片文件,提取 GPS 坐标及拍摄日期。
import exifread
import re
import json
# 辅助函数:将度分秒转换为十进制
def dms_to_decimal(degrees, minutes, seconds):
return float(degrees) + (float(minutes) / 60) + (float(seconds.split('/')[0]) / float(seconds.split('/')[-1]) / 60)
# 读取照片的 GPS 经纬度信息
def find_GPS_image(pic_path):
GPS = {}
date = ''
try:
with open(pic_path, 'rb') as f:
tags = exifread.process_file(f)
for tag, value in tags.items():
# 纬度参考
if re.match('GPS GPSLatitudeRef', tag):
GPS['GPSLatitudeRef'] = str(value)
# 经度参考
elif re.match('GPS GPSLongitudeRef', tag):
GPS['GPSLongitudeRef'] = str(value)
# 海拔参考
elif re.match('GPS GPSAltitudeRef', tag):
GPS['GPSAltitudeRef'] = str(value)
# 纬度值
elif re.match('GPS GPSLatitude', tag):
try:
# 尝试解析 [deg, min, sec/1] 格式
match_result = re.match(r'\[(\w*), (\w*), (\w.*)/(\w.*)\]', str(value))
if match_result:
groups = match_result.groups()
GPS['GPSLatitude'] = dms_to_decimal(groups[0], groups[1], groups[2] + '/' + groups[3])
else:
# 备用解析逻辑
deg, min, sec = [x.replace(' ', '') for x in str(value)[1:-1].split(',')]
GPS['GPSLatitude'] = dms_to_decimal(deg, min, sec)
except Exception:
pass
# 经度值
elif re.match('GPS GPSLongitude', tag):
try:
match_result = re.match(r'\[(\w*), (\w*), (\w.*)/(\w.*)\]', str(value))
if match_result:
groups = match_result.groups()
GPS['GPSLongitude'] = dms_to_decimal(groups[0], groups[1], groups[2] + '/' + groups[3])
else:
deg, min, sec = [x.replace(' ', '') for x in str(value)[1:-1].split(',')]
GPS['GPSLongitude'] = dms_to_decimal(deg, min, sec)
except Exception:
pass
# 拍摄日期
elif re.match('.*Date.*', tag):
date = str(value)
except FileNotFoundError:
print("未找到指定图片文件")
return None
return {'GPS_information': GPS, 'date_information': date}
步骤二:调用地图 API 转换地址
使用百度地图 Geocoding API 将经纬度转换为结构化地址。注意:实际使用时请替换为您自己的 AK 密钥,不要硬编码在代码中。
import requests
def find_address_from_GPS(GPS_info):
"""
使用百度地图 API 将经纬度坐标转换为结构化地址。
:param GPS_info: 包含 GPS 信息的字典
:return: 地址详情元组
"""
if not GPS_info or not GPS_info.get('GPS_information'):
return '该照片无 GPS 信息'
gps_data = GPS_info['GPS_information']
# 确保有有效的经纬度
if 'GPSLatitude' not in gps_data or 'GPSLongitude' not in gps_data:
return '经纬度数据缺失'
lat = gps_data['GPSLatitude']
lng = gps_data['GPSLongitude']
# 构建请求 URL
# 警告:请勿将您的 Secret Key 直接提交到公共代码仓库
secret_key = 'YOUR_BAIDU_API_SECRET_KEY'
ak = 'YOUR_BAIDU_MAP_AK'
baidu_map_api = f"http://api.map.baidu.com/geocoder/v2/?ak={ak}&callback=renderReverse&location={lat},{lng}&output=json&pois=0"
try:
response = requests.get(baidu_map_api, timeout=10)
content = response.text
# 处理回调格式
if 'renderReverse' in content:
content = content.replace("renderReverse(", "").rstrip(";")
baidu_map_address = json.loads(content)
if baidu_map_address.get('status') != 0:
return 'API 查询失败'
result = baidu_map_address.get('result', {})
formatted_address = result.get('formatted_address', '')
province = result.get('addressComponent', {}).get('province', '')
city = result.get('addressComponent', {}).get('city', '')
district = result.get('addressComponent', {}).get('district', '')
location_desc = result.get('sematic_description', '')
return formatted_address, province, city, district, location_desc
except Exception as e:
return f'网络请求错误:{str(e)}'
步骤三:完整流程整合
if __name__ == '__main__':
# 替换为你的图片路径
pic_path = 'C:/Users/pacer/desktop/img/photo.jpg'
# 1. 读取 EXIF
GPS_info = find_GPS_image(pic_path=pic_path)
if GPS_info:
print(f"拍摄时间:{GPS_info.get('date_information')}")
# 2. 转换地址
address = find_address_from_GPS(GPS_info)
print(f'照片拍摄地址:{address}')
else:
print('无法读取图片信息')
注意事项
- 隐私安全:上传至社交媒体的图片若未关闭定位权限,可能泄露精确位置。建议在发布前使用工具抹除 EXIF 信息。
- API 配额:免费地图 API 通常有每日调用次数限制,生产环境建议申请企业级账号。
- 代码安全:切勿将真实的 API Key 硬编码在代码中,应通过环境变量或配置文件管理。
- 异常处理:实际应用中需增加更完善的异常捕获机制,防止因图片损坏或网络波动导致程序崩溃。
总结
通过本文的方法,我们可以快速验证图片的来源地信息。这对于理解数字取证的基础流程以及提升个人隐私保护意识具有实用价值。在实际开发中,建议结合更多元的数据源以提高准确性。


