@ 20240818 & lth
编写时间:2025-08-15
更新时间:2025-08-15
选项:iTwinModelerCapture(原cc) 、Webodm(开源)

iTwinModelerCapture
背景
Python SDK for Bentley Systems Reality Analysis (RDA), Reality Modeling (CC) and Reality Conversion (RC) Services. It has been created and tested with Python 3.9
实操路线
选择了iTwinModelerCapture和Webodm进行比对测试
Webodm
背景
开源的无人机二维、三维、视频数据建模框架
实操路线
将航飞的pos写入图片
pos格式
Name,Lat,Lng,Alt,Roll,Pitch,Yaw
DSC00001.jpg,23.035060400,114.299812500,816.9800, -2.85, 1.17, 343.43
DSC00002.jpg,23.035826100,114.299711500,816.3600, 1.90, 1.66, 346.97
DSC00003.jpg,23.036593800,114.299644900,815.8100, 1.63, 1.84, 351.47
# -*- coding: utf-8 -*-
# 主要是将pos.txt的经纬度和高程写入图片
import os
import csv
from PIL import Image
import piexif
class ExifWriter:
"""
将 txt/CSV 中的 GPS、姿态信息写入 JPG/TIF 影像的 EXIF。
输入格式:Name,Lat,Lng,Alt,Roll,Pitch,Yaw
"""
def __init__(self, txt_path: str, img_root: str, save_root: str):
"""
:param txt_path: 包含 Name,Lat,Lng,... 的 txt 文件路径
:param img_root: 原影像目录
:param save_root: 写入后的影像保存目录(自动创建)
"""
self.txt_path = txt_path
self.img_root = img_root
self.save_root = save_root
os.makedirs(self.save_root, exist_ok=True)
# ------------------ 公共 API ------------------
def run(self):
"""主入口:遍历 txt → 逐图写入"""
for row in self._read_txt(self.txt_path):
name, lat, lng, alt, roll, pitch, yaw = row
img_name = name.strip()
img_path = os.path.join(self.img_root, img_name)
out_path = os.path.join(self.save_root, img_name)
if not os.path.isfile(img_path):
print(f"⚠️ 找不到文件: {img_path}")
continue
gps_dict = self._build_gps_dict(float(lat), float(lng), float(alt))
self._write_exif(img_path, out_path, gps_dict)
print(f"✅ {img_name} 已写入 EXIF")
def read_exif_and_write_txt(self,img_root):
"""读取指定路径下所有照片的经纬度和高程信息,写入到图片文件夹中的 txt 文件"""
txt_file_path = os.path.join(img_root, "exif_info.txt")
with open(txt_file_path, "w", newline='') as txt_file:
writer = csv.writer(txt_file)
writer.writerow(["Name", "Lat", "Lng", "Alt"])
for img_name in os.listdir(img_root):
if img_name.lower().endswith(('.jpg', '.jpeg', '.png')):
img_path = os.path.join(img_root, img_name)
lat, lng, alt = self._read_exif(img_path)
if lat is not None and lng is not None and alt is not None:
writer.writerow([img_name, lat, lng, alt])
print(f"✅ 所有照片的经纬度和高程信息已写入到 {txt_file_path}")
# ------------------ 内部工具 ------------------
@staticmethod
def _read_txt(txt_path: str):
"""读取 txt/CSV,跳过表头"""
with open(txt_path, newline='') as f:
reader = csv.reader(f, skipinitialspace=True)
next(reader, None) # 跳过标题
for row in reader:
if len(row) < 7:
continue
yield row
@staticmethod
def _format_latlng(latlng: float):
degree = int(latlng)
minute = int((abs(latlng) - abs(degree)) * 60)
second_float = (((abs(latlng) - abs(degree)) * 60) - minute) * 60
# 截断小数部分,保留6位小数(对应1000000分母)
second = int(second_float * 1000000) # 直接截断,不四舍五入
return ((degree, 1), (minute, 1), (second, 1000000))
@staticmethod
def _build_gps_dict(lat: float, lng: float, alt: float):
"""构造 EXIF 所需 GPS 字典"""
return {
"lat": ExifWriter._format_latlng(lat),
"lng": ExifWriter._format_latlng(lng),
"alt": (int(round(alt * 100)), 100),
"lat_ref": "N" if lat >= 0 else "S",
"lng_ref": "E" if lng >= 0 else "W"
}
@staticmethod
def _write_exif(src: str, dst: str, gps_dict: dict):
"""把 GPS 信息写进图片 EXIF"""
img = Image.open(src)
exif_dict = piexif.load(img.info.get("exif", b""))
# 覆盖或新建 GPS IFD
exif_dict["GPS"] = {
piexif.GPSIFD.GPSLatitudeRef: gps_dict["lat_ref"],
piexif.GPSIFD.GPSLatitude: gps_dict["lat"],
piexif.GPSIFD.GPSLongitudeRef: gps_dict["lng_ref"],
piexif.GPSIFD.GPSLongitude: gps_dict["lng"],
piexif.GPSIFD.GPSAltitudeRef: 0, # 0 表示海平面以上
piexif.GPSIFD.GPSAltitude: gps_dict["alt"]
}
exif_bytes = piexif.dump(exif_dict)
img.save(dst, "JPEG", exif=exif_bytes)
@staticmethod
def _read_exif(img_path: str):
"""读取图片的经纬度和高程信息"""
try:
img = Image.open(img_path)
exif_dict = piexif.load(img.info.get("exif", b""))
gps_dict = exif_dict.get("GPS", {})
lat = ExifWriter._convert_to_decimal(gps_dict.get(piexif.GPSIFD.GPSLatitude, None), gps_dict.get(piexif.GPSIFD.GPSLatitudeRef, None))
lng = ExifWriter._convert_to_decimal(gps_dict.get(piexif.GPSIFD.GPSLongitude, None), gps_dict.get(piexif.GPSIFD.GPSLongitudeRef, None))
alt = gps_dict.get(piexif.GPSIFD.GPSAltitude, None)
if alt is not None:
alt = alt[0] / alt[1] # 将分数转换为十进制
return lat, lng, alt
except Exception as e:
print(f"⚠️ 读取 {img_path} 的 EXIF 信息时出错: {e}")
return None, None, None
@staticmethod
def _convert_to_decimal(value, ref):
if value is None or ref is None:
return None
d, m, s = value
decimal_value = d[0]/d[1] + (m[0]/m[1])/60 + (s[0]/s[1])/3600
if ref in ["S", "W"]:
decimal_value = -decimal_value
return round(decimal_value, 10) # 保留10位小数,避免计算误差
# ------------------ 一键调用 ------------------
if __name__ == "__main__":
writer = ExifWriter(
txt_path=r'E:\work\1\20250714-04h-44m-18s.txt',
img_root=r'E:\work\1\pictures',
save_root=r'E:\work\1\pictures1'
)
writer.run()
writer.read_exif_and_write_txt(r'E:\work\1\pictures1')
实测结果
webodm速度较慢且质量不够理想