OpenDroneMap(以下简称ODM),是一套非常完整的开源无人机影像三维重建服务,我以前写过的NodeODM源码阅读就是ODM的子项目.当时我所在的公司主营业务是无人机相关,基于ODM我们打造了一套全自动化无人机数据处理链路:无人机影像在线回传,自动构建成最终的正摄影像.
ODM是一个好产品.举个例子:就算我梳理了ODM的脉络,我其实依旧不理解SLAM,毕竟这是一个门槛比较高的领域,但这并不妨碍我对ODM进行二次服务化的服务平稳好用的运行了大半年.
什么是好的产品?
ODM整个项目在我完全不了解原理的基础上,做到了让初学者即可快速搭建(现成的Docker镜像),无障碍上手(优秀易懂的UI界面),这就是我认为的好产品.尤其在我们的领域,好代码,好算法并不少,好产品很少.
很长时间过去了,ODM也终于更新到1.0版本之上(本文基于0.8.2,因为写的比较早,可能和现在的结构已经大不一样了),我也早就不再从事类似工作了.但ODM确实是一个让我们进入类似领域的好示范.
我依旧不懂SLAM和三维重建相关领域的知识,以下内容大多是靠百度的资料和自己的理解,一定会有错误,权当抛砖引玉了
作为一个通用型工具,ODM自然选择了三维重建
三维重建和核心思路就是利用同一点在两幅或多幅图像恢复三维信息
提取特征点:SIFT算法
SIFT尺度不变特征转换:
局部影像特征的描述与侦测可以帮助辨识物体,SIFT 特征是基于物体上的一些局部外观的兴趣点而与影像的大小和旋转无关。对于光线、噪声、些微视角改变的容忍度也相当高。基于这些特性,它们是高度显著而且相对容易撷取,在母数庞大的特征数据库中,很容易辨识物体而且鲜有误认。使用 SIFT特征描述对于部分物体遮蔽的侦测率也相当高,甚至只需要3个以上的SIFT物体特征就足以计算出位置与方位
图片两两匹配:
所有图片做两两匹配,生成匹配列表
根据匹配列表计算估算相机参数场景.生成的是稀疏的点云.
因为不可能图上任何一个点都是特征点
利用相机参数进行稠密重建
生成表面素材
三维重建最大的意义其实是让机器能从平面理解空间
VisualSFM/Bundler(产生稀疏点云)+CMVS-PMVS(产生稠密点云)+MeshLab(查看点云成果)
MVE(稀疏+密集)+MeshLab(查看点云成果)
这些库用于构建ODM的c++核心
我们一般只需关心三个文件夹:
modules:算法核心
c++编写,编译后生成可执行文件
opendm:全部后勤
python编写,负责拼接参数,转换格式等辅助性功能
stages:流程控制
整个ODM的运行核心,包含了完整的流程,也是程序的入口,主要是通过opendm的辅助方法构建合理的参数,调用modles里面的核心算法.
内部也采用命令行直接调用.
从stages文件夹内的模块入手,每一个模块都代表一个步骤,每个步骤都包含了若干辅助性功能.
我们应该了解到:
odm_app.py
:整个ODM的入口我们调用,传参并执行的就是odm_app.py.
流程依次是:
* 如果是集群运行的话
types.py
:不得不提的工具类涉及的辅助模块:
- get_image_size.py:获取图片的宽高
- location.py:与地理相关的库,在这里用到获取坐标系和从图片生成包含所有坐标的文件
- gcp.py:从gcp文件中读取坐标信息并整理
ODM_Stage
:所有步骤类的管道基类ODM封装了一种管道,将若干步骤串联起来,数据或变量就完全在管道内自动流转.以上所有的步骤,本质都是不同的管道
管道模式适合于这种没有分支的长流程,封闭性更强,也可以更关注于每一步,而不必关心步骤之间的连接与数据的共享
管道有如下功能,这也是所有步骤都需要的功能:
ODM_Tree
:保存文件树的类这里可以解答我们的一个疑问,所有的过程数据是全部落地的,通过约定的文件路径进行数据的传递.
ODM对于每一个任务都建立独立的文件夹,ODM_Tree可以将相对路径绑定到独立的任务路径.
ODM_Reconstruction
:构建坐标体系的类ODM可以采用图片exif的坐标信息,也可以使用GCP文件指定.
无论是何种类型,都是为了生成一个坐标集合文本.
这个点集合在这里被用来生成一个proj格式的实体,方便转化坐标
dataset.py
:创建数据集dataset是整个ODM的初始步骤,准备好了一切的源头:图片
dataset本身没有复杂的处理流程,只是将文件夹下所有图片整理出来,留作备用
步骤:
coords.txt
coords.txt
run_opensfm
:执行sfm涉及的辅助模块:
- gsd.py:地面采样间距模块,在这里使用了获取图片的宽高
- osfm.py:opensfm操作的相关库,在这里使用了其中的
OSFMContext
这个类,封装了对osfm上下文的操作
顾名思义,整个步骤都是在执行sfm,其中又有如下小步骤:
reconstruction.json
,用于计算地面分辨率reconstruction.ply
depthmaps/merged.ply
reconstruction.nvm
bundle_r000.out
和list_r000.out
geocoords_transformation.txt
mve.py
:执行mve,进行稠密重建mve:Multi-View Environment
mvs:multi view system
涉及的辅助模块:
- context.py:类似于
ODM_Tree
,保存的也是一些文件路径,不过是不依任务改变的命令行工具的路径
步骤:
bundle_r000.out
构建mve场景,直接和搭建管道mve_dense_point_cloud.ply
odm_filterpoints.py
过滤点云涉及的辅助模块:
- point_cloud.py:点云处理类,这里用到了点云过滤,就是调用了编译的C++可执行文件.
过滤点云在上几个步骤中,会出现过多的点,需要保留置信度在给定范围的点,保证模型的精确性
点云的来源有3种:
reconstruction.ply
depthmaps/merged.ply
mve_dense_point_cloud.ply
最终产生point_cloud.ply
odm_meshing
:执行mesh重构涉及的辅助模块:
- mesh.py:执行mesh操作的工具
- gsd.py:在这里用作计算dsm分辨率
步骤:
point_cloud.ply
进行筛选泊松重建输出odm_mesh.ply
odm_25dmesh.ply
执行meshing的目的是输出三维网格,纹理映射只能处理三维网格
mvstex.py
:为模型附上纹理步骤:
reconstruction.nvm
,步骤5生成的odm_mesh.ply
或odm_25dmesh.ply
直接调用了texrecon工具,利用点云生成纹理odm_textured_model.obj
odm_georeferencing.py
:计算地理参考涉及的辅助模块:
- croppe.py:计算裁剪区域
- entwine.py:如果设置为输出EPT格式的点云,则需要使用entwine
步骤:
coords.txt
,步骤2产生的geocoords_transformation.txt
,bundle_r000.out``list_r000.out
,geocoords_transformation.txt
,步骤6生成的odm_textured_model.obj
调用编译的C++可执行文件进行平差,输出odm_textured_model_geo.obj
,odm_georeferencing_model_geo.txt
,odm_georeferenced_model.laz
,odm_georeferencing_transform.txt
odm_georeferenced_model.laz
可以按需生成一系列点云输出文件
odm_georeferenced_model.csv
odm_georeferenced_model.las
entwine_pointcloud
odm_georeferenced_model.bounds.gpkg
odm_dem.py
:按需生成dem文件涉及的辅助模块:
- dem模块:封装了全部生成dem所需的方法
- cropper:切割dem.tif
本步骤是可选的
步骤:
odm_georeferenced_model.laz
,进行pdal形态学分类,修改odm_georeferenced_model.laz
odm_georeferenced_model.laz
,生成dem的tif文件odm_georeferenced_model.bounds.gpkg
切割dem,修改tifodm_orthophoto.py
:生成正摄影像涉及的模块:
- orthophoto.py:封装了一个给成果图像增加预览图的gdal命令
- cutline.py:利用grass计算tif切线
步骤:
odm_textured_model_geo.obj
或步骤6odm_textured_model.obj
(根据模式不同)编译的c++可执行工具生成tif文件,生成成果tif和odm_orthophoto_corners.txt
文件odm_orthophoto_corners.txt
文件和gdal_translate
给成果tif设定坐标范围,修改tifodm_georeferenced_model.bounds.gpkg
对成果tif计算切割线,修改tifodm_georeferenced_model.bounds.gpkg
对成果tif进行切割,生成最终成果splitmerge.py
:大任务的拆分与合并这是所有流程里最特殊的两个了.也只有在最后才能讲.
这个模块有两个类,分别对应步骤2和步骤3,如果数据并非大任务,则该两个模块不进行任何处理.
分布式是个好东西,但实现却是个难题,需要处理这几个问题:
类似的项目并不多,也没有达到接近ODM的完善程度,因此,若是深入了解其中的原理,可能会是进入该领域的一条捷径吧.