# 开源项目分享:UAVGeoMeasure——基于 DJI 无人机单图像的地面距离与面积测量工具

开源项目分享:UAVGeoMeasure------基于 DJI 无人机单图像的地面距离与面积测量工具

1. 项目简介

在无人机巡检、遥感图像分析、目标检测与图像分割任务中,我们经常不仅需要"识别目标在哪里",还希望进一步知道目标的实际尺度,例如道路宽度、车道宽度、草地面积、裸地面积、施工区域面积、地面设施长度等。

传统测绘流程通常依赖正射影像、空三处理、地面控制点或完整三维重建流程。虽然这些方法精度更高,但在很多工程验证、外业巡检和算法演示场景中,流程相对较重。

因此,我整理并开源了一个轻量级摄影测量工具:UAVGeoMeasure

项目地址:

text 复制代码
https://github.com/Thamkench/UAVGeoMeasure

UAVGeoMeasure 面向 DJI 无人机原始图像,利用图像中的 GPS、相对高度、云台姿态和视场角等元数据,通过针孔相机模型和射线-平面求交,实现单张图像上的地面距离测量和多边形面积估算。

整体流程可以概括为:

text 复制代码
单张 DJI 图像
    -> 鼠标点选目标边界
    -> 像素点反投影为空间射线
    -> 根据云台姿态转到 ENU 地面坐标系
    -> 与目标平面求交
    -> 计算真实尺度下的距离或面积

2. 项目效果

下面是部分测量效果展示。

图 1:草地多边形面积测量

图 2:车道宽度测量

图 3:太阳能板区域面积测量

图 4:输电管线距离测量

从实际结果可以看到,即使不是严格正射视角,只要图像保留 DJI 元数据,并且目标区域满足近似平面假设,也可以得到较为稳定的地面尺度估计结果。


3. 核心功能

UAVGeoMeasure 当前支持以下功能:

text 复制代码
1. 读取 DJI 图像 EXIF/XMP 元数据
2. 根据图像尺寸和 FOV 构建近似相机内参
3. 将图像像素点转换为相机坐标系下的空间射线
4. 根据云台 yaw / pitch / roll 将射线转换到局部 ENU 坐标系
5. 将射线与目标平面求交
6. 两点测距
7. 三点及以上多边形面积计算
8. 交互式点选
9. 测量结果可视化与保存

当前项目适合用于:

  • 道路宽度或车道宽度测量;
  • 草地、裸地、施工区域面积估算;
  • 非正射视角下的地面区域快速测量;
  • 无人机巡检中的目标几何尺寸估计;
  • 目标检测框或分割掩膜后处理的几何尺度补充;
  • 摄影测量与计算机视觉结合的学习示例。

4. 工程结构

项目采用模块化设计,将元数据读取、相机建模、几何计算、交互操作和结果可视化拆分为独立模块。

text 复制代码
UAVGeoMeasure/
├── README.md
├── README_zh-CN.md
├── requirements.txt
├── .gitignore
├── assets/
│   ├── car_test_distance_result_1.jpg
│   ├── car_test_distance_result_2.jpg
│   ├── grass_test_area_result.jpg
│   └── ...
├── demo_data/
│   └── solar_panel_test.jpg
├── examples/
│   └── run_interactive.py
└── uav_geo_measure/
    ├── __init__.py
    ├── config.py          # 测量与可视化配置
    ├── metadata.py        # DJI EXIF/XMP 元数据读取
    ├── camera.py          # 相机内参与像素射线生成
    ├── geometry.py        # 几何计算、ENU 转换、距离与面积计算
    ├── interaction.py     # 鼠标与键盘交互
    ├── visualization.py   # 结果绘制与图像保存
    └── measure.py         # 高层测量流程

这种结构的好处是后续扩展比较方便。例如:

  • 接入 YOLO 检测框后,可以自动测量目标长度或宽度;
  • 接入分割 mask 后,可以自动计算目标区域面积;
  • 接入 DEM / DSM 后,可以替代简单水平平面假设;
  • 接入 GeoJSON / Shapefile 导出后,可以进一步和 GIS 流程结合。

5. 使用方式

5.1 安装依赖

Python 依赖较少:

bash 复制代码
pip install -r requirements.txt

requirements.txt 内容如下:

text 复制代码
numpy
opencv-python

