当前位置: 首页 > news >正文

如何批量提取图片中GPS经纬度信息,保存到表格

一、应用场景

在地理信息系统(GIS)、摄影测量、环境监测、考古研究等领域,常常需要从大量图片中提取 GPS 经纬度信息并进行分析。例如:

  • 地理标记照片管理:摄影师需要整理带有位置信息的照片
  • 环境监测:通过无人机拍摄的照片定位污染点或植被变化区域
  • 考古研究:记录文物发现位置的照片
  • 旅行日志:根据旅行照片生成行程路线图

二、界面设计

设计一个简洁易用的图形界面,包含以下元素:

  1. 文件选择区域:支持拖放或点击选择图片文件夹
  2. 处理进度显示:显示已处理图片数量和总数量
  3. 结果预览表格:实时显示提取的 GPS 信息
  4. 导出功能:将结果保存为 CSV 或 Excel 表格
  5. 日志区域:显示处理过程中的错误或提示信息

下面是一个实现该功能的完整代码:

import os
import tkinter as tk
from tkinter import filedialog, ttk, messagebox
import pandas as pd
from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS
import threading
import queue
from datetime import datetimeclass GPSExtractorApp:def __init__(self, root):self.root = rootself.root.title("图片GPS信息提取工具")self.root.geometry("900x600")self.root.minsize(800, 500)# 设置中文字体self.style = ttk.Style()self.style.configure("TLabel", font=("SimHei", 10))self.style.configure("TButton", font=("SimHei", 10))self.style.configure("Treeview", font=("SimHei", 10))# 创建队列用于线程间通信self.queue = queue.Queue()# 创建界面self.create_widgets()def create_widgets(self):# 主框架main_frame = ttk.Frame(self.root, padding=10)main_frame.pack(fill=tk.BOTH, expand=True)# 文件选择区域file_frame = ttk.LabelFrame(main_frame, text="选择图片文件夹", padding=10)file_frame.pack(fill=tk.X, pady=(0, 10))self.folder_path = tk.StringVar()folder_entry = ttk.Entry(file_frame, textvariable=self.folder_path, width=50)folder_entry.pack(side=tk.LEFT, padx=(0, 10), fill=tk.X, expand=True)browse_btn = ttk.Button(file_frame, text="浏览...", command=self.browse_folder)browse_btn.pack(side=tk.LEFT)process_btn = ttk.Button(file_frame, text="开始处理", command=self.start_processing)process_btn.pack(side=tk.RIGHT)# 进度条区域progress_frame = ttk.Frame(main_frame, padding=(0, 5))progress_frame.pack(fill=tk.X)self.progress_var = tk.DoubleVar()self.progress_label = ttk.Label(progress_frame, text="准备就绪")self.progress_label.pack(side=tk.LEFT)self.progress_bar = ttk.Progressbar(progress_frame, variable=self.progress_var, maximum=100)self.progress_bar.pack(side=tk.RIGHT, fill=tk.X, expand=True, padx=(10, 0))# 结果表格区域result_frame = ttk.LabelFrame(main_frame, text="提取结果", padding=10)result_frame.pack(fill=tk.BOTH, expand=True, pady=(10, 0))# 创建表格columns = ("filename", "latitude", "longitude", "altitude", "datetime")self.tree = ttk.Treeview(result_frame, columns=columns, show="headings")# 设置列标题self.tree.heading("filename", text="文件名")self.tree.heading("latitude", text="纬度")self.tree.heading("longitude", text="经度")self.tree.heading("altitude", text="海拔")self.tree.heading("datetime", text="拍摄时间")# 设置列宽self.tree.column("filename", width=200)self.tree.column("latitude", width=120)self.tree.column("longitude", width=120)self.tree.column("altitude", width=80)self.tree.column("datetime", width=150)self.tree.pack(fill=tk.BOTH, expand=True)# 导出按钮export_frame = ttk.Frame(main_frame, padding=(0, 10))export_frame.pack(fill=tk.X)export_btn = ttk.Button(export_frame, text="导出到CSV", command=self.export_to_csv)export_btn.pack(side=tk.RIGHT)# 日志区域log_frame = ttk.LabelFrame(main_frame, text="处理日志", padding=10)log_frame.pack(fill=tk.BOTH, expand=True, pady=(10, 0))self.log_text = tk.Text(log_frame, height=5, state=tk.DISABLED)self.log_text.pack(fill=tk.BOTH, expand=True)scrollbar = ttk.Scrollbar(self.log_text, command=self.log_text.yview)scrollbar.pack(side=tk.RIGHT, fill=tk.Y)self.log_text.config(yscrollcommand=scrollbar.set)def browse_folder(self):folder_selected = filedialog.askdirectory()if folder_selected:self.folder_path.set(folder_selected)self.log("已选择文件夹: " + folder_selected)def log(self, message):self.log_text.config(state=tk.NORMAL)self.log_text.insert(tk.END, message + "\n")self.log_text.see(tk.END)self.log_text.config(state=tk.DISABLED)def start_processing(self):folder = self.folder_path.get()if not folder or not os.path.isdir(folder):messagebox.showerror("错误", "请选择有效的文件夹")return# 清空表格和日志for item in self.tree.get_children():self.tree.delete(item)self.log_text.config(state=tk.NORMAL)self.log_text.delete(1.0, tk.END)self.log_text.config(state=tk.DISABLED)# 启动处理线程self.processing_thread = threading.Thread(target=self.process_images, args=(folder,))self.processing_thread.daemon = Trueself.processing_thread.start()# 开始轮询队列self.root.after(100, self.poll_queue)def process_images(self, folder):# 获取所有图片文件image_extensions = ['.jpg', '.jpeg', '.png', '.tiff']image_files = []for root, dirs, files in os.walk(folder):for file in files:if os.path.splitext(file)[1].lower() in image_extensions:image_files.append(os.path.join(root, file))total = len(image_files)if total == 0:self.queue.put(("log", "未找到图片文件"))self.queue.put(("progress", 0, "准备就绪"))returnself.queue.put(("log", f"找到 {total} 个图片文件"))self.queue.put(("progress", 0, f"开始处理 {total} 个文件"))results = []# 处理每个图片文件for i, image_path in enumerate(image_files):try:gps_info = self.get_gps_info(image_path)if gps_info:filename = os.path.basename(image_path)results.append({"filename": filename,"latitude": gps_info["latitude"],"longitude": gps_info["longitude"],"altitude": gps_info.get("altitude", "N/A"),"datetime": gps_info.get("datetime", "N/A")})self.queue.put(("add_item", filename, gps_info["latitude"], gps_info["longitude"], gps_info.get("altitude", "N/A"),gps_info.get("datetime", "N/A")))else:self.queue.put(("log", f"警告: {os.path.basename(image_path)} 不包含GPS信息"))except Exception as e:self.queue.put(("log", f"错误: 处理 {os.path.basename(image_path)} 时出错 - {str(e)}"))progress = (i + 1) / total * 100self.queue.put(("progress", progress, f"已处理 {i+1}/{total}"))# 保存结果到实例变量self.results = resultsself.queue.put(("log", f"处理完成,共提取 {len(results)} 条GPS信息"))self.queue.put(("progress", 100, "处理完成"))def get_gps_info(self, image_path):try:with Image.open(image_path) as img:exif_data = img._getexif()if not exif_data:return None# 解析EXIF数据exif = {}for tag, value in exif_data.items():tag_name = TAGS.get(tag, tag)exif[tag_name] = value# 检查是否有GPS信息if 'GPSInfo' not in exif:return None# 解析GPS信息gps_info = {}for gps_tag, value in exif['GPSInfo'].items():gps_tag_name = GPSTAGS.get(gps_tag, gps_tag)gps_info[gps_tag_name] = value# 提取经纬度if ('GPSLatitude' in gps_info and 'GPSLatitudeRef' in gps_info and'GPSLongitude' in gps_info and 'GPSLongitudeRef' in gps_info):# 转换经纬度格式lat = self.convert_to_degrees(gps_info['GPSLatitude'])if gps_info['GPSLatitudeRef'] == 'S':lat = -latlon = self.convert_to_degrees(gps_info['GPSLongitude'])if gps_info['GPSLongitudeRef'] == 'W':lon = -lonresult = {"latitude": lat,"longitude": lon}# 提取海拔if 'GPSAltitude' in gps_info:result["altitude"] = gps_info['GPSAltitude']# 提取拍摄时间if 'DateTimeOriginal' in exif:result["datetime"] = exif['DateTimeOriginal']return resultreturn Noneexcept Exception as e:raise Exception(f"无法获取GPS信息: {str(e)}")def convert_to_degrees(self, value):"""将GPS坐标从度分秒格式转换为十进制度数"""d = float(value[0][0]) / float(value[0][1])m = float(value[1][0]) / float(value[1][1])s = float(value[2][0]) / float(value[2][1])return d + (m / 60.0) + (s / 3600.0)def poll_queue(self):while not self.queue.empty():try:msg = self.queue.get(0)if msg[0] == "log":self.log(msg[1])elif msg[0] == "progress":self.progress_var.set(msg[1])self.progress_label.config(text=msg[2])elif msg[0] == "add_item":self.tree.insert("", tk.END, values=msg[1:])except queue.Empty:pass# 继续轮询队列,直到处理完成if hasattr(self, 'processing_thread') and self.processing_thread.is_alive():self.root.after(100, self.poll_queue)def export_to_csv(self):if not hasattr(self, 'results') or not self.results:messagebox.showinfo("提示", "没有可导出的数据")returnfile_path = filedialog.asksaveasfilename(defaultextension=".csv",filetypes=[("CSV文件", "*.csv"), ("所有文件", "*.*")])if file_path:try:df = pd.DataFrame(self.results)df.to_csv(file_path, index=False, encoding="utf-8-sig")messagebox.showinfo("成功", f"数据已成功导出到 {file_path}")self.log(f"数据已导出到 {file_path}")except Exception as e:messagebox.showerror("错误", f"导出失败: {str(e)}")self.log(f"导出失败: {str(e)}")if __name__ == "__main__":root = tk.Tk()app = GPSExtractorApp(root)root.mainloop()    

