/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}
摘要:本文介绍一个开源的单目多视角测距项目,基于 DJI RTK 无人机图像,通过多视角三角测量恢复地面目标的 ECEF 三维坐标,进而计算目标间实际距离。实测 DJI Mavic 3E,25 样本,MAE 0.084m,平均相对误差 2.12%。项目包含核心算法库、Flask Web 演示应用、标定工具及完整文档,MIT 开源。

目录
- 项目背景
- 核心原理
- 实测精度
- 项目结构
- 快速开始
- 核心 API
- 标定工具
- 约束说明
- 项目地址
与姿态(偏航/俯仰/横滚),结合像素坐标,多视角三角测量可以完整恢复目标的三维坐标。
二、核心原理
核心思路:同一个目标在不同位置拍摄时,每张图产生一条空间射线,多条射线的交点就是目标的 3D 坐标。为什么需要RTK?因为我们需要有一个世界坐标系来构建世界坐标系下的通过目标点的射线方程,RTK坐标系是一个非常精确的坐标系,非常适合用来做测距系统的世界坐标系,2个视角下通过同一个目标点的射线方程相交就可以计算出目标点在RTK坐标系下的精确3D坐标。 
2.1 整体流程
像素坐标 (u, v)
↓ 相机内参 + 去畸变
↓ 归一化相机坐标系射线
↓ RTK 姿态变换(camera → NED → ECEF)
↓ 射线方向 d_i + 光心位置 O_i(ECEF)
↓ 多视角最小二乘三角测量
→ 3D ECEF 坐标 X
→ 两点欧氏距离 ‖X_a – X_b‖ = 目标间距
2.2 坐标变换
射线方向变换到 ECEF:
d
i
=
R
e
c
e
f
←
n
e
d
(
l
a
t
,
l
o
n
)
⋅
R
n
e
d
←
b
o
d
y
(
y
a
w
,
p
i
t
c
h
,
r
o
l
l
)
⋅
R
c
a
m
←
b
o
d
y
⋅
d
c
a
m
\\mathbf{d}_i = R_{ecef\\leftarrow ned}(lat,lon) \\cdot R_{ned\\leftarrow body}(yaw,pitch,roll) \\cdot R_{cam\\leftarrow body} \\cdot \\mathbf{d}_{cam}
di=Recef←ned(lat,lon)⋅Rned←body(yaw,pitch,roll)⋅Rcam←body⋅dcam
相机光心 ECEF 坐标:
O
i
=
ECEF
(
l
a
t
i
,
l
o
n
i
,
a
l
t
i
)
+
[
d
X
,
d
Y
,
d
Z
]
T
\\mathbf{O}_i = \\text{ECEF}(lat_i,\\ lon_i,\\ alt_i) + [dX,\\ dY,\\ dZ]^T
Oi=ECEF(lati, loni, alti)+[dX, dY, dZ]T
其中
[
d
X
,
d
Y
,
d
Z
]
[dX, dY, dZ]
[dX,dY,dZ] 为外参偏置,用于补偿云台安装误差。
2.3 最小二乘三角测量
每条观测构造一行方程:
(
I
−
d
i
d
i
T
)
X
=
(
I
−
d
i
d
i
T
)
O
i
(I – \\mathbf{d}_i\\mathbf{d}_i^T)\\mathbf{X} = (I – \\mathbf{d}_i\\mathbf{d}_i^T)\\mathbf{O}_i
(I−didiT)X=(I−didiT)Oi
将
N
N
N 条观测堆叠为:
A
X
=
b
A\\mathbf{X} = b
AX=b
使用 numpy.linalg.lstsq 求解三维点
X
\\mathbf{X}
X。
2.4 测距
D
=
∥
X
a
−
X
b
∥
2
D = \\|\\mathbf{X}_a – \\mathbf{X}_b\\|_2
D=∥Xa−Xb∥2
三、实测精度
测试平台:DJI Mavic 3E 可见光广角 样本数:25 真值范围:2.00m ~ 6.01m 基线范围:0.053m ~ 9.904m
汇总结果
| DJI Mavic 3E | 25 | 0.084 | 0.126 | 2.120 | +0.052 |
补充:最小 AE = 0.0002m,最大 AE = 0.363m;最小 RE = 0.006%,最大 RE = 6.05%。
分目标结果
| 0 | 13 | 0.024 | 0.032 | 0.879 | -0.012 |
| 1 | 3 | 0.158 | 0.176 | 2.628 | +0.068 |
| 2 | 4 | 0.240 | 0.257 | 4.006 | +0.240 |
| 3 | 5 | 0.071 | 0.075 | 3.533 | +0.060 |
四、项目结构
MMVDMS/
├── src/
│ └── mvtriangulation/ # 核心算法库
│ ├── __init__.py # 公开 API 入口
│ ├── triangulator.py # CameraTriangulator
│ ├── transforms.py # 坐标变换
│ ├── pipeline.py # triangulate_dataframe
│ ├── models.py # 数据模型
│ ├── exceptions.py
│ ├── parsers/
│ │ └── dji_xmp.py # DJI EXIF/XMP 元数据解析
│ └── calibration/
│ ├── intrinsics_zhang.py # 张正友内参标定
│ └── extrinsics.py # 外参鲁棒拟合
├── apps/
│ └── flask_demo/ # Web 演示应用
├── examples/
│ ├── minimal_usage.py # 最小化完整示例
│ ├── fit_intrinsics_zhang.py # 内参标定示例
│ └── fit_extrinsics_robust.py # 外参拟合示例
├── docs/
│ ├── zh/ # 中文文档
│ └── en/ # 英文文档
├── requirements.txt
└── LICENSE
五、快速开始
环境要求:Python >= 3.8
git clone https://github.com/BRAIN-INSPIRED-TECHNOLOGY/MMVDMS.git
cd MMVDMS
pip install -r requirements.txt
5.1 运行最小化 Demo
# 使用内置 demo_csv 数据,默认 Mavic 3E 内参
python examples/minimal_usage.py \\
–points-csv examples/demo_csv/keypoints_observation2.csv \\
–image-dir examples/demo_images \\
–camera-model m3e
# 指定机型和关键点对
python examples/minimal_usage.py \\
–points-csv examples/demo_csv/keypoints_observation2.csv \\
–image-dir examples/demo_images \\
–camera-model m3t –pair 1-2
# 其他机型,传入自定义内参 JSON
python examples/minimal_usage.py \\
–points-csv examples/demo_csv/keypoints_observation2.csv \\
–image-dir examples/demo_images \\
–camera-model others \\
–intrinsics-json path/to/camera_intrinsics.json
5.2 启动 Web 演示应用
# 默认 Mavic 3E
python -m apps.flask_demo
# 指定机型
python -m apps.flask_demo –camera-model M3T
浏览器访问 http://localhost:5000,流程:导入图像 → 标注关键点 → 三角测量 → 查看距离 → 导出。
六、核心 API
三角测量
from mvtriangulation import (
CameraTriangulator,
triangulate_dataframe,
build_camera_arrays,
)
# 方式一:直接调用 pipeline
# 先构建相机参数(内参 + 外参偏置)
camera_matrix, dist_coeffs, extr_cfg = build_camera_arrays(intrinsics, extrinsics)
# df_xy 包含像素坐标 + 从原图 XMP 提取的位姿字段(lat/lon/alt/gimbal_pitch/yaw/roll)
result_df = triangulate_dataframe(df_xy, camera_matrix, dist_coeffs, extr_cfg)
# 方式二:使用 CameraTriangulator 直接三角测量
triangulator = CameraTriangulator()
point_3d = triangulator.triangulate(origins, directions)
元数据解析
from mvtriangulation import extract_dji_metadata_from_jpeg_bytes
with open("image.JPG", "rb") as f:
metadata = extract_dji_metadata_from_jpeg_bytes(f.read())
# 返回:纬度、经度、椭球高、偏航、俯仰、横滚
七、标定工具
7.1 张正友内参标定
python examples/fit_intrinsics_zhang.py \\
–images "data/calib/*.jpg" \\
–board-cols 9 \\
–board-rows 6 \\
–square-size 0.025 \\
–out "data/camera_intrinsics.json"
输出格式:
{
"camera_matrix": [[fx, 0, cx], [0, fy, cy], [0, 0, 1]],
"distortion_coefficients": [[k1, k2, p1, p2, k3]]
}
7.2 外参偏置拟合
用于补偿云台安装角误差:
python examples/fit_extrinsics_robust.py \\
–intrinsics "data/camera_intrinsics.json" \\
–fit-csv "data/obs_fit.csv" \\
–test-csv "data/obs_test.csv" \\
–out "data/camera_extrinsic_params.json" \\
–with-xyz-offset
7.3 内置默认内参(DJI Mavic 3E / 3T)
无标定文件时可直接使用内置参数快速启动:
| DJI Mavic 3E | 5280×3956 | 3660.0 | 3660.0 | 2640.0 | 1978.0 |
| DJI Mavic 3T | 8000×6000 | 5500.0 | 5500.0 | 4000.0 | 3000.0 |
注:默认内参为估算值,高精度场景请执行棋盘格标定。
八、约束说明
| 必须 RTK | 算法依赖厘米级位置与姿态,普通 GPS 精度不足 |
| 仅支持 DJI 元数据 | 其他厂商需自行实现解析接口 |
| 最少 2 帧观测 | 每个关键点至少需要 2 条有效射线 |
| 基线几何敏感 | 射线近平行时深度方向不稳定,精度下降 |
| 演示应用内存存储 | 重启后数据清空,非生产级 |
九、项目地址
- GitHub:https://github.com/BRAIN-INSPIRED-TECHNOLOGY/MMVDMS.git
- License:MIT
- 文档:中英文均有(docs/zh/ 和 docs/en/)
- Python 版本:>= 3.8
欢迎提交 Issue 或 PR。Bug 报告请附上复现步骤、Python 版本和报错日志;功能建议请先开 Issue 讨论。
如果本文对你有帮助,欢迎点赞收藏,也欢迎 GitHub Star 支持开源。