此外,项目使用 ExifTool 读取 DJI 图像元数据,需要保证终端中可以调用 exiftool

推荐使用 Conda 跨平台安装:

bash 复制代码
conda install -c conda-forge exiftool

Ubuntu / Debian:

bash 复制代码
sudo apt update
sudo apt install libimage-exiftool-perl

macOS:

bash 复制代码
brew install exiftool

Windows 可以使用 Conda 或 Chocolatey:

bash 复制代码
conda install -c conda-forge exiftool

或者:

bash 复制代码
choco install exiftool

安装后检查:

bash 复制代码
exiftool -ver

5.2 运行交互式测量

examples/run_interactive.py 中修改图像路径:

python 复制代码
from pathlib import Path
import sys

# Add project root to Python path, so this example script can import uav_geo_measure
PROJECT_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(PROJECT_ROOT))

from uav_geo_measure import run_interactive_measurement
from uav_geo_measure.config import MeasurementConfig


IMAGE_PATH = PROJECT_ROOT / "demo_data" / "solar_panel_test.jpg"


if __name__ == "__main__":
    config = MeasurementConfig(
        max_display_width=2200,
        save_result=True,
        target_z_m=0.0,
    )

    run_interactive_measurement(
        image_path=str(IMAGE_PATH),
        config=config,
    )

运行:

bash 复制代码
python examples/run_interactive.py

如果需要使用自己的图片,也可以直接写绝对路径:

python 复制代码
IMAGE_PATH = Path("/path/to/your/dji_image.jpg")

Windows 路径示例:

python 复制代码
IMAGE_PATH = Path(r"D:\dataset\uav\dji_image.jpg")

6. 交互操作

程序启动后,会显示图像窗口。用户可以通过鼠标点选需要测量的位置。

操作 功能
鼠标左键 添加测量点
鼠标右键 撤销上一个测量点
Enter / Space 完成测量
C 清空全部已选点
Q / Esc 退出

测量规则:

text 复制代码
选择 2 个点:计算地面距离
选择 3 个及以上点:计算多边形面积

7. 方法原理

7.1 从像素点到相机射线

在针孔相机模型中,图像中的每一个像素点都对应一条从相机光心出发的空间射线。像素点本身不能直接确定真实三维点的位置,但可以确定一个视线方向。

相机内参矩阵为:

K=fx0cx 0fycy 001 \mathbf K = \begin{bmatrix} f_x & 0 & c_x \ 0 & f_y & c_y \ 0 & 0 & 1 \end{bmatrix} K=fx0cx 0fycy 001

其中,fxf_xfx 和 fyf_yfy 表示以像素为单位的焦距,cxc_xcx 和 cyc_ycy 表示主点坐标。

当前项目在没有相机标定文件的情况下,使用图像宽度和水平视场角近似计算焦距:

fx=W2tan⁡(θh/2) f_x = \frac{W}{2\tan(\theta_h/2)} fx=2tan(θh/2)W

其中,WWW 为图像宽度,θh\theta_hθh 为水平视场角。当前轻量实现中进一步假设像素近似为正方形,因此有:

fy≈fx,cx≈W2,cy≈H2 f_y \approx f_x,\qquad c_x \approx \frac{W}{2},\qquad c_y \approx \frac{H}{2} fy≈fx,cx≈2W,cy≈2H

给定图像点 (u,v)(u,v)(u,v),可以得到相机坐标系下的射线方向:

r~c=K−1u v 1(u−cx)/fx (v−cy)/fy 1 \tilde{\mathbf r}_c = \mathbf K^{-1} \begin{bmatrix} u \ v \ 1 \end{bmatrix} \begin{bmatrix} (u-c_x)/f_x \ (v-c_y)/f_y \ 1 \end{bmatrix} r~c=K−1u v 1(u−cx)/fx (v−cy)/fy 1

进一步归一化后得到单位射线:

rc=r~c∣r~c∣2 \mathbf r_c = \frac{\tilde{\mathbf r}_c} {\left|\tilde{\mathbf r}_c\right|_2} rc=∣r~c∣2r~c

这里的 rc\mathbf r_crc 表示被点击像素在相机坐标系中的视线方向。


7.2 从相机坐标系到 ENU 坐标系

为了将射线投影到真实地面,需要知道相机当前的姿态。DJI 图像通常会记录云台的 GimbalYawDegreeGimbalPitchDegreeGimbalRollDegree

