
简介面向需要在照片中写入地理坐标信息的开发者与航测爱好者这份压缩包提供了一套基于Python的批量EXIF坐标写入方案可同时写入经纬度与高程适用于无人机航片定位、外业照片整理、GIS数据预处理等场景。资源共12个文件包含10张JPG样例照片、1个Python主脚本以及1个CSV实验数据文件压缩包总大小约28.67MB目录结构简单明了。方案通过读取CSV中的坐标数据调用Python库解析并修改照片EXIF信息实现批量写入避免逐张手动操作内置样例照片和实验数据可直接运行脚本验证效果并根据实际需求调整输入路径与输出位置。目前已有1904人学习下载适合具备一定Python基础、希望快速掌握EXIF读写或处理批量照片定位信息的学习者参考。 搞测绘的朋友应该都见过这类东西同事在群里甩过来一个往照片中写入pos信息.rar压缩包解压开往往就一个exiftool.exe、一个pos.csv样例、一个bat脚本。外行看着像黑科技其实背后是一套非常标准化的元数据写入流程。我这次把这套流程彻底拆开把照片位置信息存哪儿、数据从哪来、怎么批量写、写完怎么验一次讲透。无论你是处理无人机航测外业数据、做地质调查拍照归档还是单纯想给老照片补上坐标读完都能直接上手。1. 先弄清楚这道工序到底解决什么问题别急着敲命令先想明白手上到底是什么活。以我这些年经手的实际项目来看往照片里写POS信息这个需求基本逃不出三类场景相机不带GPS模块的补录、无人机航测数据的POS校正、野外调查影像的坐标归档。三类场景看起来不一样处理思路也有区别如果你是第一次接触这个活儿尤其建议把这一节看完——很多写完之后发现白写了的返工根源都是最初没搞清楚自己的数据从哪来、要写到哪个软件里去。1.1 三类最常碰到的写POS需求第一类是相机不带GPS模块。单反、微单、胶片翻拍、老卡片机照片EXIF里根本没有坐标字段。出去玩一圈拍了几百张想在照片管理软件里按地图位置整理就得靠手机里的轨迹数据把坐标补进照片。这类需求量大但字段简单核心就是经纬度加时间不太涉及姿态角这些附加数据。第二类是无人机航测数据预处理这也是最绕不开的一类。飞机飞完、照片拷出来了但飞控记录的POS数据和照片的拍摄时间没有严格对齐或者你做了PPK/RTK解算拿到了精度更高的坐标需要把解算结果重新写回每一张照片。建模软件能不能正确读出每张照片的位置直接决定后续空三能不能跑通。这类需求除了坐标通常还要写高度和姿态角字段最全、坑也最多。第三类是野外调查、工程影像记录。拍了一堆现场照片项目要求每张都带准确坐标方便后期入库、在GIS里按点查看。现场不可能每张都掏手机记一次最省事的办法就是回到办公室统一批量写入。三类需求本质都一样把照片文件和坐标数据合并成一个文件让照片本身知道自己在哪里而不是在旁边再跟一张Excel表。理解了这一点后面所有操作就有了主线——不管数据源是什么最终都是往同一个EXIF/XMP目标里写差异只在于怎么把数据源对齐成软件认识的格式。1.2 为什么批处理不能靠看图软件有人会问Windows属性面板不也能编辑经纬度吗能但那是单张手动操作。几十张照片勉强能忍几百上千张航测照片手动改纯属浪费时间。而且属性面板能写的字段非常有限往往只给个经纬度高度、方向、时间戳要么填不了要么格式不对。更麻烦的是很多看图软件写入的坐标并不符合EXIF规范。比如只写了GPSLatitude和GPSLongitude漏了GPSLatitudeRef南北半球标识和GPSLongitudeRef东西半球标识专业建模软件读出来就是错的严重的直接拒绝识别。命令行工具的优势在于可以精确控制每一个字段而且脚本可复现、可追溯出了问题能排查。这就是为什么业内宁可用命令行批处理也不愿意在图形界面里一个个点。2. 照片里的位置信息到底存哪儿EXIF GPS与自定义标签要写对位置先得知道位置存在照片文件的哪里。照片的EXIF信息里有一块专门的GPS区域业内叫GPS IFD几乎所有地图、建模、GIS软件读照片位置都是从这儿读。这里不打算展开二进制结构只讲你写POS时一定会碰到的几个字段和它们之间的关系弄明白这些后面写命令就不会心虚。另外航测场景还涉及XMP自定义标签这一节最后单独讲。实际操作中用到的主要字段就下面这些我按写入频率排了个序字段名含义写入值示例容易犯的错GPSLatitudeRef南北纬标识N / S填成North或者干脆不填GPSLatitude纬度值31 13 49.44 或 31.2304度分秒格式写错GPSLongitudeRef东西经标识E / W国内照片写成W点直接飞到美洲GPSLongitude经度值121 28 25.32 或 121.4737精度不够小数位太少GPSAltitude高度值米12.5单位错用了英尺GPSAltitudeRef高度参考0 海平面以上 / 1 以下不写导致软件读不到高度GPSDateStamp日期2024:01:15用了短横线或斜杠GPSTimeStamp时间02:30:00时区不统一GPSImgDirection拍摄朝向135需要和GPSImgDirectionRef成对出现先别急着背关键是理解两件事。第一经纬度在EXIF里是三截式存储的度、分、秒各占一个有理数位而不是一个简单的小数第二所有GPS字段都是成组出现的少了一个Ref类标识字段坐标就是残废数据。很多来来回回折腾写入了但读不出的问题最后查出来都是这两条没做到尤其是第二条少一个Ref字段坐标就凭空多了个负号或者飞了半球。2.1 度分秒换算最容易写错的一步EXIF里的GPSLatitude保存的是三个有理数分别对应度、分、秒。十进制度数转度分秒的公式很简单度数取整数部分小数部分乘以60得到分剩余的小数部分再乘以60得到秒。拿我实际算过的例子说31.2304°N度是31分是(31.2304-31)×6013.824取13秒是(13.824-13)×6049.44所以写成31 13 49.44。用ExifTool写入时可以直接填十进制的31.2304工具会自动转换省去手工计算。但如果你自己写Python脚本或者打算手工处理原始数据就必须按这个算法转成三个值再写。我见过太多人栽在这一步——把31.2304当成度分秒直接填进去坐标偏出去好几公里后面怎么查都查不出来。2.2 航测POS还要写自定义XMP标签标准EXIF GPS字段能存经纬度和高度但无人机航测说的POS信息还包括姿态角航向角Yaw、俯仰角Pitch、横滚角Roll以及相对起飞点的高度。这些数据在EXIF里没有标准位置各家建模软件约定俗成地写在照片文件的XMP元数据区域里字段名各带厂商前缀比如AbsoluteAltitude、RelativeAltitude、GimbalYawDegree、GimbalPitchDegree这类。所以写POS时成熟做法是双写坐标写EXIF GPS字段高度和姿态写XMP字段。有些建模软件读EXIF里的绝对高度另一些读XMP里的相对高度只写一边换软件就露馅回头排查时还不容易想到是字段没写全。提示动手写自定义XMP前先用exiftool -a -u -G1 原片.jpg把原始照片的全部元数据列出来看目标软件原厂数据长什么样、用的什么字段名和单位照着它的习惯写比自己猜字段名靠谱得多。3. 位置数据从哪儿来三种主流来源与预处理坐标不会凭空出现。批处理之前你得先有一份位置数据源文件它是写入的原料。按我的经验无非下面三种来源GPX轨迹、CSV点位表、飞控日志解算出的POS数据。来源不同预处理的重点也不同搞错来源直接在第一步就把坐标污染了。3.1 手机或手持GPS导出的GPX轨迹适合随手拍、回来统一打点的场景。用手机上的户外轨迹记录软件或者运动手表开一个轨迹记录走完导出GPX文件里面是一串带时间戳的轨迹点。ExifTool的-geotag功能就是干这个的拿每张照片的拍摄时间去GPX里找对应位置轨迹点之间还能做线性插值所以不需要每个拍摄点都手动记录。这里有个几乎所有人都会踩的坑时区。GPX文件里的时间一般是UTC而相机照片里的时间戳是本地时间。国内用北京时间比UTC快8小时匹配前必须给ExifTool一个偏移量也就是后面会讲的-geosync参数否则匹配出来的位置全错。相机本身时间不准的匹配前先统一校正。3.2 手动整理的CSV点位表适合照片数量不多、或者每个拍摄点都用RTK/全站仪实测过坐标的场景。整理成一个CSV表格常见列是文件名、经度、纬度、高度如需写姿态角就再加上Yaw、Pitch、Roll。CSV有两个细节特别容易出问题。一是文件名匹配必须一字不差含扩展名DJI_20250101_100001.JPG和DJI_20250101_100001.jpg在Windows下是同一个文件但在脚本里逐字符比对时不相等建议预处理时统一大小写。二是编码Windows下Excel另存的CSV默认可能是GBK而Python脚本按UTF-8读就会乱码建议统一转成UTF-8无BOM格式。3.3 无人机飞控日志解算的POS数据航测场景最常用。飞控原始日志记录了每个时刻的经纬度、高度和姿态角经过PPK或RTK解算后能得到厘米级精度的坐标。解算软件通常能直接导出带时间戳的POS表格格式一般是时间、经度、纬度、高度、Yaw、Pitch、Roll。这类数据的预处理核心是时间对齐。飞控日志的时间是UTC照片EXIF里的CreateDate有时是UTC有时是UTC8取决于你设置的相机时区。写脚本对齐前先拿一张照片把它的所有时间字段打出来确认用哪个时区别想当然。另外解算出来的高度基准可能是椭球高而建模软件可能需要的是海拔高中间差着一个大地水准面差距各地不同一般十几米到几十米要精确处理就别忽略。4. 动手实操ExifTool批量写入的完整链路工具选型直接给结论批处理首选ExifTool没有之一。它几乎支持所有主流图片格式的EXIF/XMP读写跨平台命令行稳定能批量处理。那些往照片中写入pos信息.rar压缩包里打包的核心工具九成就是它。所以不管压缩包里套了多少层bat和exe你真正要学的是把ExifTool用熟。4.1 准备阶段工具、备份与编码先把环境准备好。Windows下用exiftool(-k).exe建议改名为exiftool.exe放进一个干净目录后面所有命令都以这个文件名为准Linux或macOS用包管理器装libimage-exiftool-perl即可。第一次跑批处理千万不要加-overwrite_original参数让ExifTool默认给每张照片生成带_original后缀的备份文件。等确认写入无误、目标软件能正常识别之后再考虑清理备份或者重跑覆盖。Windows的cmd还有一个隐藏大坑默认代码页是GBK脚本里只要出现中文文件名就会乱码写入的目标路径直接找不到文件。批量脚本开头加一句 chcp 65001 nul 切到UTF-8能省掉大量排查时间。这个细节看起来小但我在帮别人排查批处理跑一半报错时有相当比例都是栽在这里。4.2 先跑通单张写入写之前先拿一张照片当试验品。道理很简单任何字段写错单张返工的代价远小于几百张掺在一起才发现问题。假设要把这张照片定位到东经121.4737、北纬31.2304、海拔12.5米、拍摄朝向135度ExifTool命令长这样exiftool -GPSLatitude31.2304 -GPSLatitudeRefN -GPSLongitude121.4737 -GPSLongitudeRefE -GPSAltitude12.5 -GPSAltitudeRef0 -GPSDateStamp2024:01:15 -GPSTimeStamp02:30:00 -GPSImgDirection135 -GPSImgDirectionRefT photo.jpg写完之后读回来核对命令是exiftool -n -gps:all photo.jpg-n参数让工具直接输出十进制度数方便人眼比对-gps:all把这个命名空间下所有已写字段列出来。看到GPSLatitude31.2304、GPSLongitude121.4737、GPSAltitude12.5这张就成了。这里我故意把GPSTimeStamp写成02:30:00而不是10:30:00因为报表时间用UTC是行业惯例。后面如果要接GPX轨迹匹配或者进专业建模软件时间统一用UTC能少很多莫名其妙的问题。4.3 GPX轨迹自动匹配写入有GPX轨迹文件时不需要手工填坐标直接让ExifTool匹配写入exiftool -geotag track.gpx -geosync08:00:00 ./photos/*.jpgExifTool会读取照片的CreateDate或DateTimeOriginal到GPX里找对应时间的位置在最近的两个轨迹点之间做线性插值。命令里的-geosync08:00:00就是时区校正告诉工具照片时间比轨迹时间快8小时。匹配不上的时候按顺序排查三件事第一照片的拍摄时间本身是不是当年没校准时区或日期用exiftool -time:all photo.jpg看实际记录的时间第二GPX文件是否导出完整打开看一眼轨迹点时间范围够不够覆盖照片时间第三照片是不是被某些压缩工具重写过EXIF里的时间字段被抹掉了。百分之九十的匹配失败都出在这三处。4.4 按CSV批量写入bat脚本与Python脚本网上流传的rar包里最常见的是一段bat脚本。它写的是硬逻辑只适用于CSV列顺序固定、全部坐标都在同一半球的情况。假设CSV格式是文件名,纬度,经度,高度且全部在北半球东经一个能用的版本长这样echo off chcp 65001 nul for /f tokens1-4 delims, %%a in (pos.csv) do ( exiftool.exe -GPSLatitude%%b -GPSLatitudeRefN -GPSLongitude%%c -GPSLongitudeRefE -GPSAltitude%%d -GPSAltitudeRef0 -overwrite_original %%a )注意这个脚本默认全是北纬东经也不支持CSV表头属于能用但糙的版本。如果你手里的CSV带表头、经纬度还可能为负建议直接用Python一次性写清楚import csv, os, subprocess EXIFTOOL rD:\tools\exiftool.exe PHOTO_DIR rD:\photos POS_CSV rD:\pos\pos.csv def dms(value): value abs(float(value)) deg int(value) minutes_float (value - deg) * 60 minutes int(minutes_float) seconds round((minutes_float - minutes) * 60, 5) return f{deg} {minutes} {seconds} with open(POS_CSV, encodingutf-8) as f: for row in csv.DictReader(f): name row[photo_name] lat, lon, alt float(row[lat]), float(row[lon]), row.get(alt, 0) cmd [EXIFTOOL, -overwrite_original, f-GPSLatitude{dms(lat)}, f-GPSLatitudeRef{N if lat 0 else S}, f-GPSLongitude{dms(lon)}, f-GPSLongitudeRef{E if lon 0 else W}, f-GPSAltitude{alt}, -GPSAltitudeRef0, os.path.join(PHOTO_DIR, name)] subprocess.run(cmd, checkTrue, capture_outputTrue) print(written:, name)这段脚本的核心逻辑是负纬度自动配S标识负经度自动配W标识不用手工按正负分文件用到的是度分秒字符串ExifTool会解析成内部有理数结构。如果要加姿态角往cmd列表里追加f-XMP:GimbalYawDegree{row[yaw]}这类参数就行。注意照片数量到了几千张级别逐张调用exiftool进程会有进程启动开销速度偏慢。稳妥做法是先小批量试跑确认命令无误再放开跑。追求极限性能可以把所有命令写进argfile用exiftool - argfile一次性处理这是进阶玩法日常批处理掌握上面的脚本足够。5. 写入之后的验证、踩坑与收尾心得写完了不代表完事建模软件读不出来前面全白干。我自己习惯在交付前做足三步验证把问题拦在最后一道门之前。5.1 三步验证写入结果第一步命令行抽查。随机抽三到五张照片执行exiftool -n -gps:all 照片.jpg把经纬度、高度、时间戳和源数据逐项比对。坐标数量和格式一眼就能看出问题。第二步软件实测。把照片拖进地图类查看软件或者GIS软件比如QGIS加载后看点位是不是落在预期位置。这一轮能发现命令行看不出的大问题比如坐标系偏移——点位上显示的位置和真实位置差了三百米多半就是坐标系的锅。第三步也是最重要的一步进目标建模软件试跑。航测照片写完后随便挑几张导入ContextCapture或Pix4D这类软件确认软件能正确读出每张照片的POS。这一步专门用来暴露字段写入了但软件不认的格式兼容问题别跳过等空三跑起来才发现就晚了。5.2 这批活最常见的五个坑坐标系混用排第一。手机地图App导出的坐标很多是加密偏移过的坐标GCJ-02直接写入照片在WGS84坐标系的专业软件里会偏移几百米。判断很简单写入后拿一个已知坐标的点对照或者在GIS里叠一层影像看对齐情况。航测POS一般来自飞控或RTK解算是WGS84或CGCS2000可以直接用。高度基准没搞清。椭球高和海拔高差着几十米不搞清基准写进去的高度就可能把后续处理带偏。飞控POS通常给的是椭球高要海拔高就得做大地水准面改正。照片时间没校正。相机时间快了5分钟GPX匹配出来的位置就能偏出几百米。批量处理前先校正相机时间或者用-geosync统一补偿。备份文件被二次写入。没有用-overwrite_original时每张照片会生成一个_original备份。如果批量命令的匹配规则写得太宽脚本会把这些备份也当成目标写一遍生成一堆嵌套的_original_original目录直接乱套。批处理时明确指定扩展名和目录别偷懒。只写坐标不写时间。GPSDateStamp和GPSTimeStamp不全很多软件在时间轴上无法正确显示点位GPX匹配也会失效。坐标和时间是一体的要写就一起写。5.3 一点收尾建议我现在每次做这类批处理都会保留三样东西原始照片备份、pos.csv源数据、一条能复现的完整命令记录。航测项目周期长三个月后要补飞一个架次、重跑一遍空三没有这套记录你根本想不起来当年坐标是哪儿来的、写入格式是什么。这个习惯帮我省过不少麻烦。说回那个往照片中写入pos信息.rar——里面的工具再花哨拆穿了也就是ExifTool加一段批处理脚本。真正值钱的不是那几KB的代码是你对照片元数据结构和数据源处理的理解。把EXIF GPS字段、时区对齐、坐标系基准这三件事吃透你完全能自己写出更好用、更适合自己业务的POS写入包。本文还有配套的精品资源点击获取