很多人第一次意识到照片能带坐标,是在整理旅行相册的时候:手机拍的图在地图上密密麻麻排成一条线,而相机拍的那几百张却像被扔进了黑洞,只能靠文件名和记忆去猜是在哪儿按的快门。还有一类人反过来——做外业记录、房屋勘察、门店巡店、徒步轨迹留档的,明明相机压根没有定位模块,却必须在交付的照片里带上经纬度,否则整个成果就没法归档核验。这篇就聊聊照片上的经纬度、地址这些信息到底是怎么写进去的,以及相机没有定位功能时,怎么用电脑或者手机把坐标老老实实塞进图片文件里。内容偏实操,工具以命令行和脚本为主,也照顾不想敲代码的读者,中间会穿插不少我这些年踩过的坑。
1. 照片的经纬度不是"贴在图片上",而是写进 EXIF 的一个独立数据块
1.1 GPS IFD 里到底有哪些字段
先说底层。一张 JPEG 里除了像素数据,还挂着一堆元数据段,其中 APP1 段里装的就是 EXIF。EXIF 内部是按 IFD(Image File Directory,可以理解成一张"字段索引表")组织的,主 IFD0 记录相机型号、镜头、拍摄参数,ExifIFD 记录曝光时间、光圈、拍摄时间,而 GPS 信息单独放在一个叫 GPS IFD 的独立表里。这个设计挺合理的——定位信息是可选的,单独一张表,没有就整张表不写,不会影响其他字段。
GPS IFD 里的字段和我们平时说的"一个坐标"差别很大。它不是存一个十进制小数的,而是拆成好几项:GPSLatitudeRef存北纬还是南纬(值是字符串 "N" 或 "S"),GPSLatitude存三个有理数,分别代表度、分、秒;经度同理,用GPSLongitudeRef加GPSLongitude。这就是为什么你手工算坐标的时候必须先做一次十进制到度分秒的换算,直接往字段里写39.908722是不行的。
除经纬度外,常用的还有这么几项:
| 字段 | 含义 | 单位/取值 |
|---|---|---|
| GPSVersionID | GPS IFD 版本号 | 通常是 2.3.0.0 |
| GPSAltitudeRef | 海拔基准 | 0 表示海平面以上,1 表示以下 |
| GPSAltitude | 海拔高度 | 米,有理数 |
| GPSTimeStamp | 定位时刻 | UTC 时间,时分秒三个有理数 |
| GPSDateStamp | 定位日期 | 格式为 YYYY:MM:DD |
| GPSImgDirection | 镜头朝向 | 0 到 359.99 度 |
| GPSMapDatum | 大地基准 | 一般是 WGS-84 |
| GPSHPositioningError | 水平精度 | 米 |
这些字段里,经纬度是必写的,其余都是可选项。但如果你做的成果要求比较高,比如工程记录、测绘辅助资料,把海拔、精度、朝向一起写上,事后追溯会方便很多——尤其是朝向,回溯一张照片拍的是哪一面墙、哪一棵树,有时候比坐标本身还管用。
1.2 相机没 GPS 不等于照片不能有坐标
这是最容易被误解的一点。很多人默认"我的相机没有定位模块,所以拍出来的照片天生不可能有坐标"。前半句对,后半句错。EXIF 只是一个数据容器,谁往里写都行,跟写入者是不是相机没有必然关系。相机不写,电脑上的工具可以补写;甚至你完全可以给一张十年前的老照片补上坐标,只要那处 EXIF 段还有空间容纳新增字段。
真正决定"能不能写"的是三件事:文件格式是否支持 EXIF、写入工具是否支持这个格式、以及这张照片原来有没有被别的软件写坏结构。JPEG 是最宽容的,随便改;HEIC 这几年在手机端普及得很快,但跨平台写入支持一直参差不齐;PNG 传统上压根没有 EXIF 标准位置,虽然新版本规范加了 eXIf 数据块,可大部分相册和地图服务根本不认;RAW 各家格式自成一派,写入要格外小心。所以实际操作里,如果你的目标只是"让照片在地图上显示出来",最稳妥的路径永远是先转成 JPEG 再写。
还有一个隐藏前提:写入工具在原地修改文件时,是靠重写整个文件结构完成的。这意味着任何中途中断、磁盘写满、权限不足,都可能把原图弄成半残。所以在动手之前做一份原始备份,不是谨慎,是必须。
1.3 坐标系这关:为什么你写进去的点和地图对不上
这是我见过最多的"明明写了坐标却飘了"的原因。EXIF 标准里,坐标默认按 WGS-84 大地基准记录,GPSMapDatum字段写的就是这个。国际上大部分地图服务、轨迹记录设备、GPS 芯片原始输出,用的都是 WGS-84,一切正常。
问题出在数据来源上。如果你是从某些国内在线地图的网页上"拾取"了一个点,或者从某个地图开放接口反查回来的坐标,那些数值往往已经经过了一次非线性偏移处理,跟 WGS-84 不是一个体系。你要是把这种坐标原封不动写进 EXIF,再拿到另一个地图上看,位置就会偏出去好几百米——在市区可能就是隔了两条街,在山区可能直接跑到另一座山头上。
实操上的判断方法是:先问清楚坐标是从哪来的。手持 GPS 记录仪、运动相机、无人机原始日志、GPX 轨迹文件导出的点,基本都是 WGS-84,可以直接写。至于从地图网页上手工点的位置,得先做一次坐标系转换再入库。转换算法有一批公开实现,思路是用一套基于偏移量的经验公式做迭代逼近,网上能找到不少成熟代码,直接调用就行,不必自己推导。
提示:写 EXIF 之前,务必确认手上坐标的原始基准。这一步搞错,后面所有验证都是白费功夫,因为坐标本身"写对了",只是写进去的东西从源头就偏了。
另外还有个容易忽略的细节:度分秒的秒值精度。EXIF 用有理数存储,分母可以很大,理论上能到很高的精度。但很多工具在转换时会粗暴地取整到整秒,一秒的纬度差大约是 31 米,精度损失相当可观。稳妥做法是保留到小数点后四到六位,对应米级甚至亚米级。
2. 动手前的三件准备:验证底子、确定数值、留好备份
2.1 一条命令摸清照片现有的 EXIF 结构
我最推荐的第一件工具是 ExifTool。它跨平台、命令行、支持格式极广,读写都行,而且不会像某些图形界面工具那样顺手把你的其他元数据改掉。装好之后,先用只读方式把照片的底子摸清楚:
exiftool -a -G1 -s photo.jpg-a表示允许重复标签,-G1按信息组分类显示(这样你能一眼看出哪些字段属于 EXIF、哪些属于 XMP、哪些属于 GPS),-s用短标签名。输出会很长,如果你只关心定位相关的,可以直接过滤:
exiftool -a -G1 -s -GPS:all -XMP:GPS* photo.jpg这一步的目的是确认三件事:这张照片现在有没有 GPS 段、EXIF 主结构是否完整、有没有被其他软件塞进去冲突的 XMP 定位信息。如果 XMP 里也有一份坐标,且和 EXIF 里的不一致,那不同软件读取时优先级不一样,显示出来的位置可能完全不同——这种双份坐标打架的情况,在经历过多次修图的照片上非常常见。
顺带说一句,ExifTool 还有一个特别好用的输出方式:
exiftool -n -GPSLatitude -GPSLongitude -GPSAltitude photo.jpg-n表示输出原始数值而不是格式化后的字符串,经纬度会直接给你十进制小数,方便你复制出来做计算或对比。
2.2 拿到可信坐标的几种渠道
写坐标的前提是你得先有一个准确的坐标。不同场景下我有不同的取法:
- 拍摄现场有网络:直接开手机地图,长按落点读数值。注意这一步拿到的可能是偏移坐标,要问清楚来源,或者干脆用能直接输出 WGS-84 的工具。
- 拍摄现场没网络:提前用离线地图或者专用记录设备打点,把点号记在纸上,回来再对应照片。这种土办法在外业里其实特别可靠,比事后凭记忆猜强一百倍。
- 整段行程:开一个轨迹记录,全程录 GPX。这是最省事的方式,一趟下来无论拍了多少张,回来按时间戳批量匹配就行,后面第 4 节会详细讲。
- 固定机位:比如门店监控位、工地固定观察点,坐标是常量,直接建一张"机位-坐标"对照表,所有该机位拍的照片共用一组数值。
这里有个经验:不要相信"我记得大概在那一带"。人对自己位置的记忆误差,在陌生城市里轻松超过一公里,在山区或者大型园区里更是离谱。凡是要写进照片的坐标,必须有可追溯的来源,要么是设备记录,要么是现场打点,要么是明确的对照表。
2.3 格式兼容性与备份策略
备份这件事我不想说得太重,但确实见过有人批量处理后把原图覆盖掉,结果坐标全写错又没法回滚的。最简单的做法是:原图单独放一个目录,只读不改,所有写入操作都作用在副本上。
格式方面,我整理了一张常用的对照表:
| 格式 | EXIF 写入支持 | 实际可用性 | 建议 |
|---|---|---|---|
| JPEG | 完整支持 | 各类软件和服务普遍识别 | 首选 |
| HEIC | 部分工具支持 | 跨平台读取不稳定 | 先转 JPEG 再写 |
| PNG | 新版规范支持 eXIf | 多数相册和地图不认 | 不建议作为交付格式 |
| TIFF | 完整支持 | 专业软件识别良好 | 可用于存档 |
| RAW(各家) | 工具支持但风险高 | 部分相机拒绝读取被改文件 | 优先写 XMP 附属文件 |
| WebP | 有限支持 | 识别率一般 | 不作为主力 |
RAW 那一条值得多说两句。ExifTool 确实能直接往 RAW 文件里写,但某些机型的 RAW 内部有校验,被外部工具改过之后,原厂软件可能拒绝打开。稳妥的做法是保持 RAW 原封不动,把元数据写到一个同名的 XMP 附属文件里,文件名保持一致,软件读取时会自动关联。ExifTool 可以这样指定输出:
exiftool -o %d%f.xmp -GPSLatitude=39.908722 -GPSLatitudeRef=N target.CR2这样原文件一个字节都不动,元数据单独存放,风险最低。
3. 电脑端主力方案:ExifTool 单张与批量写入
3.1 单张写入命令的逐段拆解
先看最基础的单张写入。假设你已经确认好坐标是北纬 39.908722、东经 116.397499,海拔 45 米,命令是这样:
exiftool -GPSLatitude=39.908722 -GPSLatitudeRef=N \ -GPSLongitude=116.397499 -GPSLongitudeRef=E \ -GPSAltitude=45 -GPSAltitudeRef=0 \ -GPSMapDatum="WGS-84" \ -overwrite_original photo.jpg拆开看每一项都在干什么。-GPSLatitude和-GPSLongitude直接给十进制就行,ExifTool 会自动帮你折算成度分秒的有理数,不需要手工转换,这是它比手写脚本省事的地方。-GPSLatitudeRef和-GPSLongitudeRef是符号位,北纬写 N、南纬写 S、东经写 E、西经写 W,如果你的坐标本身带负号,ExifTool 也能自动推断,但显式写出来更保险。
-GPSAltitudeRef=0表示海拔基准取海平面以上,这个字段容易被漏掉,漏了之后部分软件解析海拔会得到奇怪的结果。-GPSMapDatum写上 WGS-84 是个好习惯,虽然很多软件不看这一项,但一旦你的成果要交给别人做校验,这个字段就是"我这份数据是哪个体系"的声明。
-overwrite_original的作用是不生成photo.jpg_original备份文件。第一次用的时候建议先不加这个参数,看看生成出来的备份文件是否正确,确认无误之后再批量加上。反过来说,如果你是在副本目录操作,-overwrite_original可以放心用,能省掉一堆临时文件。
写完之后立刻验证:
exiftool -GPSPosition -GPSAltitude photo.jpg-GPSPosition是一个复合标签,会把经纬度拼成人类可读的格式输出,比如"39 deg 54' 31.40" N, 116 deg 23' 51.00" E"。看到这行输出,说明 GPS 段已经建起来了。
3.2 CSV 批量写入,一次处理几百张
一张一张写是不现实的,实际工作里更常见的场景是:手上有几百张照片,配一张表格,每张照片对应的坐标都记好了。这种时候用 CSV 一次性灌进去最省事。
准备一个 CSV 文件,第一列必须是文件名(列名固定为SourceFile),后面跟各字段:
SourceFile,GPSLatitude,GPSLatitudeRef,GPSLongitude,GPSLongitudeRef,GPSAltitude IMG_0001.JPG,39.908722,N,116.397499,E,45 IMG_0002.JPG,39.909105,N,116.398211,E,46 IMG_0003.JPG,39.908433,N,116.396877,E,44然后执行:
exiftool -csv=coords.csv -GPSLatitude -GPSLatitudeRef \ -GPSLongitude -GPSLongitudeRef -GPSAltitude ./photos注意-csv=后面跟的是导入文件路径,后面那一串是告诉 ExifTool 只有这几列允许写入,避免 CSV 里有其他列误改到别的元数据。这个白名单机制很重要,我曾经因为 CSV 里带了一列DateTimeOriginal,结果把一批照片的拍摄时间全改成了表格生成的时间,追溯起来相当麻烦。
CSV 的坐标列也可以直接写带方向的一体化数值,比如-GPSLatitude列直接写-39.908722(南纬用负数),ExifTool 会自动拆分符号位。不过我依然推荐显式写 Ref 列,一是可读性好,二是不同版本 ExifTool 对负号自动推断的行为有过变化,显式写不会踩到版本差异。
批量执行完之后,建议抽检而不是全检。抽检的方法是随机挑五到十张,用-GPSPosition读出来,和 CSV 里的原值对比。如果发现整批都偏了,那大概率是坐标系搞错了;如果是个别偏了,那可能是 CSV 里那几行写串了。
3.3 顺带写入地名、海拔与拍摄方向
坐标本身对机器友好,对人并不友好。一张照片打开一看是"39.908722, 116.397499",除了专业用户,没人知道这是哪儿。所以把地名一起写进去,实用价值很高。
ExifTool 支持写 IPTC 和 XMP 里的位置描述字段:
exiftool -IPTC:City="示例城区" -IPTC:SubLocation="某路段" \ -XMP:Location="示例地点说明" \ -XMP:GPSLatitude=39.908722 -XMP:GPSLongitude=116.397499 \ photo.jpg这里同时写了 EXIF 的 GPS 和 XMP 的 GPS,是刻意的。有些相册软件读 EXIF,有些读 XMP,两份都写上兼容性最好。地名文本建议控制在简短的层级结构里,比如"城市-片区-具体位置",太长了很多软件会截断,反而难看。
海拔和方向这两个字段,在特定场景下价值很高。海拔对于登山、野外考察类的记录很有意义;拍摄方向(GPSImgDirection)在建筑外立面记录、设备巡检里能帮你判断镜头朝向。写入方式:
exiftool -GPSImgDirection=137.5 -GPSImgDirectionRef=T \ -GPSHPositioningError=3.5 photo.jpgGPSImgDirectionRef里 T 表示真北方向,M 表示磁北方向,一般用 T。GPSHPositioningError记录水平定位误差,单位是米,如果你用的定位源精度一般,写上这个字段等于给后续使用者一个明确提示,比不写要负责任。
4. 代码路线:用 Python 按拍摄时间自动匹配轨迹
4.1 piexif 和 Pillow 的取舍
如果只是偶尔写几张,ExifTool 完全够用。但当你需要按业务规则自动补坐标——比如"所有照片按机位表匹配"、"按拍摄时间从轨迹里插值"——那就得上代码了。
Python 里常用的两个库是 Pillow 和 piexif。Pillow 的Image对象可以直接读写 EXIF,img.getexif()拿到的是一个字典式结构,改完再save回去。优点是它同时能干图片处理的事,转格式、缩放一步到位;缺点是它对 EXIF 的 GPS 子结构支持偏弱,很多版本里改完 GPS 再保存会丢字段。
piexif 专注做 EXIF 读写,GPS 部分处理得很干净:
import piexif exif_dict = piexif.load("photo.jpg") print(exif_dict.keys())输出里会看到0th、Exif、GPS、Interop、1st(缩略图)、thumbnail这几个键。如果原图没有 GPS 段,GPS就是一个空字典,直接往里塞就行。
写坐标的核心是把十进制转成三个有理数:
def to_dms_rational(value): value = abs(value) degree = int(value) minute_float = (value - degree) * 60 minute = int(minute_float) second = round((minute_float - minute) * 60, 6) return ((degree, 1), (minute, 1), (int(second * 10000), 10000))注意最后一项,秒值乘一万取整做分子,分母固定一万,这样精度到 0.0001 秒,对应地面大约 3 毫米,完全够用。如果偷懒写成(int(second), 1),精度就掉到整秒,前面说过,一秒的纬度差超过 30 米。
完整的写入逻辑:
import piexif def write_gps(path, lat, lon, alt=None): exif_dict = piexif.load(path) gps = { piexif.GPSIFD.GPSVersionID: (2, 3, 0, 0), piexif.GPSIFD.GPSLatitudeRef: b"N" if lat >= 0 else b"S", piexif.GPSIFD.GPSLatitude: to_dms_rational(lat), piexif.GPSIFD.GPSLongitudeRef: b"E" if lon >= 0 else b"W", piexif.GPSIFD.GPSLongitude: to_dms_rational(lon), piexif.GPSIFD.GPSMapDatum: b"WGS-84", } if alt is not None: gps[piexif.GPSIFD.GPSAltitudeRef] = 0 if alt >= 0 else 1 gps[piexif.GPSIFD.GPSAltitude] = (int(abs(alt) * 100), 100) exif_dict["GPS"] = gps exif_dict["thumbnail"] = None # 避免旧缩略图导致 dump 报错 exif_bytes = piexif.dump(exif_dict) piexif.insert(exif_bytes, path)exif_dict["thumbnail"] = None这一行是个小陷阱。如果原图带内嵌缩略图,某些情况下piexif.dump会因为缩略图字节和 IFD 结构不匹配而抛异常。清掉缩略图再 dump,成功率会高很多;代价是内嵌缩略图丢了,但主流看图软件会自己重新生成,影响不大。
4.2 从 GPX 轨迹反查每张照片的位置
这是我认为最优雅的一种方案:拍摄时只开一个轨迹记录,回来之后按时间戳把每张照片"投影"到轨迹上,位置自动算出来。
先说 ExifTool 的办法,它内置了这个能力,一条命令搞定:
exiftool -geotag track.gpx "-Geotime<DateTimeOriginal" ./photos-geotag告诉它去读 GPX,-Geotime<DateTimeOriginal表示用照片的原始拍摄时间去和轨迹点比对,匹配上的就把坐标写进去。如果相机时钟不准,还能用-geosync做整体偏移校正:
exiftool -geotag track.gpx -geosync=-00:02:30 "-Geotime<DateTimeOriginal" ./photos意思是相机时钟比标准时间慢了 2 分 30 秒,补偿掉之后再匹配。这个参数特别实用,我见过太多相机时钟飘了几分钟的情况,不校正的话匹配出来的位置能偏出去好几公里。
提示:Windows 版的 ExifTool 在处理 GPX 时依赖
Geo::Line模块。如果执行-geotag报错说找不到模块,需要单独补装一下,官方文档里有说明。
如果要更精细的控制,比如轨迹点稀疏需要做插值、或者要处理跨时区,那就自己写脚本。核心逻辑是:解析 GPX 得到一串 (时间戳, 纬度, 经度) 的有序点;读照片的DateTimeOriginal;在轨迹序列里做二分查找,找到照片时间落在哪两个轨迹点之间,然后按时间比例线性插值。
import gpxpy from datetime import datetime, timezone import piexif def load_track(gpx_path): with open(gpx_path, "r", encoding="utf-8") as f: gpx = gpxpy.parse(f) points = [] for track in gpx.tracks: for segment in track.segments: for pt in segment.points: points.append((pt.time, pt.latitude, pt.longitude)) points.sort(key=lambda x: x[0]) return points插值部分:
def interpolate(points, target_time): if not points: return None lo, hi = 0, len(points) - 1 while lo < hi: mid = (lo + hi) // 2 if points[mid][0] < target_time: lo = mid + 1 else: hi = mid if lo == 0: return points[0][1], points[0][2] t0, lat0, lon0 = points[lo - 1] t1, lat1, lon1 = points[lo] span = (t1 - t0).total_seconds() if span <= 0: return lat0, lon0 ratio = (target_time - t0).total_seconds() / span return lat0 + (lat1 - lat0) * ratio, lon0 + (lon1 - lon0) * ratio这段代码不长,但把线性插值的核心讲清楚了。轨迹点间隔十几秒的情况下,插值精度通常比直接取最近点高不少,尤其是在快速移动的场景里——比如坐车时拍的窗外风景,取最近点可能差出去上百米。
4.3 批量脚本里必须加的三层保护
脚本批量跑几百上千张照片,出错是常态,关键是要错得可控。我一般会加三层保护。
第一层是时间窗口过滤。如果照片的拍摄时间和轨迹的时间范围差了太远,说明这张照片根本不在这次行程里,强行匹配会得到离谱的结果。加一个阈值判断:
MAX_GAP = 300 # 秒 if abs((target_time - nearest_time).total_seconds()) > MAX_GAP: skip_and_log(path)第二层是文件级异常捕获。piexif.load在遇到结构损坏的 JPEG 时会直接抛异常,一张坏图能让整个批处理任务中断。所以每张照片的处理必须包在try/except里,失败就记日志继续下一张,最后统一汇总。
第三层是写后校验。写完不要直接认定成功,重新piexif.load一遍,把 GPS 读出来和预期值比对,误差超过阈值就标记为可疑。这一步能抓到一些非常隐蔽的问题,比如某些工具在写入时因为字段长度限制悄悄截断了大分母的秒值。
日志建议记这么几列:文件名、匹配到的时间戳、计算出的坐标、写入后的坐标、状态(成功/跳过/异常)。有了这份日志,出问题的时候能直接定位到是哪一步出的岔子,而不是两眼一黑重来一遍。
5. 不开电脑的做法:手机 App 与修图软件
5.1 手机端手动落点的操作逻辑
不是所有人愿意装命令行工具,手机端也有现成方案。常见的做法是找一款支持编辑照片元数据的 App,打开照片后选择"添加位置",然后在地图上点选。
这类 App 的操作逻辑一般是这样:地图上给你一个可拖动的标记,你把它拖到目标位置,App 内部把你点的位置换算成坐标写进 EXIF。这里有个隐藏坑——App 地图上显示的位置,和它实际写进照片的坐标,可能属于不同坐标系。有些 App 做了转换,有些没做。判断方法很简单:写完之后,把照片导到另一台设备的相册里看地图视图,如果位置偏了,那就是坐标系没对齐。
还有一个体验层面的问题:手机屏幕小,在地图上精确落点很难。如果目标位置在密集城区,几百米的误差肉眼根本看不出来。所以如果是精度要求高的场景,我建议还是在电脑上用大屏幕配合坐标输入框直接填数值,别在手机上硬拖。
5.2 修图软件导出时最容易丢坐标的环节
很多人遇到的情况是:明明原图有坐标,修完图之后坐标就没了。这不是软件"故意"删,而是导出流程默认不保留。
常见的丢失环节有几个。一是导出时选择了"导出为 Web 所用格式"或者"压缩优化",这类选项为了减小体积会把元数据整体剥掉。二是导出时勾选了"移除个人信息"之类的隐私选项,GPS 属于个人信息范畴,一并被清。三是导出格式变了,比如从 JPEG 导成 WebP 或 AVIF,目标格式的元数据支持不完全,GPS 段直接没写进去。
排查方法很直接:导出前后各跑一次exiftool -GPS:all 文件名,对比输出。如果导出后是空的,回头去导出设置里找"保留元数据""包含 EXIF"之类的开关,一般在导出对话框的高级或者元数据选项卡里。有些软件把这类选项藏在预设里,需要先保存一个自定义预设,之后每次导出都用这个预设,才不会再丢。
还有一类更隐蔽的:软件保留了 EXIF 的 GPS 段,但把GPSVersionID写坏了,或者把经纬度的有理数分母写成了 0。这种文件用 ExifTool 读会报解析错误,但图片本身能正常显示,所以在相册里看不出异常,只有当地图功能尝试读取时才暴露。校验的时候不能只看"有没有 GPS 字段",还要看能不能正确读出数值。
5.3 系统相册地图视图的依赖条件
写完之后想看效果,最直观的方式是打开系统相册的地图视图。但这里有几个前置条件,缺一个都看不到照片。
首先,相册必须真的读取了 EXIF 里的 GPS。有些系统相册优先读自己数据库里索引的信息,你把文件拷进去,它可能没有重新索引发掘新写入的坐标。遇到这种情况,删掉重新导入一次通常能解决。
其次,地图视图本身需要联网加载底图。离线状态下很多相册的地图页是空白的,容易让人误以为坐标没写进去。
第三,坐标系必须和相册使用的地图底图一致。前面说过,不同地图服务的坐标基准不一样,如果照片里是 WGS-84,而相册底图用的是偏移后的坐标,位置就会偏。这种偏移是系统性的,同一批照片会整体朝一个方向偏,很容易识别。
我一般的做法是:不依赖相册做最终验证,直接用 ExifTool 读数值,再拿数值到地图上手工查一次位置。虽然多一步,但结果最可信。
6. 写完之后的验证、被抹掉与隐私收尾
6.1 三步验证坐标真的写进去了
验证这件事,光看"字段存在"是不够的,我习惯分三步走。
第一步,确认字段齐全。跑exiftool -a -G1 -s -GPS:all photo.jpg,看输出里经纬度的 Ref 和数值是不是都在,海拔、基准这些可选字段有没有漏。
第二步,确认数值正确。用exiftool -n -GPSLatitude -GPSLongitude photo.jpg读出十进制值,和你的原始输入对比。注意这里会有极小的换算误差,因为度分秒的有理数表示是近似,误差在 0.0001 秒量级属于正常,超过这个量级就说明写入环节有问题。
第三步,确认能被第三方读取。把照片传到另一个设备,或者用另一款看图软件打开,看它能不能正常识别出位置。这一步是为了排除"只有 ExifTool 自己能读"的情况——有些写入工具写出来的结构不规范,ExifTool 容错性强能读,换成别的软件就歇菜了。
三步都过了,才算真正写完。
6.2 上传平台后坐标消失的常见原因
坐标在自己电脑上好好的,一上传到某个平台就没了,这种情况太常见了。原因基本就三类。
第一类是平台主动剥离。很多内容平台为了隐私保护,会在图片入库时统一清掉 GPS 信息。这是产品策略,不是你操作失误,也没法绕过——除非平台明确提供了"保留位置信息"的选项。
第二类是平台做了转码。上传的图片被重新压缩、重新编码,生成了一份新的文件,元数据在转码过程中丢失。这类情况有个特征:下载回来的图片,分辨率和文件大小都和原图不一样。判断方法就是下载回来对比一下。
第三类是 CDN 或者图片处理服务只保留了部分元数据。有些服务商的图片处理链路只透传尺寸、色彩空间这些必要信息,EXIF 整段丢弃。
应对办法其实很有限。如果你的场景确实需要坐标随图走,那要么选择明确支持保留元数据的存储方式,要么把坐标信息单独存一份结构化的记录文件,图片和记录通过文件名关联。后者虽然不够优雅,但在实际工作里最可靠——毕竟你控制不了别人的处理链路。
6.3 分享前把坐标清掉或模糊化
反过来说,如果你的照片要公开发布,坐标这件事就得反过来想。一张带精确坐标的照片发出去,等于把你的住址、常去的地点、作息规律一起公开了。家里拍的照片、孩子上学的路线、常去的健身房,这些信息拼起来能做的事,比大多数人想象的多。
清除的办法很简单,一条命令批量处理:
exiftool -GPS:all= -XMP:Geotag= -overwrite_original ./share-GPS:all=后面那个等号表示赋空值,也就是删除该组所有标签。注意 XMP 里的地理位置信息是另一套标签,要单独清,不然 EXIF 清干净了 XMP 里还留着一份。
如果不想全删,只想模糊化,可以把坐标取整到小数点后两位,大概对应一公里左右的精度,足够体现"在哪个城市",又不至于精确到门牌号:
exiftool -GPSLatitude=39.91 -GPSLatitudeRef=N \ -GPSLongitude=116.40 -GPSLongitudeRef=E photo.jpg还有一个折中方案是延迟写入。也就是拍照时不写坐标,等需要归档的时候再批量补上。这样原图天然干净,分享和归档用两套文件,互不影响。这个习惯我从几年前开始用,虽然多了一步,但省心很多。
7. 几个反复踩到的坑,写在这儿省得你再踩一遍
7.1 时间戳时区错位导致整批匹配失败
这是做轨迹匹配时最容易翻车的地方。相机的DateTimeOriginal记录的是本地时间,不带时区信息;GPX 轨迹里的时间戳是 UTC,带 Z 后缀。两者直接比较,会差出一整个时区的偏移量。在国内就是差 8 小时,匹配结果会整体跑到轨迹的另一端去,看起来像"全错",实际上是一个固定偏移。
解决办法有两个。一是在解析照片时间时,显式补上时区偏移,转成 UTC 再比较:
from datetime import datetime, timezone, timedelta raw = "2026:03:15 14:32:10" local = datetime.strptime(raw, "%Y:%m:%d %H:%M:%S") utc = local.replace(tzinfo=timezone(timedelta(hours=8))).astimezone(timezone.utc)二是用 ExifTool 的-geosync参数,把整个偏移量一次性补掉。第二种更省事,但前提是偏移量是固定的。如果你跨了时区拍摄,相机又没自动更新时区,那就得按拍摄日期分段处理,麻烦一些。
顺带提一句,EXIF 里其实有OffsetTimeOriginal这个字段,专门记录时区偏移,但绝大多数相机根本不写。所以别指望它,自己算。
7.2 度分秒与十进制混用
这个坑不分新手老手。有些人写坐标的时候,从某个地方复制来的是度分秒格式的字符串,比如39°54'31.4"N,直接截取数字往十进制字段里塞,结果写出来一个39.5431的纬度,位置直接跑到几百公里外。
判断方法很简单:纬度的绝对值不可能超过 90,经度不可能超过 180。如果你算出来的值超过这个范围,或者小数点后位数看着不对,先怀疑格式没转换。
正确的换算公式是:十进制度数 = 度 + 分/60 + 秒/3600。反向换算时,整数部分是度,小数部分乘 60 取整是分,剩下的小数部分再乘 60 就是秒。这三步一定要写清楚,别图省事。
7.3 HEIC 与 RAW 写入失败的处理
HEIC 的问题在于容器结构完全不同于 JPEG,EXIF 不是放在 APP1 段里,而是放在 ISOBMFF 的元数据盒子中。piexif 这类基于 JPEG 结构设计的库,遇到 HEIC 直接报错。能处理 HEIC 的工具不多,ExifTool 是其中一个,但写法上要注意,有些版本需要显式指定写入 XMP 才能被识别。
RAW 的问题前面提过,主要是原厂软件可能拒绝读取被外部修改过的文件。我的处理原则是:HEIC 一律先转 JPEG 再写,RAW 一律只写 XMP 附属文件。这两条规矩执行下来,基本没再出过问题。
转换 HEIC 可以用系统自带的预览工具批量导出,也可以用命令行工具。转的时候注意保留原图的 EXIF——有些转换工具默认会清掉元数据,那就白转了一场,还得重新写坐标。
我个人在实际操作里的体会是,写坐标这件事技术难度并不高,真正花时间的是前期确认和后期验证:坐标从哪来、属于哪个坐标系、写完有没有被别的软件改掉。这三件事想清楚了,剩下的就是敲几条命令的事。如果你的工作量大,早点把流程脚本化,比每次都手工点要省心得多——我现在手上那套脚本,最初就是为了处理一次外业回来的一千多张照片写的,后来改改参数就能复用,摊下来比手工操作划算太多。