项目利用这些角度构建相机坐标系到局部 ENU 坐标系的旋转矩阵:

$$

\mathbf R_{e \leftarrow c}

\mathbf R(\psi,\theta,\phi)

其中,ψ\\psiψ、θ\\thetaθ、ϕ\\phiϕ 分别表示 yaw、pitch 和 roll。相机坐标系中的射线可以转换为 ENU 坐标系中的射线: re=Re←crc \\mathbf r_e = \\mathbf R_{e \\leftarrow c}\\mathbf r_c re=Re←crc ENU 坐标系定义为: E 表示向东,N 表示向北,U 表示向上 E \\text{ 表示向东},\\qquad N \\text{ 表示向北},\\qquad U \\text{ 表示向上} E 表示向东,N 表示向北,U 表示向上 相机中心在局部 ENU 坐标系中表示为: C=\[0 0 h\] \\mathbf C = \\begin{bmatrix} 0 \\ 0 \\ h \\end{bmatrix} C=\[0 0 h\] 其中,hhh 来自 DJI 图像中的 `RelativeAltitude`。 *** ** * ** *** #### 7.3 射线与目标平面求交 将相机射线转换到 ENU 坐标系后,可以写成参数方程: ##

\mathbf P(\lambda)

\mathbf C + \lambda \mathbf r_e

其中,C\\mathbf CC 为相机中心,re\\mathbf r_ere 为 ENU 坐标系下的射线方向,λ\\lambdaλ 为射线尺度参数。 默认目标平面为: U=zt U = z_t U=zt 其中,ztz_tzt 由 `target_z_m` 控制。默认情况下: zt=0 z_t = 0 zt=0 通过射线和平面求交,即可得到每个点击像素对应的目标平面点坐标。 对于水平目标平面,射线与平面的交点尺度可以写为: λ=zt−CUrU \\lambda = \\frac{z_t - C_U}{r_U} λ=rUzt−CU 其中,CUC_UCU 是相机中心的竖直分量,rUr_UrU 是射线方向在竖直方向上的分量。 最终得到的目标平面投影点为: ##

\mathbf P_g =
\mathbf C + \lambda \mathbf r_e

\begin{bmatrix}

E \ N \ U

\end{bmatrix}

$$

随后就可以在 (E,N)(E,N)(E,N) 平面内计算距离和面积。


8. 距离与面积计算

8.1 两点距离

两个点投影到 ENU 平面后,分别记为:

P1=E1 N1 zt,P2=E2 N2 zt \mathbf P_1 = \begin{bmatrix} E_1 \ N_1 \ z_t \end{bmatrix}, \qquad \mathbf P_2 = \begin{bmatrix} E_2 \ N_2 \ z_t \end{bmatrix} P1=E1 N1 zt,P2=E2 N2 zt

其水平地面距离为:

d=(E2−E1)2+(N2−N1)2 d = \sqrt{(E_2-E_1)^2 + (N_2-N_1)^2} d=(E2−E1)2+(N2−N1)2

这个距离是目标平面上的米制距离,不是图像中的像素距离,也不是三维斜距。


8.2 多边形面积

当用户选择三个或更多点时,程序会将这些点视为多边形顶点,并使用鞋带公式计算面积:

A=12∣∑i=1m(EiNi+1Ei+1Ni)∣ A = \frac{1}{2} \left| \sum_{i=1}^{m} \left( E_iN_{i+1} E_{i+1}N_i \right) \right| A=21 i=1∑m(EiNi+1Ei+1Ni)

其中,mmm 为顶点数量,最后一个点需要与第一个点相连:

(Em+1,Nm+1)=(E1,N1) (E_{m+1},N_{m+1}) = (E_1,N_1) (Em+1,Nm+1)=(E1,N1)

实际使用时,建议按照目标边界顺时针或逆时针顺序点选。如果点选顺序混乱,或者多边形自相交,面积结果会不可靠。


9. 必需元数据

当前实现依赖以下 DJI 元数据字段:

text 复制代码
XMP-drone-dji:GPSLatitude
XMP-drone-dji:GPSLongitude
XMP-drone-dji:RelativeAltitude
XMP-drone-dji:GimbalYawDegree
XMP-drone-dji:GimbalPitchDegree
XMP-drone-dji:GimbalRollDegree
Composite:FOV

可以用下面命令检查图像是否包含这些信息:

