航测建模篇-正射影像生产-持续连载中

技术栈

复制代码
编写时间:2025-07-10
更新时间:2025-07-30
选项:iTwinModelerCapture(原cc) 、Webodm(开源)

iTwinModelerCapture

背景

复制代码

实操路线

复制代码

Webodm

背景

复制代码

实操路线

将航飞的pos写入图片

复制代码
# -*- coding: utf-8 -*-
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')