python 1 大疆照片rtk gps 信息提取 定位精度 - MKT
python 1 大疆照片rtk gps 信息提取 定位精度
1要用原图

2 提取保存路径

DJI_0041.JPG quality=rtk_fixed_cm_level rtk_flag=50 positioning=NA accuracy_source=rtk_std_fields h_acc_m=0.0173 v_acc_m=0.02368 rtk_std_lat_m=0.01279 rtk_std_lon_m=0.01170 rtk_std_hgt_m=0.02368 rtk_diff_age=NA surveying_mode=NA gps_dop=NA absolute_altitude=+134.90 relative_altitude=+45.69 flight_pitch=-9.40 flight_roll=+6.90 flight_yaw=-91.00 gimbal_pitch=-89.90 gimbal_roll=+0.00 gimbal_yaw=-89.90 speed_x=-0.10 speed_y=-7.20 speed_z=+0.00 fields_found=AbsoluteAltitude,CamReverse,CreateDate,DewarpFlag,EXIF_BodySerialNumber,EXIF_DateTimeDigitized,EXIF_DateTimeOriginal,FlightPitchDegree,FlightRollDegree,FlightXSpeed,FlightYSpeed,FlightYawDegree,FlightZSpeed,GPSLatitude,GPSLongtitude,GPS_GPSAltitude,GPS_GPSAltitudeRef,GPS_GPSLatitude,GPS_GPSLatitudeRef,GPS_GPSLongitude,GPS_GPSLongitudeRef,GPS_GPSVersionID,GimbalPitchDegree,GimbalRollDegree,GimbalYawDegree,GpsLatitude,Image_Make,Image_Model,Latitude,Make,MakerNote_CameraPitch,MakerNote_CameraRoll,MakerNote_CameraYaw,MakerNote_Make,MakerNote_Pitch,MakerNote_Roll,MakerNote_SpeedX,MakerNote_SpeedY,MakerNote_SpeedZ,MakerNote_Tag_0x000C,MakerNote_Tag_0x000D,MakerNote_Tag_0x000E,MakerNote_Tag_0x0017,MakerNote_Yaw,Model,ModifyDate,RTKFlag,RelativeAltitude,RtkFlag,RtkStdHgt,RtkStdLat,RtkStdLon MakerNote_Tag_0x000C=[(0.5,),(0.5,)] MakerNote_Tag_0x000D=[5,6,5,4] MakerNote_Tag_0x000E=5 MakerNote_Tag_0x0017=1 DJI_0042.JPG quality=rtk_fixed_cm_level rtk_flag=50 positioning=NA accuracy_source=rtk_std_fields h_acc_m=0.0173 v_acc_m=0.02358 rtk_std_lat_m=0.01278 rtk_std_lon_m=0.01167 rtk_std_hgt_m=0.02358 rtk_diff_age=NA surveying_mode=NA gps_dop=NA absolute_altitude=+134.88 relative_altitude=+45.68 flight_pitch=-9.50 flight_roll=+6.90 flight_yaw=-91.10 gimbal_pitch=-90.00 gimbal_roll=+0.00 gimbal_yaw=-89.80 speed_x=+0.00 speed_y=-7.30 speed_z=+0.00 fields_found=AbsoluteAltitude,CamReverse,CreateDate,DewarpFlag,EXIF_BodySerialNumber,EXIF_DateTimeDigitized,EXIF_DateTimeOriginal,FlightPitchDegree,FlightRollDegree,FlightXSpeed,FlightYSpeed,FlightYawDegree,FlightZSpeed,GPSLatitude,GPSLongtitude,GPS_GPSAltitude,GPS_GPSAltitudeRef,GPS_GPSLatitude,GPS_GPSLatitudeRef,GPS_GPSLongitude,GPS_GPSLongitudeRef,GPS_GPSVersionID,GimbalPitchDegree,GimbalRollDegree,GimbalYawDegree,GpsLatitude,Image_Make,Image_Model,Latitude,Make,MakerNote_CameraPitch,MakerNote_CameraRoll,MakerNote_CameraYaw,MakerNote_Make,MakerNote_Pitch,MakerNote_Roll,MakerNote_SpeedX,MakerNote_SpeedY,MakerNote_SpeedZ,MakerNote_Tag_0x000C,MakerNote_Tag_0x000D,MakerNote_Tag_0x000E,MakerNote_Tag_0x0017,MakerNote_Yaw,Model,ModifyDate,RTKFlag,RelativeAltitude,RtkFlag,RtkStdHgt,RtkStdLat,RtkStdLon MakerNote_Tag_0x000C=[(0.5,),(0.5,)] MakerNote_Tag_0x000D=[5,6,5,4] MakerNote_Tag_0x000E=5 MakerNote_Tag_0x0017=1 DJI_0043.JPG quality=rtk_fixed_cm_level rtk_flag=50 positioning=NA accuracy_source=rtk_std_fields h_acc_m=0.0173 v_acc_m=0.02353 rtk_std_lat_m=0.01274 rtk_std_lon_m=0.01165 rtk_std_hgt_m=0.02353 rtk_diff_age=NA surveying_mode=NA gps_dop=NA absolute_altitude=+134.88 relative_altitude=+45.67 flight_pitch=-9.80 flight_roll=+5.80 flight_yaw=-90.90 gimbal_pitch=-90.00 gimbal_roll=+0.00 gimbal_yaw=-89.90 speed_x=+0.00 speed_y=-7.30 speed_z=+0.00 fields_found=AbsoluteAltitude,CamReverse,CreateDate,DewarpFlag,EXIF_BodySerialNumber,EXIF_DateTimeDigitized,EXIF_DateTimeOriginal,FlightPitchDegree,FlightRollDegree,FlightXSpeed,FlightYSpeed,FlightYawDegree,FlightZSpeed,GPSLatitude,GPSLongtitude,GPS_GPSAltitude,GPS_GPSAltitudeRef,GPS_GPSLatitude,GPS_GPSLatitudeRef,GPS_GPSLongitude,GPS_GPSLongitudeRef,GPS_GPSVersionID,GimbalPitchDegree,GimbalRollDegree,GimbalYawDegree,GpsLatitude,Image_Make,Image_Model,Latitude,Make,MakerNote_CameraPitch,MakerNote_CameraRoll,MakerNote_CameraYaw,MakerNote_Make,MakerNote_Pitch,MakerNote_Roll,MakerNote_SpeedX,MakerNote_SpeedY,MakerNote_SpeedZ,MakerNote_Tag_0x000C,MakerNote_Tag_0x000D,MakerNote_Tag_0x000E,MakerNote_Tag_0x0017,MakerNote_Yaw,Model,ModifyDate,RTKFlag,RelativeAltitude,RtkFlag,RtkStdHgt,RtkStdLat,RtkStdLon MakerNote_Tag_0x000C=[(0.5,),(0.5,)] MakerNote_Tag_0x000D=[5,6,5,4] MakerNote_Tag_0x000E=5 MakerNote_Tag_0x0017=1
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Extract GPS and DJI RTK metadata from DJI Phantom 4 RTK photos.Outputs:
1. Position txt: filename latitude longitude altitude
2. RTK txt: one key=value summary line per photoDependencies:pip install exifread
"""import argparse
import os
import re
from pathlib import Pathimport exifreadIMAGE_EXTS = {".jpg",".JPG",".jpeg", ".tif", ".tiff"}XMP_RTK_KEYS = ("Make","Model","CreateDate","ModifyDate","UTCAtExposure","GpsStatus","GPSStatus","AltitudeType","GpsLatitude","GPSLatitude","Latitude","GpsLongitude","GPSLongitude","GPSLongtitude","Longitude","RtkFlag","RTKFlag","RtkStdLon","RtkStdLat","RtkStdHgt","RtkDiffAge","SurveyingMode","NTRIPMountPoint","NTRIPPort","NTRIPHost","GPSXYAccuracy","GPSZAccuracy","GPSPositioningType","GPSPositioningStatus","GPSMapDatum","GPSMeasureMode","GPSDOP","AbsoluteAltitude","RelativeAltitude","FlightPitchDegree","FlightRollDegree","FlightYawDegree","FlightXSpeed","FlightYSpeed","FlightZSpeed","GimbalPitchDegree","GimbalRollDegree","GimbalYawDegree","DewarpFlag","CamReverse","DroneModel","DroneSerialNumber","CameraSerialNumber","LensSerialNumber",
)MAKERNOTE_INTERESTING_TAGS = ("Image Make","Image Model","EXIF BodySerialNumber","EXIF DateTimeOriginal","EXIF DateTimeDigitized","GPS GPSVersionID","GPS GPSLatitudeRef","GPS GPSLatitude","GPS GPSLongitudeRef","GPS GPSLongitude","GPS GPSAltitudeRef","GPS GPSAltitude","GPS GPSImgDirectionRef","GPS GPSImgDirection","GPS GPSMapDatum","GPS GPSStatus","GPS GPSMeasureMode","GPS GPSDOP","MakerNote GPSXYAccuracy","MakerNote GPSZAccuracy","MakerNote RtkFlag","MakerNote RTKFlag","MakerNote PGPSStatus","MakerNote Make","MakerNote SpeedX","MakerNote SpeedY","MakerNote SpeedZ","MakerNote Pitch","MakerNote Yaw","MakerNote Roll","MakerNote CameraPitch","MakerNote CameraYaw","MakerNote CameraRoll","MakerNote Tag 0x000C","MakerNote Tag 0x000D","MakerNote Tag 0x000E","MakerNote Tag 0x0017",
)def ratio_to_float(value):if isinstance(value, (list, tuple)) and value:value = value[0]if hasattr(value, "num") and hasattr(value, "den"):if value.den == 0:return Nonereturn float(value.num) / float(value.den)try:return float(value)except (TypeError, ValueError):return Nonedef dms_to_decimal(dms, ref):deg = ratio_to_float(dms[0])minute = ratio_to_float(dms[1])sec = ratio_to_float(dms[2])if deg is None or minute is None or sec is None:return Nonevalue = deg + minute / 60.0 + sec / 3600.0if str(ref).strip().upper() in {"S", "W"}:value = -valuereturn valuedef parse_number(text):match = re.search(r"[-+]?\d+(?:\.\d+)?", str(text))return float(match.group(0)) if match else Nonedef get_exif_gps(tags):lat = lon = alt = Nonegps_lat = tags.get("GPS GPSLatitude")gps_lat_ref = tags.get("GPS GPSLatitudeRef")gps_lon = tags.get("GPS GPSLongitude")gps_lon_ref = tags.get("GPS GPSLongitudeRef")if gps_lat and gps_lat_ref and gps_lon and gps_lon_ref:lat = dms_to_decimal(gps_lat.values, gps_lat_ref)lon = dms_to_decimal(gps_lon.values, gps_lon_ref)gps_alt = tags.get("GPS GPSAltitude")if gps_alt:alt = ratio_to_float(gps_alt.values)alt_ref = tags.get("GPS GPSAltitudeRef")if alt_ref is not None and parse_number(getattr(alt_ref, "values", alt_ref)) == 1:alt = -altreturn lat, lon, altdef read_text_head(path, size=512 * 1024):with open(path, "rb") as f:data = f.read(size)return data.decode("utf-8", errors="ignore")def extract_xmp_values(path):text = read_text_head(path)values = {}for key in XMP_RTK_KEYS:patterns = (rf"(?:drone-dji:)?{re.escape(key)}\s*=\s*['\"]([^'\"]*)['\"]",rf"<(?:[^>:]+:)?{re.escape(key)}[^>]*>([^<]*)</(?:[^>:]+:)?{re.escape(key)}>",)for pattern in patterns:match = re.search(pattern, text, flags=re.IGNORECASE)if match:values[key] = match.group(1).strip()breakreturn valuesdef xmp_altitude(xmp):for key in ("AbsoluteAltitude", "RelativeAltitude"):if key in xmp:value = parse_number(xmp[key])if value is not None:return valuereturn Nonedef tag_value_to_text(value):text = str(value)return re.sub(r"\s+", "", text)def extract_makernote_values(tags):values = {}for key in MAKERNOTE_INTERESTING_TAGS:if key in tags:values[key.replace("MakerNote ", "MakerNote_").replace(" ", "_")] = tag_value_to_text(tags[key])for key, value in tags.items():lowered = key.lower()if any(word in lowered for word in ("rtk", "accuracy", "hacc", "vacc", "pgps")):values[key.replace(" ", "_")] = tag_value_to_text(value)return valuesdef first_present(mapping, keys):for key in keys:if key in mapping and mapping[key] not in ("", None):return mapping[key]return Nonedef normalize_rtk_flag(value):if value is None:return Nonenumber = parse_number(value)if number is not None:return int(number)text = str(value).strip().lower()if "fix" in text or "fixed" in text:return 50if "float" in text:return 34return Nonedef classify_quality(rtk):rtk_flag = str(first_present(rtk, ("RtkFlag", "RTKFlag", "MakerNote_RtkFlag", "MakerNote_RTKFlag")) or "").lower()positioning = str(first_present(rtk, ("GPSPositioningType", "GPSPositioningStatus", "GPSStatus", "MakerNote_PGPSStatus")) or "").lower()text = f"{rtk_flag} {positioning}"flag = normalize_rtk_flag(rtk_flag)if flag == 50:return "rtk_fixed_cm_level"if flag == 34:return "rtk_float_dm_level"if flag == 16:return "single_meter_level"if flag == 0:return "no_position"if any(token in text for token in ("fix", "fixed")):return "rtk_fixed_cm_level"if "float" in text:return "rtk_float_dm_level"if rtk:return "metadata_present_unclassified"return "no_rtk_metadata_found"def estimate_accuracy(rtk):h_acc = first_present(rtk, ("GPSXYAccuracy", "HAcc", "HorizontalAccuracy", "MakerNote_HAcc"))v_acc = first_present(rtk, ("GPSZAccuracy", "VAcc", "VerticalAccuracy", "MakerNote_VAcc"))if h_acc is not None or v_acc is not None:return h_acc, v_acc, "gps_accuracy_fields"std_lat = parse_number(first_present(rtk, ("RtkStdLat", "RTKStdLat")))std_lon = parse_number(first_present(rtk, ("RtkStdLon", "RTKStdLon")))std_hgt = first_present(rtk, ("RtkStdHgt", "RTKStdHgt"))if std_lat is not None and std_lon is not None:horizontal = (std_lat * std_lat + std_lon * std_lon) ** 0.5return format_float(horizontal, precision=4), std_hgt, "rtk_std_fields"return None, None, "not_available"def format_float(value, precision=10):if value is None:return "NA"return f"{value:.{precision}f}".rstrip("0").rstrip(".")def safe_value(value):if value is None or value == "":return "NA"return str(value).replace(" ", "_").replace("\t", "_")def extract_one(path):with open(path, "rb") as f:tags = exifread.process_file(f, details=True)lat, lon, alt = get_exif_gps(tags)xmp = extract_xmp_values(path)if alt is None:alt = xmp_altitude(xmp)rtk = {}rtk.update(xmp)rtk.update(extract_makernote_values(tags))h_acc, v_acc, accuracy_source = estimate_accuracy(rtk)rtk_flag = first_present(rtk, ("RtkFlag", "RTKFlag", "MakerNote_RtkFlag", "MakerNote_RTKFlag"))std_lat = first_present(rtk, ("RtkStdLat", "RTKStdLat"))std_lon = first_present(rtk, ("RtkStdLon", "RTKStdLon"))std_hgt = first_present(rtk, ("RtkStdHgt", "RTKStdHgt"))positioning = first_present(rtk,("GPSPositioningType", "GPSPositioningStatus", "GpsStatus", "GPSStatus", "GPS_GPSStatus", "MakerNote_PGPSStatus"),)rtk_summary = {"quality": classify_quality(rtk),"rtk_flag": rtk_flag,"positioning": positioning,"accuracy_source": accuracy_source,"h_acc_m": h_acc,"v_acc_m": v_acc,"rtk_std_lat_m": std_lat,"rtk_std_lon_m": std_lon,"rtk_std_hgt_m": std_hgt,"rtk_diff_age": first_present(rtk, ("RtkDiffAge", "RTKDiffAge")),"surveying_mode": first_present(rtk, ("SurveyingMode",)),"gps_dop": first_present(rtk, ("GPSDOP", "GPS_GPSDOP")),"absolute_altitude": first_present(rtk, ("AbsoluteAltitude",)),"relative_altitude": first_present(rtk, ("RelativeAltitude",)),"flight_pitch": first_present(rtk, ("FlightPitchDegree", "MakerNote_Pitch")),"flight_roll": first_present(rtk, ("FlightRollDegree", "MakerNote_Roll")),"flight_yaw": first_present(rtk, ("FlightYawDegree", "MakerNote_Yaw")),"gimbal_pitch": first_present(rtk, ("GimbalPitchDegree", "MakerNote_CameraPitch")),"gimbal_roll": first_present(rtk, ("GimbalRollDegree", "MakerNote_CameraRoll")),"gimbal_yaw": first_present(rtk, ("GimbalYawDegree", "MakerNote_CameraYaw")),"speed_x": first_present(rtk, ("FlightXSpeed", "MakerNote_SpeedX")),"speed_y": first_present(rtk, ("FlightYSpeed", "MakerNote_SpeedY")),"speed_z": first_present(rtk, ("FlightZSpeed", "MakerNote_SpeedZ")),"fields_found": ",".join(sorted(rtk.keys())) if rtk else "none",}# Keep raw interesting values so unknown DJI MakerNote fields are not lost.for key in sorted(rtk):if key.startswith("MakerNote_Tag_") or key.startswith("MakerNote_PGPSStatus"):rtk_summary[key] = rtk[key]return lat, lon, alt, rtk_summarydef iter_images(folder, recursive=False):base = Path(folder)pattern = "**/*" if recursive else "*"for path in sorted(base.glob(pattern), key=lambda p: p.name.lower()):if path.is_file() and path.suffix.lower() in IMAGE_EXTS:yield pathdef write_outputs(input_dir, position_txt, rtk_txt, recursive=False):input_path = Path(input_dir)if not input_path.is_dir():raise FileNotFoundError(f"input folder does not exist: {input_dir}")position_path = Path(position_txt)rtk_path = Path(rtk_txt)position_path.parent.mkdir(parents=True, exist_ok=True)rtk_path.parent.mkdir(parents=True, exist_ok=True)count = 0gps_ok = 0with position_path.open("w", encoding="utf-8", newline="\n", buffering=1) as pos_f, rtk_path.open("w", encoding="utf-8", newline="\n", buffering=1) as rtk_f:for image_path in iter_images(input_path, recursive=recursive):count += 1try:lat, lon, alt, rtk_summary = extract_one(image_path)if lat is not None and lon is not None:gps_ok += 1pos_f.write(f"{image_path.name} {format_float(lat)} {format_float(lon)} {format_float(alt, precision=4)}\n")rtk_items = " ".join(f"{key}={safe_value(value)}" for key, value in rtk_summary.items())rtk_f.write(f"{image_path.name} {rtk_items}\n")except Exception as exc:pos_f.write(f"{image_path.name} NA NA NA\n")rtk_f.write(f"{image_path.name} quality=read_error error={safe_value(exc)}\n")return count, gps_ok, position_path, rtk_pathdef resolve_scene_paths(target_path, position_txt=None, rtk_txt=None):"""Accept a scene root, slam_config, images, or images_source path."""target = Path(target_path)name = target.name.lower()if name == "slam_config":scene_dir = target.parentoutput_dir = targetimage_candidates = (scene_dir / "images_source", scene_dir / "images")elif name in {"images", "images_source"}:scene_dir = target.parentoutput_dir = scene_dir / "slam_config"image_candidates = (target,)else:scene_dir = targetoutput_dir = scene_dir / "slam_config"image_candidates = (scene_dir / "images_source", scene_dir / "images")input_dir = next((path for path in image_candidates if path.is_dir()), image_candidates[0])position_path = Path(position_txt) if position_txt else output_dir / "gnss.txt"rtk_path = Path(rtk_txt) if rtk_txt else output_dir / "rtkinfo.txt"return input_dir, position_path, rtk_pathdef main():default_target_path = r"F:\2数据\City4_outskirts2\map_cloudy_0303_15pm_135m\slam_config"parser = argparse.ArgumentParser(description="Extract latitude, longitude, altitude, and DJI RTK precision metadata from a folder of photos.")parser.add_argument("input_dir",nargs="?",default=default_target_path,help="Photo folder, scene folder, or slam_config folder",)parser.add_argument("position_txt",nargs="?",default=None,help="Output txt path for: filename latitude longitude altitude",)parser.add_argument("rtk_txt",nargs="?",default=None,help="Output txt path for RTK precision summary",)parser.add_argument("-r", "--recursive", action="store_true", help="Search images recursively")args = parser.parse_args()input_dir, position_txt, rtk_txt = resolve_scene_paths(args.input_dir, args.position_txt, args.rtk_txt)count, gps_ok, position_path, rtk_path = write_outputs(input_dir,position_txt,rtk_txt,recursive=args.recursive,)print(f"processed={count} gps_ok={gps_ok}")print(f"position_txt={position_path}")print(f"rtk_txt={rtk_path}")if __name__ == "__main__":main()'''
你现在这批 images_source 解析出来的数据可以这样解读。以第一行为例:DJI_0002.JPG quality=rtk_fixed_cm_level rtk_flag=50 accuracy_source=rtk_std_fields h_acc_m=0.0175 v_acc_m=0.02378rtk_std_lat_m=0.01280 rtk_std_lon_m=0.01189 rtk_std_hgt_m=0.02378含义:DJI_0002.JPG照片名。quality=rtk_fixed_cm_levelrtk_flag=50这张照片是 RTK Fixed 固定解,属于厘米级定位。RtkFlag=50 是最关键字段。accuracy_source=rtk_std_fields精度来自 DJI XMP 里的 RTK 标准差字段,不是脚本猜的。rtk_std_lat_m=0.01280rtk_std_lon_m=0.01189rtk_std_hgt_m=0.02378分别表示纬度方向、经度方向、高程方向的标准差,单位是米。h_acc_m=0.0175v_acc_m=0.02378脚本计算后的精度:水平精度约 0.0175 m = 1.75 cm高程精度约 0.02378 m = 2.38 cm所以这张图可以写成:DJI_0002.JPG:RTK Fixed,水平精度约 1.75 cm,高程精度约 2.38 cm。'''