:EXIF與XMP元數(shù)據(jù)批量處理指南)
簡介這份資源面向需要在照片中批量寫入定位信息的開發(fā)者與測繪、航拍從業(yè)者解決數(shù)碼照片缺乏坐標(biāo)記錄、難以與地理數(shù)據(jù)對應(yīng)的問題。核心思路是借助Python腳本讀取CSV中的經(jīng)緯度與高程數(shù)據(jù)將其寫入JPG文件的EXIF屬性實現(xiàn)坐標(biāo)信息的批量注入省去逐張手工編輯的繁瑣。壓縮包共12個文件包含10張JPG示例照片、1個main.py腳本和1份實驗數(shù)據(jù)CSV整體約28.67MB示例照片與數(shù)據(jù)文件可直接用于驗證腳本效果。目前已有1909人學(xué)習(xí)下載說明該方案在相關(guān)場景中具有一定參考價值。讀者可獲得一套可運(yùn)行的坐標(biāo)寫入腳本、配套測試數(shù)據(jù)與示例照片理解EXIF字段結(jié)構(gòu)、經(jīng)緯度與高程的寫入方式并在此基礎(chǔ)上按自身數(shù)據(jù)格式調(diào)整批量處理邏輯快速遷移到實際項目中。1. 往照片里塞進(jìn) POS 信息一個被低估的現(xiàn)場取證剛需很多做外業(yè)采集、巡檢、工程驗收的人手機(jī)相冊里躺著幾千張照片每張都對應(yīng)一個坐標(biāo)、一個時間、一個作業(yè)點可一旦導(dǎo)出到電腦這些信息就全丟了——照片還是那張照片但它和哪條線路、哪個樁號、哪次任務(wù)的關(guān)聯(lián)徹底斷了。往照片中寫入 POS 信息說的就是這件事把定位Position和姿態(tài)/時間Orientation/Time這類 POS 數(shù)據(jù)在拍攝或后處理階段直接嵌進(jìn)圖片文件本身讓照片自帶我在哪、我朝哪、我什么時候拍的。它解決的不是好看而是可追溯。測繪、電力巡檢、道路病害采集、農(nóng)業(yè)遙感、無人機(jī)航拍這些場景里照片一旦脫離 POS 就是廢片。適合誰適合手里有帶定位的相機(jī)或手機(jī)、又需要把照片交給別人做后續(xù)處理的一線人員也適合想自己寫腳本批量處理的開發(fā)者。下面按原理—實現(xiàn)—坑—進(jìn)階的順序講透。2. POS 信息到底寫進(jìn)照片的哪個位置EXIF、XMP 與文件名三條路2.1 先搞清楚 POS 數(shù)據(jù)能落在哪幾個容器里照片不是一塊鐵板它是一個有結(jié)構(gòu)的容器。POS 信息能塞進(jìn)去的地方主要有三處選哪處決定了你后面用什么工具讀、別人能不能讀到。第一處是 EXIF。這是 JPEG/TIFF 最傳統(tǒng)的元數(shù)據(jù)區(qū)GPS 經(jīng)緯度、海拔、拍攝時間、相機(jī)朝向都在這里有標(biāo)準(zhǔn)字段。GPSLatitude、GPSLatitudeRef、GPSLongitude、GPSLongitudeRef、GPSAltitude、GPSImgDirection 是幾個關(guān)鍵標(biāo)簽。優(yōu)點是幾乎所有看圖軟件、GIS 軟件都認(rèn)缺點是字段容量有限姿態(tài)角、精度因子、坐標(biāo)系這些擴(kuò)展信息沒地方放。第二處是 XMP。Adobe 主導(dǎo)的 XML 元數(shù)據(jù)框架可以自定義命名空間想寫多少寫多少。無人機(jī)航拍里常見的 Yaw/Pitch/Roll、RTK 固定解狀態(tài)、坐標(biāo)系 EPSG 碼塞 XMP 最合適。缺點是老軟件不認(rèn)得靠 ExifTool 或?qū)iT的解析庫。第三處是文件名和伴隨文件。有些采集軟件干脆把坐標(biāo)寫進(jìn)文件名比如DJI_20240101_120000_116.397_39.908.JPG再配一個同名.pos或.mrk文本。這條路最土但最穩(wěn)任何系統(tǒng)都能讀代價是要額外維護(hù)一個索引。提示如果照片最終要進(jìn) GIS 或做空三優(yōu)先 EXIF 寫基礎(chǔ) GPSXMP 寫擴(kuò)展姿態(tài)文件名保留可讀的編號三條路一起用不沖突。2.2 用 ExifTool 命令行寫入最小可復(fù)現(xiàn)操作先裝 ExifTool這是元數(shù)據(jù)操作的事實標(biāo)準(zhǔn)跨平臺。下面這條命令把經(jīng)緯度、海拔、拍攝方向?qū)戇M(jìn)一張照片。# 寫入 GPS 基礎(chǔ)信息到單張照片 exiftool \ -GPSLatitude39.908722 \ -GPSLatitudeRefN \ -GPSLongitude116.397499 \ -GPSLongitudeRefE \ -GPSAltitude45.6 \ -GPSAltitudeRef0 \ -GPSImgDirection127.5 \ -GPSImgDirectionRefT \ -DateTimeOriginal2024:01:01 12:00:00 \ ./IMG_0001.JPG邏輯說明ExifTool 的參數(shù)是標(biāo)簽值的形式Ref類標(biāo)簽是方向參考緯度 N/S、經(jīng)度 E/W、海拔 0 表示海平面以上、方向 T 表示真北。GPSImgDirection就是相機(jī)朝向角正北為 0順時針增加。寫完可以用exiftool -GPS:all ./IMG_0001.JPG回讀驗證。參數(shù)說明經(jīng)緯度必須用十進(jìn)制度不要寫成度分秒字符串否則 ExifTool 會按字面存進(jìn)去導(dǎo)致 GIS 讀出來是錯的。海拔單位是米。時間格式固定為YYYY:MM:DD HH:MM:SS冒號分隔日期這是 EXIF 標(biāo)準(zhǔn)的怪癖寫成橫杠會失敗。2.3 批量寫入從 POS 文本到照片的映射腳本單張寫沒意義現(xiàn)場都是成百上千張。常見做法是采集軟件導(dǎo)出一個 POS 文本每行是文件名,經(jīng)度,緯度,海拔,朝向,時間然后用腳本批量灌進(jìn)去。import subprocess import csv # POS 文件格式: filename,lon,lat,alt,yaw,datetime POS_FILE pos.txt PHOTO_DIR ./photos with open(POS_FILE, newline, encodingutf-8) as f: reader csv.reader(f) for row in reader: fname, lon, lat, alt, yaw, dt row # 緯度方向由正負(fù)號決定負(fù)數(shù)為南緯 lat_ref N if float(lat) 0 else S lon_ref E if float(lon) 0 else W cmd [ exiftool, f-GPSLatitude{abs(float(lat))}, f-GPSLatitudeRef{lat_ref}, f-GPSLongitude{abs(float(lon))}, f-GPSLongitudeRef{lon_ref}, f-GPSAltitude{alt}, -GPSAltitudeRef0, f-GPSImgDirection{yaw}, -GPSImgDirectionRefT, f-DateTimeOriginal{dt}, f{PHOTO_DIR}/{fname}, ] subprocess.run(cmd, checkTrue) print(fdone: {fname})邏輯說明腳本讀 POS 文本逐行拼 ExifTool 命令。關(guān)鍵點是緯度方向不能直接寫負(fù)數(shù)EXIF 用Ref字段表達(dá)南北所以取絕對值再配 N/S。經(jīng)度同理。checkTrue讓任何一張失敗就拋異常避免靜默漏寫。參數(shù)說明abs()處理正負(fù)號是必須的很多人圖省事直接寫負(fù)緯度結(jié)果 GIS 里照片跑到南極。時間字段如果 POS 里是2024-01-01 12:00:00要先替換成冒號格式再傳否則 ExifTool 報格式錯誤。3. 姿態(tài)角與坐標(biāo)系為什么你的照片在 GIS 里躺平了3.1 Yaw/Pitch/Roll 和 GPSImgDirection 不是一回事現(xiàn)場采集常拿到三個姿態(tài)角Yaw偏航、Pitch俯仰、Roll橫滾。很多人直接把 Yaw 寫進(jìn)GPSImgDirection結(jié)果在三維軟件里照片朝向?qū)Σ簧稀T蚴荊PSImgDirection只表達(dá)相機(jī)光軸在水平面的投影方向而 Yaw 的定義依賴載體坐標(biāo)系兩者在相機(jī)有俯仰時并不相等。正確做法是如果只是二維地圖展示Yaw 近似當(dāng)方向角用問題不大如果要做空三或三維重建姿態(tài)角應(yīng)該寫進(jìn) XMP 自定義字段保留原始定義讓后續(xù)軟件自己換算。下面是把三個角寫進(jìn) XMP 的例子。# 寫入自定義 XMP 姿態(tài)字段 exiftool \ -XMP-drone:Yaw127.5 \ -XMP-drone:Pitch-15.2 \ -XMP-drone:Roll2.1 \ -XMP-drone:EPSG4326 \ ./IMG_0001.JPG邏輯說明XMP-drone里的drone是自定義命名空間前綴ExifTool 允許這樣臨時定義。EPSG 碼寫清楚坐標(biāo)系4326 是 WGS84 經(jīng)緯度如果是投影坐標(biāo)要寫對應(yīng)碼否則下游軟件會按經(jīng)緯度誤讀。參數(shù)說明姿態(tài)角單位是度Pitch 向下為負(fù)還是正取決于你的采集軟件約定寫之前一定確認(rèn)這個符號搞反在三維里就是上下顛倒。EPSG 碼不要省省了下游就得猜。3.2 坐標(biāo)系轉(zhuǎn)換WGS84 和 CGCS2000 別混著寫國內(nèi)項目經(jīng)常遇到 CGCS2000 坐標(biāo)它和 WGS84 在米級精度上差異很小但嚴(yán)格來說不是同一個橢球。如果采集設(shè)備輸出的是 CGCS2000 經(jīng)緯度直接當(dāng) WGS84 寫進(jìn) EXIF在要求厘米級的場景會引入系統(tǒng)偏差。常見做法是EXIF 里統(tǒng)一寫 WGS84轉(zhuǎn)換在采集端完成如果必須保留原始坐標(biāo)系就在 XMP 里寫明 EPSG 碼并在項目文檔里記錄轉(zhuǎn)換參數(shù)。下面是一個用 pyproj 做轉(zhuǎn)換的片段。from pyproj import Transformer # CGCS2000 經(jīng)緯度 - WGS84 經(jīng)緯度 transformer Transformer.from_crs(EPSG:4490, EPSG:4326, always_xyTrue) lon_wgs, lat_wgs transformer.transform(116.397499, 39.908722) print(lon_wgs, lat_wgs)邏輯說明always_xyTrue保證輸入輸出都是經(jīng)度在前、緯度在后這是 pyproj 最容易踩的坑默認(rèn)順序是緯度在前。EPSG:4490是 CGCS2000 地理坐標(biāo)EPSG:4326是 WGS84。參數(shù)說明轉(zhuǎn)換前確認(rèn)源坐標(biāo)系碼別拿投影坐標(biāo)如 EPSG:4547當(dāng)?shù)乩碜鴺?biāo)傳進(jìn)去那樣結(jié)果會離譜到?jīng)]法看。轉(zhuǎn)換后建議保留 8 位小數(shù)EXIF 精度足夠。4. 避坑與排查寫入 POS 信息時最容易翻車的五件事4.1 照片在 GIS 里位置整體偏移幾百米現(xiàn)象批量寫入后所有照片在 GIS 里統(tǒng)一往一個方向偏。原因源 POS 用的是投影坐標(biāo)米被當(dāng)成經(jīng)緯度度寫進(jìn)去了或者坐標(biāo)系碼寫錯。解決回讀一張照片的經(jīng)緯度看數(shù)值范圍經(jīng)度應(yīng)在 -180 到 180緯度在 -90 到 90超出這個范圍一定是坐標(biāo)系搞錯了。用exiftool -GPS:all逐張抽查。4.2 部分照片寫入成功、部分沒寫進(jìn)去現(xiàn)象腳本跑完有的照片有 GPS有的沒有。原因文件名大小寫不一致、POS 文本里有空行、或者照片本身是只讀屬性。解決腳本里加文件存在性檢查POS 解析時跳過空行寫入前用os.chmod確保可寫。別用checkFalse吞掉錯誤那等于給自己埋雷。4.3 時間字段寫進(jìn)去但排序亂了現(xiàn)象照片按時間排序時順序不對。原因EXIF 時間格式必須是YYYY:MM:DD HH:MM:SS如果 POS 里是 Unix 時間戳或帶時區(qū)偏移直接寫會失敗或被忽略。解決統(tǒng)一轉(zhuǎn)成標(biāo)準(zhǔn)格式時區(qū)問題在采集端就歸一化到本地時間或 UTC別混著來。4.4 寫入后原圖被改壞、打不開現(xiàn)象某張照片寫入后無法預(yù)覽。原因ExifTool 默認(rèn)是原地修改如果磁盤滿或進(jìn)程被殺文件可能寫了一半。解決加-o輸出到新目錄保留原圖或者先備份再批量寫。血淚經(jīng)驗是永遠(yuǎn)不要在沒有備份的原始數(shù)據(jù)上直接跑批量寫入。4.5 手機(jī)拍的照片讀不到 GPS現(xiàn)象明明手機(jī)拍照時開了定位導(dǎo)出后 EXIF 里沒有 GPS。原因部分手機(jī)默認(rèn)不把 GPS 寫進(jìn)原圖或者通過社交軟件傳輸時被剝離。解決用原圖導(dǎo)出別走壓縮通道確認(rèn)相機(jī)設(shè)置里定位權(quán)限開啟。如果原圖確實沒有只能靠外部 POS 文件補(bǔ)補(bǔ)的時候注意時間對齊。5. 進(jìn)階把 POS 寫入做成可驗證的流水線寫到這一步單張、批量、坐標(biāo)系、避坑都過了剩下的是怎么讓這件事可復(fù)現(xiàn)、可驗證。我一般會搭一條最小流水線采集端導(dǎo)出 POS 文本 → 腳本批量寫入 → 自動回讀校驗 → 生成一份校驗報告。校驗報告里至少包含每張照片的經(jīng)緯度、時間、寫入狀態(tài)任何一張對不上就標(biāo)紅。下面是一個回讀校驗的片段思路是寫入后立刻讀回來和源數(shù)據(jù)比對誤差超過閾值就報警。import subprocess import re def read_gps(path): out subprocess.run( [exiftool, -GPSLatitude, -GPSLongitude, -s, -s, -s, path], capture_outputTrue, textTrue ).stdout nums re.findall(r[-]?\d\.\d, out) return nums # [lat, lon] # 寫入后校驗容差 1e-6 度 lat_src, lon_src 39.908722, 116.397499 lat_read, lon_read map(float, read_gps(./IMG_0001.JPG)) assert abs(lat_read - lat_src) 1e-6, 緯度寫入不一致 assert abs(lon_read - lon_src) 1e-6, 經(jīng)度寫入不一致 print(校驗通過)邏輯說明-s -s -s讓 ExifTool 只輸出值不輸出標(biāo)簽名方便正則提取。容差設(shè) 1e-6 度大約對應(yīng) 0.1 米足夠覆蓋浮點誤差。校驗不通過就中斷別讓壞數(shù)據(jù)流到下游。參數(shù)說明正則只抓帶小數(shù)點的數(shù)避免把整數(shù)標(biāo)簽誤抓進(jìn)來。如果照片本身沒有 GPSread_gps返回空列表map會報錯這其實是好事——說明寫入根本沒生效早發(fā)現(xiàn)早處理。一個具體技巧把 POS 文本和照片目錄做成參數(shù)化用配置文件管理坐標(biāo)系、時間格式、字段映射這樣換一個項目只改配置不改代碼。我吃過虧的地方是硬編碼字段順序結(jié)果換了個采集軟件POS 列順序變了整批數(shù)據(jù)全錯位。后來養(yǎng)成習(xí)慣任何批量處理前先拿三張照片做小樣驗證確認(rèn)無誤再全量跑。這個習(xí)慣救過我很多次。希望幫到你。本文還有配套的精品資源點擊獲取