最近购入了一台二手的索尼A7R2相机,拍照还是挺不错的,因为我没有啥视频需求。但是这是2015年发布的相机,不能连接creater's app。拍出来的照片没有GPS信息。
于是我想到了使用手表记录运动轨迹然后导入EXIF信息,我使用的小米手表S4,选择的运动模式是健走,导出类型选导出数据文件,选GPX文件,可保存到本地或者通过微信发送至电脑。
GPX文件相当于一个xml文件,部分格式如下,主要就是记录了时间(每秒)与经纬度信息。
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<gpx version="1.0" xmlns="http://www.topografix.com/GPX/1/1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
<name>20251108健走</name>
<desc>Export from Mi Fitness</desc>
<trk>
<extensions>
<totalDistance>4013</totalDistance>
<cumulativeClimb>18.042004</cumulativeClimb>
<cumulativeDecrease>16.06397</cumulativeDecrease>
</extensions>
<trkseg>
<trkpt lat="24.936992645263672" lon="118.58102416992188">
<ele>30.0</ele>
<time>2025-11-08T14:19:06.000Z</time>
</trkpt>
<trkpt lat="24.93698501586914" lon="118.5810317993164">
<ele>30.0</ele>
<time>2025-11-08T14:19:07.000Z</time>
</trkpt>
<trkpt lat="24.93696403503418" lon="118.5810317993164">
<ele>30.0</ele>
<time>2025-11-08T14:19:08.000Z</time>
</trkpt>
<trkpt lat="24.936946868896484" lon="118.5810317993164">
<ele>30.0</ele>
<time>2025-11-08T14:19:09.000Z</time>
</trkpt>
<trkpt lat="24.93693733215332" lon="118.58106231689453">
<ele>30.0</ele>
<time>2025-11-08T14:19:10.000Z</time>
</trkpt>
<trkpt lat="24.936922073364258" lon="118.58106231689453">
<ele>30.0</ele>
<time>2025-11-08T14:19:11.000Z</time>
</trkpt>
<trkpt lat="24.936908721923828" lon="118.5810546875">
<ele>30.0</ele>
<time>2025-11-08T14:19:12.000Z</time>
</trkpt>
<trkpt lat="24.936899185180664" lon="118.5810546875">
<ele>30.0</ele>
<time>2025-11-08T14:19:13.000Z</time>
</trkpt>
<trkpt lat="24.9368839263916" lon="118.5810546875">
<ele>30.0</ele>
<time>2025-11-08T14:19:14.000Z</time>
</trkpt>
<trkpt lat="24.936864852905273" lon="118.58104705810547">
<ele>30.0</ele>
<time>2025-11-08T14:19:15.000Z</time>
</trkpt>
<trkpt lat="24.936853408813477" lon="118.58103942871094">
<ele>30.0</ele>
<time>2025-11-08T14:19:16.000Z</time>
</trkpt>
<trkpt lat="24.936843872070312" lon="118.58103942871094">
<ele>30.0</ele>
<time>2025-11-08T14:19:17.000Z</time>
</trkpt>
<trkpt lat="24.936832427978516" lon="118.58103942871094">
<ele>30.0</ele>
<time>2025-11-08T14:19:18.000Z</time>
</trkpt>
<trkpt lat="24.936826705932617" lon="118.58103942871094">
<ele>30.0</ele>
<time>2025-11-08T14:19:19.000Z</time>
</trkpt>
<trkpt lat="24.93682098388672" lon="118.58104705810547">
<ele>30.0</ele>
<time>2025-11-08T14:19:20.000Z</time>
</trkpt>
<trkpt lat="24.936813354492188" lon="118.5810546875">
<ele>30.0</ele>
<time>2025-11-08T14:19:21.000Z</time>
</trkpt>
<trkpt lat="24.936800003051758" lon="118.58106231689453">
<ele>30.0</ele>
<time>2025-11-08T14:19:22.000Z</time>
</trkpt>
<trkpt lat="24.936784744262695" lon="118.5810546875">
<ele>30.0</ele>
<time>2025-11-08T14:19:23.000Z</time>
</trkpt>
<trkpt lat="24.936771392822266" lon="118.5810546875">
<ele>30.0</ele>
<time>2025-11-08T14:19:24.000Z</time>
</trkpt>
<trkpt lat="24.936758041381836" lon="118.5810546875">
<ele>30.0</ele>
<time>2025-11-08T14:19:25.000Z</time>
</trkpt>
<trkpt lat="24.936742782592773" lon="118.5810546875">
<ele>30.0</ele>
<time>2025-11-08T14:19:26.000Z</time>
</trkpt>
<trkpt lat="24.936731338500977" lon="118.5810546875">
<ele>30.0</ele>
<time>2025-11-08T14:19:27.000Z</time>
</trkpt>
......
</trkseg>
</trk>
</gpx>照片的EXIF信息中有拍摄时间信息,于是可以匹配到对应时间的GPS信息。
主要流程就是:读取并解析GPX信息→读取照片EXIF时间信息→匹配最接近的时间信息对应的GPS信息→写入GPS信息到照片。
python脚本如下,首先需要安装exiftool工具
import xml.etree.ElementTree as ET
from datetime import datetime, timezone, timedelta
import sys
import os
import subprocess
from pathlib import Path
def parse_gpx_file(gpx_file_path):
"""
解析GPX文件,提取时间(转为北京时间)和经纬度信息
"""
try:
# 解析GPX文件
tree = ET.parse(gpx_file_path)
root = tree.getroot()
# GPX命名空间
namespaces = {
'gpx': 'http://www.topografix.com/GPX/1/1'
}
# 查找所有的trkpt(轨迹点)
track_points = root.findall('.//gpx:trkpt', namespaces)
if not track_points:
# 如果没有找到trkpt,尝试查找rtept(路线点)或wpt(航点)
track_points = root.findall('.//gpx:rtept', namespaces)
if not track_points:
track_points = root.findall('.//gpx:wpt', namespaces)
# 存储轨迹点数据
gpx_data = []
for point in track_points:
# 获取经纬度
lat = point.get('lat')
lon = point.get('lon')
# 获取时间
time_element = point.find('gpx:time', namespaces)
time_str = time_element.text if time_element is not None else None
# 处理时间
local_time = None
if time_str:
try:
# 根据您的说明,GPX文件中的时间已经是中国当地时间
# 所以我们只需要解析时间字符串,不需要进行时区转换
if time_str.endswith('Z'):
# 移除Z并解析时间
temp_time = datetime.fromisoformat(time_str[:-1])
local_time = temp_time
else:
# 直接解析时间
local_time = datetime.fromisoformat(time_str)
except ValueError as e:
print(f"无法解析时间格式: {time_str}, 错误: {e}")
continue
if local_time and lat and lon:
# 保持原始精度,不进行浮点数转换
gpx_data.append({
'time': local_time,
'lat': lat, # 保持字符串格式以维持精度
'lon': lon # 保持字符串格式以维持精度
})
print(f"GPX文件解析完成,共找到 {len(gpx_data)} 个轨迹点")
return gpx_data
except ET.ParseError as e:
print(f"解析GPX文件出错: {e}")
except FileNotFoundError:
print(f"文件未找到: {gpx_file_path}")
except Exception as e:
print(f"处理文件时出错: {e}")
return []
def get_photo_capture_time(photo_path):
"""
使用exiftool获取照片的拍摄时间
"""
try:
# 使用exiftool获取拍摄时间
result = subprocess.run([
'exiftool',
'-DateTimeOriginal',
'-s',
'-s',
'-s',
photo_path
], capture_output=True, text=True, check=True)
time_str = result.stdout.strip()
if time_str and time_str != '-':
# 解析时间字符串 (格式: 2025:11:08 14:19:06)
capture_time = datetime.strptime(time_str, '%Y:%m:%d %H:%M:%S')
return capture_time
except subprocess.CalledProcessError:
pass
except ValueError:
pass
# 如果DateTimeOriginal不可用,尝试CreateDate
try:
result = subprocess.run([
'exiftool',
'-CreateDate',
'-s',
'-s',
'-s',
photo_path
], capture_output=True, text=True, check=True)
time_str = result.stdout.strip()
if time_str and time_str != '-':
capture_time = datetime.strptime(time_str, '%Y:%m:%d %H:%M:%S')
return capture_time
except (subprocess.CalledProcessError, ValueError):
pass
return None
def find_closest_gpx_point(photo_time, gpx_data, max_time_diff=10):
"""
在GPX数据中找到与照片拍摄时间最接近的点
"""
if not gpx_data or not photo_time:
return None
closest_point = None
min_time_diff = None
for point in gpx_data:
time_diff = abs(point['time'] - photo_time)
if max_time_diff is not None and time_diff > timedelta(seconds=max_time_diff):
continue
if min_time_diff is None or time_diff < min_time_diff:
min_time_diff = time_diff
closest_point = point
return closest_point
def find_best_matching_gpx_point(photo_time, gpx_data):
"""
查找最佳匹配的GPX点,使用更智能的匹配策略
"""
if not gpx_data or not photo_time:
return None
# 首先尝试严格的10秒匹配
closest_point = find_closest_gpx_point(photo_time, gpx_data, 10)
if closest_point:
return closest_point
# 如果没有找到,尝试更宽松的匹配(30秒内)
closest_point = find_closest_gpx_point(photo_time, gpx_data, 30)
if closest_point:
time_diff = abs(closest_point['time'] - photo_time)
print(f" 注意: 时间差较大 ({time_diff.total_seconds():.1f}秒),但仍使用最接近的点")
return closest_point
# 如果时间差超过30秒,不进行匹配
# 查找全局最接近的点,检查时间差是否超过30秒
closest_point = find_closest_gpx_point(photo_time, gpx_data, None)
if closest_point:
time_diff = abs(closest_point['time'] - photo_time)
if time_diff > timedelta(seconds=30):
print(f" 时间差过大 ({time_diff.total_seconds():.1f}秒 > 30秒),跳过GPS信息写入")
return None
else:
print(f" 警告: 时间差较大 ({time_diff.total_seconds():.1f}秒),使用最接近的点")
return closest_point
return None
def write_gps_to_photo(photo_path, lat, lon):
"""
使用exiftool将GPS信息写入照片,保持原始精度
"""
try:
# 转换经纬度为EXIF格式
lat_ref = 'N' if float(lat) >= 0 else 'S'
lon_ref = 'E' if float(lon) >= 0 else 'W'
# 使用原始精度的字符串格式
result = subprocess.run([
'exiftool',
f'-GPSLatitude={lat}', # 保持原始精度
f'-GPSLongitude={lon}', # 保持原始精度
f'-GPSLatitudeRef={lat_ref}',
f'-GPSLongitudeRef={lon_ref}',
'-overwrite_original',
photo_path
], check=True, capture_output=True, text=True)
print(f" 已写入GPS信息: lat={lat}, lon={lon}")
return True
except subprocess.CalledProcessError as e:
print(f"写入GPS信息失败: {e}")
return False
def process_photos(path, gpx_data):
"""
处理照片文件或目录中的所有照片
"""
path_obj = Path(path)
# 支持的照片格式
photo_extensions = {'.jpg', '.jpeg', '.png', '.tiff', '.tif'}
photo_files = []
# 如果是文件
if path_obj.is_file():
if path_obj.suffix.lower() in photo_extensions:
photo_files = [path_obj]
# 如果是目录
elif path_obj.is_dir():
for ext in photo_extensions:
photo_files.extend(path_obj.glob(f'*{ext}'))
photo_files.extend(path_obj.glob(f'*{ext.upper()}'))
if not photo_files:
print(f"未找到照片文件: {path}")
return
print(f"找到 {len(photo_files)} 个照片文件")
success_count = 0
for photo_path in photo_files:
print(f"\n处理照片: {photo_path.name}")
# 获取照片拍摄时间
photo_time = get_photo_capture_time(str(photo_path))
if not photo_time:
print(f" 无法获取拍摄时间")
continue
print(f" 拍摄时间: {photo_time.strftime('%Y-%m-%d %H:%M:%S')}")
# 查找最接近的GPX点,使用改进的匹配策略
closest_point = find_best_matching_gpx_point(photo_time, gpx_data)
if not closest_point:
print(f" 未找到时间匹配的GPS数据 (时间差超过30秒或无匹配数据)")
continue
time_diff = abs(closest_point['time'] - photo_time)
print(f" 匹配GPX点: {closest_point['time'].strftime('%Y-%m-%d %H:%M:%S')} (误差: {time_diff.total_seconds():.1f}秒)")
print(f" GPS坐标: {closest_point['lat']}, {closest_point['lon']}")
# 写入GPS信息
if write_gps_to_photo(str(photo_path), closest_point['lat'], closest_point['lon']):
print(f" GPS信息写入成功")
success_count += 1
else:
print(f" GPS信息写入失败")
print(f"\n处理完成: {success_count}/{len(photo_files)} 张照片成功写入GPS信息")
def main():
"""
主函数
"""
if len(sys.argv) < 3:
print("使用方法: python gpx2exif.py <gpx文件路径> <照片文件或目录路径>")
print("示例: python gpx2exif.py track.gpx photos/")
print("示例: python gpx2exif.py track.gpx photo.jpg")
return
gpx_file_path = sys.argv[1]
photo_path = sys.argv[2]
# 解析GPX文件
gpx_data = parse_gpx_file(gpx_file_path)
if not gpx_data:
print("GPX数据解析失败")
return
# 处理照片
process_photos(photo_path, gpx_data)
if __name__ == "__main__":
main()使用方法:
# 处理目录中的所有照片
python gpx2exif.py track.gpx photos/
# 处理单张照片
python gpx2exif.py track.gpx photo.jpg注意事项:
- 需要系统中已安装exiftool工具
- 照片的拍摄时间需要与GPX记录时间在同一时区
- 时间匹配精度为±10秒,可以根据需要调整
- 写入操作会直接修改原文件(使用-overwrite\_original参数)

