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