# DJIExifs **Repository Path**: wangdl23/djiexifs ## Basic Information - **Project Name**: DJIExifs - **Description**: 本工具用于从 DJI 无人机拍摄的图片中提取 XMP 元数据信息,并导出到 Excel 文件中。提供 **GUI 图形界面** 和 **命令行/模块** 两种使用方式。 - **Primary Language**: Unknown - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-06-19 - **Last Updated**: 2026-11-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # DJI 工具箱(DJI Toolbox) **当前版本:v1.7.2** 在线帮助文档 / 项目主页:https://gitee.com/wangdl23/djiexifs 📄 **配套推文**:[「路旁工坊 01」无人机照片点位图:哪拍的、朝哪拍,一目了然](https://mp.weixin.qq.com/s/Y2D573u46PI1J-rFZsxXpA)(公众号「山行借路」· 路旁工坊 01) 📖 **操作手册:[docs/操作手册.md](docs/操作手册.md)** —— 分步图文,含工具1 / 工具2 / 工具3 / ArcGIS 附录 --- ## ⬇ 下载 **普通用户(推荐)**:直接下载打包好的免安装压缩包 —— 见 **Gitee 发行版**:https://gitee.com/wangdl23/djiexifs/releases 1. 下载 `DJI_Toolbox_v1.7.2.zip` → **解压到任意位置** 2. 双击 `DJI_Toolbox.exe` 即可运行 —— **无需安装 Python 或任何依赖** 3. ⚠️ **首次运行前,先把整个 `DJI_Toolbox` 文件夹加入杀毒软件信任区** (PyInstaller 打包的常见误报;**只信任 exe 不够**,`_internal` 里的运行库也要一并放行) 4. ⚠️ **必须整个文件夹一起用**:主程序依赖同目录下的 `_internal` 运行库,单独把 exe 拷出来打不开 > 解压后目录里的 `操作手册.html` 双击即可打开(含工具1 / 工具2 / ArcGIS 附录 A、B)。 **开发者 / 想跑源码**:见下方「环境准备」与「打包为 exe」。源码方式完全不经过 exe,杀软不会拦。 --- 面向 DJI 无人机数据处理的三功能工具箱,提供统一图形界面(GUI): | 工具 | 功能 | 输入 | 输出 | |------|------|------|------| | 工具1 · DJI 元数据提取 | 提取 DJI 图片 XMP 元数据 | JPG/JPEG 图片文件夹 | Excel(.xlsx) | | 工具2 · 地形取景面生成 | 生成每张照片真实地形取景面 | DJI 元数据 Excel + DEM | Shapefile(.shp) / GeoPackage(.gpkg) | | 工具3 · 单元选片与归属标注 | 给每个单元排出该拍哪几张,并写回「单元名/主副」 | 单元范围面 + 取景面(工具2) + 元数据 Excel | 选片建议表(.csv) + 归属标注 Excel | ## 目录结构 DJI_Toolbox/ ├── main_gui.py 程序入口(三工具合并 GUI) ├── 启动工具箱.bat 免 exe 启动(被杀软拦截时用) ├── DJIexif.py 工具1 核心库:XMP 提取与 Excel 导出 ├── footprint_generator.py 工具2 核心库:地形取景面生成 ├── unit_selector.py 工具3 核心库:单元选片与归属标注 ├── tests/run_tests.py 工具3 自检(合成场景,纯脚本) ├── version.py 版本号(唯一来源) ├── requirements.txt 依赖清单 ├── readme.md ├── LICENSE (木兰宽松许可证 v2) ├── packaging/ 打包相关 │ ├── DJI_Toolbox.spec PyInstaller 打包配置 │ ├── build_exe.bat 一键打包脚本(双击即可) │ ├── hook-pyexiv2-runtime.py pyexiv2 DLL 运行时路径钩子 │ └── version_info.txt exe 版本资源 ├── docs/ │ ├── 操作手册.md 操作手册(源文件) │ ├── 操作手册.html 操作手册(自包含,随包发给使用者) │ └── images/ 界面截图 └── legacy/ 旧版保留(不再维护,可随时删除) ├── DJIexif_gui.py 工具1 旧版独立 GUI ├── DJIexif.spec 旧版打包配置 └── exiv2.dll 冗余副本(真实副本来自 pyexiv2 包) 构建产物目录 build/、dist/ 与虚拟环境 .venv/ 已被 .gitignore 忽略。 ## 环境准备(虚拟环境) python -m venv .venv .venv\Scripts\python.exe -m pip install --upgrade pip .venv\Scripts\python.exe -m pip install -r requirements.txt .venv\Scripts\python.exe -m pip install pyinstaller ## 使用方法 ### GUI(推荐) 方式一:双击 **启动工具箱.bat**(源码方式,杀软不会拦) 方式二:命令行运行 —— .venv\Scripts\python.exe main_gui.py - 工具1:选择照片文件夹 → 选择导出 .xlsx 路径 → 点击"开始提取" - 工具2:选择飞行元数据 Excel(.xls/.xlsx) → 选择 DEM(.tif) → 选择输出文件(.shp 推荐) → 选并行进程数 → 点击"开始生成" - 工具3:选择单元范围面(.shp/.gpkg) → 选择取景面(.gpkg 推荐) → 选择元数据 Excel → 选 DEM(可选) → 点击"分析并给出选片建议" → 点击"写回元数据 Excel" - 窗口页脚有"打开在线帮助文档"链接,指向本仓库 ### 命令行 工具1(DJI 元数据提取): .venv\Scripts\python.exe DJIexif.py <照片文件夹路径> # 或先设置环境变量 PHOTOS_FOLDER 后直接运行 > 输出列数:**58 列**(v1.9.2 起)。除 DJI XMP 字段外,额外从**照片 EXIF** 读取三列—— > `FocalLength`(实际焦距 mm)、`FocalLengthIn35mmFilm`(35mm 等效焦距)、`DigitalZoomRatio`(数码变焦比)。 > 这三项在 DJI 官方 XMP 字段表里**没有**(官方只有 `DewarpData` 给内参),必须从 EXIF 取; > 变焦镜头(`ImageSource=ZoomCamera`)通常不给标定内参,这三列是判断"当前是哪一档焦段"的唯一线索。 工具2(地形取景面生成): .venv\Scripts\python.exe footprint_generator.py <输出.shp> [start_row] [end_row] [workers] 工具3-1(单元选片与归属标注 · 人工主片模式): .venv\Scripts\python.exe unit_selector.py ^ --mains 主片目录 ^ --pool 副/次片原始池 ^ --dem DEM.tif ^ --metadata DJI_metadata.xlsx ^ --out 输出根目录 主片=人工用 batchphotogeo 选好并改名为「单元名.jpg」的照片;副片按覆盖单元面比例选,次片按要素补近景。 工具3-2(剖面线选片 · 人工剖面线模式): .venv\Scripts\python.exe profile_selector.py ^ --lines 剖面线.shp ^ --pool I:\pic\GN E:\GITEE\testdata\zq\pic ^ --metadata 元数据-主片+池-v2.xlsx ^ --footprints 取景面-全池.gpkg ^ --out 输出根目录 --line-field code > **`--pool` 可给多个根**(v1.9.1 起):主片常与池不在同一盘,只给一个根会把主片判成"源不存在"。 > > **准入过滤默认开启**(v1.9.3 起,按 DJI 官方口径): > 只选 `SurveyingMode=1`(官方"精度有保证、推荐用于测绘")**且**有真内参 > (`CalibratedFocalLength` 或 `DewarpData`)的照片;变焦镜头的照片因此默认不进主/副/次片。 > 需要全部照片时加 `--no-survey-filter`。 > > 输出「剖面线标注.xlsx」= 元数据全列 + 12 列标注,其中 > `内参来源`(CALIB / DEWARP / 无)与 `F35`(35mm 等效焦距)用来判断这张片能不能直接交会。 自检(两套,都要跑): .venv\Scripts\python.exe tests\run_tests.py # 工具4-1/4-2(原 3-1/3-2 自动选片版)回归 .venv\Scripts\python.exe tests\test_v19_selectors.py # 工具3-1/3-2(v1.9.x 人工主片/剖面线版) ### 作为模块导入 from DJIexif import build_dataframe, export_to_excel, find_image_files from footprint_generator import generate_footprints, FootprintConfig from unit_selector import SelectConfig, select_photos, write_excel_assign cfg = FootprintConfig(hfov_deg=84.0, grid_width=61, grid_height=45, workers=4) stat = generate_footprints("DJI.xls", "dem.tif", "out.shp", config=cfg) res = select_photos("out.gpkg", "单元范围面.shp", SelectConfig(dem_path="dem.tif")) write_excel_assign("DJI.xlsx", res, "DJI_单元归属.xlsx") ## 工具2 原理 1. 读元数据:经纬度、云台 yaw/pitch、绝对高程(AbsoluteAltitude / GpsAltitude) 2. 相机高程用绝对高程,不用"地面高程 + 相对高度"——RelativeAltitude 是相对起飞点,山区会让取景面整体错位 3. 坐标 WGS84 → DEM 坐标系;DEM 按"照片分组包围盒"窗口读取,不整幅载入内存 4. 按针孔相机模型在整幅视场投射网格光线(viewshed),而非只算四个角 5. 逐条光线求与地形的首个交点,命中点用线性插值精确求交;步进自适应,保证每个 DEM 像素至少采样一次 6. DEM 的 nodata 视为遮挡边界,光线不再"穿透"空洞打到后面的地形 7. 未落点光线按混合策略处理:沿途地形最高点 / 最远距离端点(纯外推点不参与构面) 8. 取景面由网格四边形并集矢量化,凹包/凸包兜底,再做 Douglas-Peucker 简化 9. 支持多进程(workers > 1)按分组并行 可调参数(FootprintConfig):视场角、画幅比、视域网格密度、光线步进与最远距离、构面简化容差、高程字段与改正、DEM 窗口像素预算、进程数等。 ## 工具3 原理 给一个单元从本批照片里排出「该拍哪几张」: 1. 单元范围面按**单元编号**读取,**一个单元号只允许一个矢量**(多个矢量直接报错,避免把两个面当成两个单元算错覆盖率); 2. 取景面(工具2 的成果)已经把**越界 / 被遮挡 / 光线未落地**处理掉了,所以 覆盖率 = 单元面 ∩ 取景面 / 单元面,直接可信; 3. **顺序 1 = 主片,每个有候选的单元一定给出一张**,再按质量分两级: **合规** = 覆盖率 ≥ 90% 且 **正对**(离轴角 ≤ 35°)且 **占幅率 ≥ 12%**,在合规者里取占幅最大的; **需复核** = 没有合规的,仍自动选一张(按「占幅 × 正对」取最优),并把卡在哪一项写进说明 —— 用不用由人工定夺,**不留空**; - **占幅率** = 单元面 ∩ 取景面 / 取景面 —— 单元占了画面多大一块(只看覆盖率会挑出「拍全了但只有一小块」的远景照); - **离轴角** = 光轴与「相机→单元质心」的夹角 —— 镜头有没有正对这个单元; 4. **顺序 2..N**:在「覆盖率 ≥ 下限」且「与已选的每一张交会角 ≥ 阈值」的候选里取**覆盖增量最大**者 —— 范围互补 + 方位分散,即**交叉覆盖**(交会角用单元范围内多个采样点的中位值,需 DEM 取高程); 5. **入射角 β 只作为只读参考写在表里,不参与筛选** —— 不会因为 β 大就把覆盖率高的好照片踢掉。 注意 **离轴角 ≠ 入射角 β**:β 反映拍得多斜(作业方式),离轴角只反映是不是冲着该单元拍的; 6. 输出「选片建议表」,并把「单元名」「主副」两列**插入**元数据 Excel 的副本(不覆盖原文件), 供 Unit2Photo(单元要素入册投影工具)读取。 可调参数(SelectConfig):最小交会角、每单元最多几张、覆盖率下限、全貌图判定阈值、 **主片正对离轴上限、主片占幅下限**、DEM、垂直基准改正等。 ## 打包为 exe 双击 packaging/build_exe.bat,或在工程根目录执行: .venv\Scripts\python.exe -m PyInstaller packaging\DJI_Toolbox.spec --clean 输出目录:dist/DJI_Toolbox/,双击 DJI_Toolbox.exe 运行。 分发时携带整个 dist/DJI_Toolbox/ 文件夹(含运行时与依赖,无需安装 Python)。 ## 注意事项 1. 工具1 仅支持 JPG/JPEG 格式;已支持中文路径(含非 ASCII 路径时自动复制到临时 ASCII 路径读取) 2. 工具1 需要 DJI 无人机拍摄且包含 XMP 元数据的图片 3. 工具2 支持 .xls/.xlsx 元数据格式(.xls 依赖 xlrd,.xlsx 依赖 openpyxl),需要与 DEM 空间范围匹配 4. 工具2 默认用绝对高程,请确认 DEM 的垂直基准与之匹配(必要时用 altitude_offset 做大地水准面改正); 若元数据只有 RelativeAltitude,可设 altitude_source='relative',此时需给出 takeoff_z 或用首张照片位置估算 5. 输出默认推荐 .shp(ArcGIS / QGIS 通用性最好):Shapefile 字段名上限 10 字符, DJI 字段会被自动截断去重(如 GimbalYawDegree → GimbalYawD);需要保留完整字段名时改用 .gpkg 6. 视场角默认 HFOV=80°、VFOV 由 4:3 画幅推算;不同机型建议按实际参数或标定值设置 ## 常见问题 **Q:exe 被杀毒软件(360 等)报毒或直接删除?** 这是 PyInstaller 打包程序的常见误报,不是源码问题:本工程只依赖公开的开源库, 打包时也**没有使用 UPX 加壳**(见 packaging/DJI_Toolbox.spec 中 upx=False)。 误报来自三点:PyInstaller 引导程序 + 未做代码签名 + 文件无信誉。 **① 自己电脑上用 —— 最快(推荐):直接跑源码,完全不经过 exe,杀软不会拦** 双击 启动工具箱.bat **② 要继续用 exe,按顺序处理:** 1. **先把整个 dist/DJI_Toolbox/ 文件夹加入杀软信任区,再运行** - 360:360 安全卫士 → 木马查杀 → 信任区(或 设置 → 信任与拦截 → 信任区)→ 添加目录 - 只信任 exe 不够:_internal/ 里的运行时要一并放行,否则下次扫描仍会被清掉 2. 文件已被清除:先到 360 的**恢复区**恢复出来,再按第 1 步加信任 3. 提交**误报申诉**(360 官方误报反馈渠道),附上文件的 SHA-256 4. 长期方案:给 exe 做**代码签名**(OV/EV 证书),签名后误报率大幅下降 5. **已实施:重编译 PyInstaller 引导程序。** 官方预编译的引导程序是 360 特征命中的主因; 本机装有 VS2022 C++ 工具链,已用本地 MSVC 重新编译(字节与官方版完全不同),实测 360 不再拦截。 若以后重装/升级 PyInstaller 使引导程序变回官方版,按下列步骤重做: # 1) 下载 PyInstaller 源码包(sdist 自带的是官方预编译引导程序,必须自己重编) .venv/Scripts/python.exe -m pip download --no-binary pyinstaller --no-deps pyinstaller -d _pyi_src tar -xf _pyi_src/pyinstaller-6.22.3.tar.gz -C _pyi_src # 2) 在 VS 编译环境里用 waf 编译引导程序 call "D:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build/vcvarsall.bat" x64 cd _pyi_src/pyinstaller-6.22.3/bootloader E:/Users/djiexifs/.venv/Scripts/python.exe ./waf all --target-arch=64bit # 3) 用新引导程序覆盖 venv 里的官方版,再重新打包 # 源目录 _pyi_src/pyinstaller-6.22.3/PyInstaller/bootloader/Windows-64bit-intel/ # 目标目录 .venv/Lib/site-packages/PyInstaller/bootloader/Windows-64bit-intel/ **③ 分发给别人:打包成「带密码的压缩包」** 把整个 dist/DJI_Toolbox/ 用 WinRAR / 7-Zip 打成**带密码**的压缩包再发, 并在说明里写「先加信任区,再解压运行」。带密码的压缩包杀软不扫描内部, 能避免文件在传输和解压环节被直接删掉。