python 1 大疆照片rtk gps 信息提取 定位精度
https://dl.djicdn.com/downloads/phantom_4_rtk/20190530/Phantom_4_RTK_SDK_Quick_Start_Guide_v1.0_CHS.pdf



1要用原图

2 提取保存路径

input_dir=D:\0ubuntu20\1slam\数据\2RTK\City4_outskirts2\map_cloudy_0303_15pm_135m\images total_images=319 gps_ok=319 gps_missing=0 [rtk_flag_frequency] rtk_flag=50 count=319 percent=100% [quality_frequency] quality=rtk_fixed_cm_level count=319 percent=100% [rtk_accuracy_stats] rtk_std_lat_m count=319 min=0.01142 max=0.01389 mean=0.012648 median=0.01275 unit=m rtk_std_lon_m count=319 min=0.01019 max=0.01269 mean=0.011466 median=0.01161 unit=m rtk_std_hgt_m count=319 min=0.02259 max=0.0262 mean=0.024064 median=0.0241 unit=m h_acc_m count=319 min=0.0154 max=0.0188 mean=0.017075 median=0.0173 unit=m v_acc_m count=319 min=0.02259 max=0.0262 mean=0.024064 median=0.0241 unit=m accuracy_3d_m count=319 min=0.02734 max=0.031634 mean=0.029508 median=0.029553 unit=m [speed_stats] speed_x_mps count=319 min=-13 max=11.2 mean=-0.03605 median=0 unit=m/s speed_y_mps count=319 min=-10.8 max=11.1 mean=-0.019122 median=0 unit=m/s speed_z_mps count=319 min=0 max=0.1 mean=0.001254 median=0 unit=m/s speed_3d_mps count=319 min=0.1 max=13 mean=8.097805 median=7.8 unit=m/s [altitude_stats] altitude_m count=319 min=134.61 max=135.17 mean=134.841442 median=134.84 unit=m
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 photo
Dependencies:
pip install exifread
"""
import argparse
import math
import re
from collections import Counter
from pathlib import Path
from statistics import mean, median
import exifread
IMAGE_EXTS = {".jpg",".JPG",".jpeg", ".tif", ".tiff"}
# =======================
# Manual configuration
# =======================
# Run directly:
# python tool0_extract_dji_rtk_info.py
DEFAULT_INPUT_DIR = r"D:\0ubuntu20\1slam\数据\2RTK\City4_outskirts2\map_cloudy_0303_15pm_135m\images"
DEFAULT_OUTPUT_DIR = r"D:\0ubuntu20\1slam\数据\2RTK\City4_outskirts2\map_cloudy_0303_15pm_135m\slam_config"
DEFAULT_POSITION_TXT = str(Path(DEFAULT_OUTPUT_DIR) / "gnss.txt")
DEFAULT_RTK_TXT = str(Path(DEFAULT_OUTPUT_DIR) / "rtkinfo.txt")
DEFAULT_SUMMARY_TXT = str(Path(DEFAULT_OUTPUT_DIR) / "rtk_summary.txt")
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 None
return float(value.num) / float(value.den)
try:
return float(value)
except (TypeError, ValueError):
return None
def 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 None
value = deg + minute / 60.0 + sec / 3600.0
if str(ref).strip().upper() in {"S", "W"}:
value = -value
return value
def parse_number(text):
match = re.search(r"[-+]?\d+(?:\.\d+)?", str(text))
return float(match.group(0)) if match else None
def get_exif_gps(tags):
lat = lon = alt = None
gps_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 = -alt
return lat, lon, alt
def 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()
break
return values
def xmp_altitude(xmp):
for key in ("AbsoluteAltitude", "RelativeAltitude"):
if key in xmp:
value = parse_number(xmp[key])
if value is not None:
return value
return None
def xmp_coordinate(xmp, keys):
for key in keys:
if key in xmp:
value = parse_number(xmp[key])
if value is not None:
return value
return None
def 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 values
def first_present(mapping, keys):
for key in keys:
if key in mapping and mapping[key] not in ("", None):
return mapping[key]
return None
def normalize_rtk_flag(value):
if value is None:
return None
number = 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 50
if "float" in text:
return 34
return None
def 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.5
return 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 to_float(value):
if value is None or value == "NA":
return None
return parse_number(value)
def add_number(bucket, key, value):
number = to_float(value)
if number is not None:
bucket.setdefault(key, []).append(number)
def number_stats(values):
if not values:
return None
return {
"count": len(values),
"min": min(values),
"max": max(values),
"mean": mean(values),
"median": median(values),
}
def write_stat_line(file_obj, name, values, unit="m"):
stats = number_stats(values)
if stats is None:
file_obj.write(f"{name} count=0 min=NA max=NA mean=NA median=NA unit={unit}\n")
return
file_obj.write(
f"{name} count={stats['count']} "
f"min={format_float(stats['min'], 6)} "
f"max={format_float(stats['max'], 6)} "
f"mean={format_float(stats['mean'], 6)} "
f"median={format_float(stats['median'], 6)} "
f"unit={unit}\n"
)
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 lat is None:
lat = xmp_coordinate(xmp, ("GpsLatitude", "GPSLatitude", "Latitude"))
if lon is None:
lon = xmp_coordinate(xmp, ("GpsLongitude", "GPSLongitude", "GPSLongtitude", "Longitude"))
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_summary
def 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 path
def write_outputs(input_dir, position_txt, rtk_txt, summary_txt=None, 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)
summary_path = Path(summary_txt) if summary_txt else rtk_path.with_name("rtk_summary.txt")
position_path.parent.mkdir(parents=True, exist_ok=True)
rtk_path.parent.mkdir(parents=True, exist_ok=True)
summary_path.parent.mkdir(parents=True, exist_ok=True)
count = 0
gps_ok = 0
stats = {}
flag_counter = Counter()
quality_counter = Counter()
with 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 += 1
try:
lat, lon, alt, rtk_summary = extract_one(image_path)
if lat is not None and lon is not None:
gps_ok += 1
pos_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")
rtk_flag = safe_value(rtk_summary.get("rtk_flag"))
flag_counter[rtk_flag] += 1
quality_counter[safe_value(rtk_summary.get("quality"))] += 1
add_number(stats, "rtk_std_lat_m", rtk_summary.get("rtk_std_lat_m"))
add_number(stats, "rtk_std_lon_m", rtk_summary.get("rtk_std_lon_m"))
add_number(stats, "rtk_std_hgt_m", rtk_summary.get("rtk_std_hgt_m"))
add_number(stats, "h_acc_m", rtk_summary.get("h_acc_m"))
add_number(stats, "v_acc_m", rtk_summary.get("v_acc_m"))
add_number(stats, "speed_x_mps", rtk_summary.get("speed_x"))
add_number(stats, "speed_y_mps", rtk_summary.get("speed_y"))
add_number(stats, "speed_z_mps", rtk_summary.get("speed_z"))
add_number(stats, "altitude_m", alt)
h_acc = to_float(rtk_summary.get("h_acc_m"))
v_acc = to_float(rtk_summary.get("v_acc_m"))
if h_acc is not None and v_acc is not None:
stats.setdefault("accuracy_3d_m", []).append(math.sqrt(h_acc * h_acc + v_acc * v_acc))
speed_x = to_float(rtk_summary.get("speed_x"))
speed_y = to_float(rtk_summary.get("speed_y"))
speed_z = to_float(rtk_summary.get("speed_z"))
if speed_x is not None and speed_y is not None and speed_z is not None:
stats.setdefault("speed_3d_mps", []).append(
math.sqrt(speed_x * speed_x + speed_y * speed_y + speed_z * speed_z)
)
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")
quality_counter["read_error"] += 1
with summary_path.open("w", encoding="utf-8", newline="\n") as summary_f:
summary_f.write(f"input_dir={input_path}\n")
summary_f.write(f"total_images={count}\n")
summary_f.write(f"gps_ok={gps_ok}\n")
summary_f.write(f"gps_missing={count - gps_ok}\n")
summary_f.write("\n")
summary_f.write("[rtk_flag_frequency]\n")
for flag, flag_count in flag_counter.most_common():
percent = flag_count / count * 100 if count else 0.0
summary_f.write(f"rtk_flag={flag} count={flag_count} percent={format_float(percent, 4)}%\n")
summary_f.write("\n")
summary_f.write("[quality_frequency]\n")
for quality, quality_count in quality_counter.most_common():
percent = quality_count / count * 100 if count else 0.0
summary_f.write(f"quality={quality} count={quality_count} percent={format_float(percent, 4)}%\n")
summary_f.write("\n")
summary_f.write("[rtk_accuracy_stats]\n")
for key in ("rtk_std_lat_m", "rtk_std_lon_m", "rtk_std_hgt_m", "h_acc_m", "v_acc_m", "accuracy_3d_m"):
write_stat_line(summary_f, key, stats.get(key, []), unit="m")
summary_f.write("\n")
summary_f.write("[speed_stats]\n")
for key in ("speed_x_mps", "speed_y_mps", "speed_z_mps", "speed_3d_mps"):
write_stat_line(summary_f, key, stats.get(key, []), unit="m/s")
summary_f.write("\n")
summary_f.write("[altitude_stats]\n")
write_stat_line(summary_f, "altitude_m", stats.get("altitude_m", []), unit="m")
return count, gps_ok, position_path, rtk_path, summary_path
def resolve_scene_paths(target_path, position_txt=None, rtk_txt=None, summary_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.parent
output_dir = target
image_candidates = (scene_dir / "images_source", scene_dir / "images")
elif name in {"images", "images_source"}:
scene_dir = target.parent
output_dir = scene_dir / "slam_config"
image_candidates = (target,)
else:
scene_dir = target
output_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"
summary_path = Path(summary_txt) if summary_txt else output_dir / "rtk_summary.txt"
return input_dir, position_path, rtk_path, summary_path
def main():
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_INPUT_DIR,
help="Photo folder, scene folder, or slam_config folder",
)
parser.add_argument(
"position_txt",
nargs="?",
default=DEFAULT_POSITION_TXT,
help="Output txt path for: filename latitude longitude altitude",
)
parser.add_argument(
"rtk_txt",
nargs="?",
default=DEFAULT_RTK_TXT,
help="Output txt path for RTK precision summary",
)
parser.add_argument(
"--summary-txt",
default=DEFAULT_SUMMARY_TXT,
help="Output txt path for overall RTK statistics",
)
parser.add_argument("-r", "--recursive", action="store_true", help="Search images recursively")
args = parser.parse_args()
input_dir, position_txt, rtk_txt, summary_txt = resolve_scene_paths(
args.input_dir,
args.position_txt,
args.rtk_txt,
args.summary_txt,
)
count, gps_ok, position_path, rtk_path, summary_path = write_outputs(
input_dir,
position_txt,
rtk_txt,
summary_txt,
recursive=args.recursive,
)
print(f"processed={count} gps_ok={gps_ok}")
print(f"position_txt={position_path}")
print(f"rtk_txt={rtk_path}")
print(f"summary_txt={summary_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.02378
rtk_std_lat_m=0.01280 rtk_std_lon_m=0.01189 rtk_std_hgt_m=0.02378
含义:
DJI_0002.JPG
照片名。
quality=rtk_fixed_cm_level
rtk_flag=50
这张照片是 RTK Fixed 固定解,属于厘米级定位。RtkFlag=50 是最关键字段。
accuracy_source=rtk_std_fields
精度来自 DJI XMP 里的 RTK 标准差字段,不是脚本猜的。
rtk_std_lat_m=0.01280
rtk_std_lon_m=0.01189
rtk_std_hgt_m=0.02378
分别表示纬度方向、经度方向、高程方向的标准差,单位是米。
h_acc_m=0.0175
v_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。
'''
浙公网安备 33010602011771号