三、详细代码步骤

  1. 导入必要的库

    • 使用ostkinter构建图形界面
    • pandas处理表格数据
    • PIL库读取图片 EXIF 信息
    • threadingqueue实现多线程处理
  2. 创建主应用类

    • 初始化界面组件,包括文件选择、进度条、结果表格和日志区域
    • 设置中文字体确保界面正常显示
  3. 实现图片处理功能

    • 遍历指定文件夹中的所有图片文件
    • 使用 PIL 库读取图片 EXIF 信息
    • 解析 GPS 信息,包括经纬度、海拔和拍摄时间
    • 将度分秒格式的经纬度转换为十进制格式
  4. 多线程处理

    • 使用单独的线程处理图片,避免界面卡顿
    • 通过队列在主线程和处理线程之间通信
    • 实时更新进度条和日志信息
  5. 结果展示与导出

    • 在表格中显示提取的 GPS 信息
    • 支持将结果导出为 CSV 格式文件

这个工具可以满足基本的图片 GPS 信息提取需求,通过上述优化可以使其更加健壮和功能丰富。

http://www.xdnf.cn/news/530227.html

相关文章:

  • MTK zephyr平台:系统休眠流程
  • CAU数据库class2 SQL语言
  • Java 中Supplier延迟生成值的原因
  • AI提示词魔法公式 - 轻松应对70%挑战
  • LeetCode 39. 组合总和 LeetCode 40.组合总和II LeetCode 131.分割回文串
  • IDC数据中心动力环境监控系统解决方案
  • 安防综合管理系统EasyCVR视频融合平台安防知识:门禁系统与视频监控系统如何联动?
  • Supermemory:让大模型拥有“长效记忆“
  • Vue.js教学第六章:Vue 开发的高效路径,组件化基础
  • 如何设计一个二级缓存(Redis+Caffeine)架构?Redis 6.0多线程模型如何工作?
  • 【盈达科技】GEO优化实战策略
  • BUUCTF PWN刷题笔记(持续更新!!)
  • 2025年全国青少年信息素养大赛(图形化编程)小高组初赛真题及答案解析总结
  • App 发布后才想起安全?iOS 后置混淆的实战方法与工具路线(含 Ipa Guard 应用体验)
  • C++面试4-sizeof解析
  • Python Day26 学习
  • Flink流处理:实时计算URL访问量TopN(基于时间窗口)
  • GitHub 趋势日报 (2025年05月18日)
  • 《CF25E Test》
  • 浏览器的奇幻之旅:从输入网址到页面出现的幕后故事
  • ISO 26262-5 硬件详细设计
  • RV1126多线程获取SMARTP的GOP模式数据和普通GOP模式数据
  • 如何用体育数据做分析:从基础统计到AI驱动的决策科学
  • DB31/T 1545—2025《卫生健康数据分类分级要求》上海地方标准全面解析与未来对医院数据管理以及数据编程影响
  • gtest 库的安装和使用
  • 【保姆级】Nginx简介以及安装
  • vue3中element-plus修改el-tooltip的宽度
  • vue2使用three.js实现一个旋转球体
  • AI自媒体封面生成系统
  • c++字符串常用语法特性查询示例文档(二)