bash 复制代码
exiftool -a -G1 -s image.jpg | grep -Ei "GPS|Altitude|Gimbal|Yaw|Pitch|Roll|FOV"

需要注意的是,很多公开视频抽帧、数据集图片、截图、网页图片可能已经去除了 EXIF/XMP 元数据。这类图片可以用于视觉展示,但如果没有额外相机参数和姿态信息,通常无法直接用于几何测量。

推荐使用:

  • 原始 DJI JPEG 图像;
  • 未被清除 EXIF/XMP 元数据的图像;
  • 具有有效相对高度和云台姿态的图像;
  • 目标区域近似位于水平面的图像。

10. 配置说明

MeasurementConfig 用于控制显示、测量和输出行为:

python 复制代码
MeasurementConfig(
    max_display_width=2200,
    save_result=True,
    target_z_m=0.0,
)

主要参数说明:

参数 说明
max_display_width 图像显示窗口最大宽度。显示图像可以缩放,但点选坐标会自动映射回原图。
save_result 是否保存绘制后的测量结果图。
target_z_m 局部 ENU 坐标系中的目标平面位置,默认为 0。
line_color_bgr 测量线或多边形颜色,OpenCV BGR 格式。
line_thickness 测量线或多边形线宽。
text_scale 结果文本字号比例。
text_thickness 结果文本线宽。

11. 精度影响因素

UAVGeoMeasure 的误差主要来自图像到目标平面的几何投影过程,而不是距离或面积公式本身。

主要影响因素包括:

误差来源 影响
相对高度误差 影响射线与目标平面的求交尺度。
云台 pitch / roll 误差 会改变射线与目标平面的交点,倾斜视角下更明显。
FOV 内参近似 可能引入尺度误差。
镜头畸变 图像边缘区域影响更明显。
目标表面非平面 违反平面假设,会造成系统偏差。
元数据缺失或不同步 会导致投影几何关系不成立。
人工点选误差 边界模糊或图像分辨率较低时影响更大。

对于单张图像中的相对距离和面积测量,水平 GPS 位置误差通常主要影响绝对经纬度位置,而不一定显著影响相对距离和面积。真正影响尺度的通常是相机内参、云台姿态、相对高度、目标平面假设以及人工选点精度。

为了提高结果可靠性,建议优先使用原始 DJI 图像,避免在图像极边缘选点,选择边界清晰且近似平面的目标,并在可能的情况下使用已知尺寸目标、外部测量结果或正射影像进行对比验证。


12. 输出结果

距离测量输出:

text 复制代码
original_name_distance_result.jpg

面积测量输出:

text 复制代码
original_name_area_result.jpg

终端会同步打印:

text 复制代码
选取的像素坐标
投影后的 ENU 坐标
近似纬度和经度
测量得到的距离或面积

其中,经纬度是由局部 ENU 偏移量和图像 GPS 位置近似换算得到的,应理解为辅助地理参考,而不是测绘级大地坐标成果。


13. 局限性

UAVGeoMeasure 基于以下假设:

  • 所选目标位于近似平面表面;
  • 目标平面可以用一个局部平面近似表示;
  • DJI 元数据可用且与图像同步;
  • 基于 FOV 的内参近似满足当前测量需求;
  • 镜头畸变可以忽略,或者对近似测量影响较小;
  • 当前云台姿态定义与代码中的旋转模型一致。

该工具适合地面目标的近似测量。如果需要精确测量建筑立面、楼顶、树冠、桥梁、料堆或复杂三维表面,需要引入 DEM/DSM、LiDAR 或多视图三维重建结果。


14. 后续计划

后续可以继续扩展:

text 复制代码
1. 支持相机标定内参
2. 镜头畸变校正
3. DJI 相机型号数据库
4. DEM / DSM 辅助射线求交
5. GCP 辅助校正
6. 批量测量模式
7. YOLO 检测框尺寸测量
8. 分割 mask 面积计算
9. GeoJSON / Shapefile 导出
10. 不确定性估计与误差传播
11. 与正射影像或 GIS 流程集成

15. 项目地址

GitHub:

text 复制代码
https://github.com/Thamkench/UAVGeoMeasure

如果你也在做无人机视觉、遥感图像处理、摄影测量、目标检测或图像分割相关方向,欢迎查看项目。如果觉得有帮助,也欢迎点一个 Star 支持一下。