☰
DSO直接法视觉里程计编译运行全攻略:从源码到数据集实战
2026/10/14 12:11:56 网站建设 项目流程

视觉SLAM笔记写到这里,前面几篇基本都在特征点法的圈子里打转,今天换个口味,把DSO的编译运行完整过一遍。DSO(Direct Sparse Odometry)是直接法稀疏里程计的代表作,也是这几年SLAM学习路线里几乎绕不开的一个开源项目。文章既是我自己的实操记录,也想帮那些刚把源码拉下来、结果卡在第一步编译的读者少走弯路。内容覆盖环境依赖、源码适配、数据集准备、运行参数和常见报错,适合已经看完ORB-SLAM系列、正准备进入直接法源码阅读阶段的同学参考。

1. 为什么我建议把DSO的编译运行放在阅读源码之前

1.1 直接法到底“直接”在哪里

大多数人对SLAM的理解都是从特征点法开始的:先提取ORB特征,计算描述子,匹配特征对,再求解位姿和地图点。这套流程非常成熟,ORB-SLAM系列就是典型代表。但特征点法有一个天然的瓶颈——它把图像里最丰富的灰度信息压缩成了几百个特征点,纹理弱、重复纹理多的场景很容易歇菜。

DSO走的是另一条路。它不提取特征、不计算描述子,直接拿像素灰度开刀。核心假设是同一个空间点在不同帧里的光度值应该一致,然后通过最小化光度误差来同时优化相机位姿和点的逆深度。这种思路的好处很明显:能用上图像里几乎所有的像素信息,在弱纹理场景下的表现往往比特征点法更稳。缺点也同样明显——对光照变化非常敏感,对相机运动初始值的要求也很苛刻。

所以如果你想真正理解“直接法为什么有人愿意用”,光看论文是不够的,跑一遍DSO的实时输出会让你立刻感受到两种流派在行为上的差异。

1.2 编译过程就是一次免费的源码预习

我见过不少人的做法是先抱着论文啃两周,再打开源码从第一行看。但DSO的代码组织其实非常紧凑,编译过程本身就能帮你建立初步的代码地图。

你在构建时一定会注意到它分出了几个核心模块:FullSystem负责整个滑动窗口优化,OptimizationBackend是实现直接法点优化的后端,dso_dataset是数据集入口工具。这些命名非常直白,编译时看到这么多模块链接在一起,你对整个系统的数据流就会有个模糊的轮廓,后面读代码会顺畅得多。

另一个原因是DSO提供了一套很直观的可视化界面。跑通之后你能在GUI里看到稀疏深度点如何随运动不断更新,能直接观察到关键帧边缘化和点的淘汰过程,这种“眼见为实”的反馈比看十篇文章都有用。

2. 编译前的环境准备:依赖、版本与源码选择

2.1 依赖清单和版本红线

DSO本质上是一个C++项目,依赖不算特别多,但版本坑确实存在。先看我在Ubuntu 22.04上实际使用的依赖清单:

组件推荐版本作用备注
Eigen33.3.x矩阵运算核心库系统包或源码安装均可
Boost1.7x+线程、时间戳等工具库libboost-all-dev 一步到位
OpenCV3.x / 4.x图像读取、可视化4.x 需做小幅代码适配
SuiteSparse4.x+稀疏求解器DSO后端优化依赖
libzip1.x读取压缩数据缺少时程序可能直接退出
Pangolin0.5/0.6GUI可视化(可选)不装也能编译主体
libpng-dev任意PNG图像解码系统一般自带

这里最需要留神的就是OpenCV。DSO官方仓库的代码是基于OpenCV 2.4/3.x时代的习惯写的,在新系统上直接编译大概率会碰到几个接口变更问题,原因是OpenCV 4移除了大量老的C接口和宏定义。

我当时没有强行安装OpenCV 3,因为系统里已经有一套4.x,和很多其他项目共享,不想为了DSO去动全局环境。实际处理起来也只是改两处代码,后面第3章会详细说。

2.2 源码用官方版还是社区fork

DSO的源码在GitHub上有多个副本,社区里也有一堆加了ROS接口、适配了新OpenCV的分支版本。我的建议是:如果是学习用途,第一遍尽量用原始版本,不要用加了一堆功能的fork。

原因有三个。第一,论文里的符号、公式和官方代码的变量名是对应最紧密的,你读论文时能直接对照源码,换到fork里反而对不上。第二,fork版本往往会顺手改掉一些原作者认为是“特性”的行为,学习阶段容易被带偏。第三,官方版代码量更少,编译问题也更可控,你改起来知道每一行是干什么的。

当然你也会看到网上有人说“DSO又老又不维护”,然后推荐一堆新项目。这话有一定道理,但DSO作为直接法源码阅读的入门教材价值依然很高,二十几岁的代码库在SLAM领域并不能算老古董。

2.3 安装依赖的完整命令

以下是我的实际安装命令,适用于Debian系的系统。如果你用的是其他发行版,把包管理器换成对应工具即可:

sudo apt-get update sudo apt-get install -y \ build-essential cmake git \ libeigen3-dev \ libboost-all-dev \ libsuitesparse-dev \ libopencv-dev \ libzip-dev \ libpng-dev \ libglew-dev \ libgl1-mesa-dev

如果你的系統已经有OpenCV 4.x,libopencv-dev装好后系统里就是4.x。如果系统是干净状态,apt默认也会给到4.5.x或更高,三条里面没得选,做好代码适配就行。

Pangolin如果你确实想看到GUI效果,单独编译一份:

git clone https://github.com/stevenlovegrove/Pangolin.git cd Pangolin mkdir build && cd build cmake .. make -j4 sudo make install

不装Pangolin也不影响DSO主体编译,最多是看不到渲染窗口。我实验时前几次就没装,用mode=1关闭GUI跑完整个数据集,结果一点不影响。

3. 从下载到可执行文件:完整编译过一遍

3.1 拉取源码并建立构建目录

先把源码拉下来,建议直接用官方仓库路径:

git clone https://github.com/JakobEngel/dso.git cd dso mkdir build && cd build cmake .. make -j4

如果你的CPU核心多,可以把-j4改成-j8或更高,但要留意内存,DSO编译时比较吃内存,线程开太多可能出现编译进程被系统杀掉的情况。我推荐先-j4,跑一遍成功之后再用-j8做增量编译。

编译产物主要三个:

文件位置说明
dso_librarybuild/src/libdso.so核心库文件
dso_datasetbuild/bin/dso_dataset数据集入口程序
dso_guibuild/bin/(可选)直接法可视化demo

实际上如果你安装了Pangolin,CMake会自动检测到并多编一个GUI示例。那个示例我会放到后面再提。

3.2 处理OpenCV版本差异的两个修改

在OpenCV 4.x系统上编译时,我遇到的第一个报错是:

error: ‘CV_LOAD_IMAGE_GRAYSCALE’ was not declared in this scope

这是DSO源码里用了老版本OpenCV的整型常量,OpenCV 4把它移除了。解决方案很简单,把源码里的两处旧宏替换为新的常量:

sed -i 's/CV_LOAD_IMAGE_GRAYSCALE/cv::IMREAD_GRAYSCALE/g' src/*.cpp sed -i 's/CV_LOAD_IMAGE_UNCHANGED/cv::IMREAD_UNCHANGED/g' src/*.cpp

然后还要检查一下有没有包含正确的头文件。DSO的main.cpp里通常只有#include "opencv2/opencv.hpp",这个在OpenCV 4下依然有效,不需要额外改动。但如果编译时提示找不到cv::imread之类的符号,就需要在相关文件里加一行:

#include <opencv2/imgcodecs.hpp>

还有一个链接层面的坑,DSO的CMakeLists里对dso_dataset只链接了opencv_core和opencv_imgproc,没有opencv_imgcodecs。在OpenCV 4环境下,cv::imread这类函数属于imgcodecs模块,链接时会报undefined reference。解决方法是在根目录CMakeLists.txt找到dso_dataset对应的target_link_libraries,把模块补上:

target_link_libraries(dso_dataset dso_library opencv_core opencv_imgproc opencv_imgcodecs)

如果你用的是OpenCV 3.x系统,这两个修改都不需要,直接编译即可。

3.3 编译中途的其他零星问题

我编译过程中还碰到过一个警告高亮:Eigen的对齐警告。这种通常和CPU指令集对齐有关,不影响编译结果。真正恼人的是另一个问题——部分新版本的Pangolin和DSO自带的thirdparty里的旧版本头文件冲突,导致一堆模板报错。

解决方案很直接:如果不需要GUI,就在编译DSO之前把系统里的Pangolin临时移出/usr/local/lib和/usr/local/include,或者直接用cmake关掉Pangolin检测。在构建目录里执行:

cmake ..

如果它自动找到了Pangolin但又编不过,可以显式告诉CMake忽略:

cmake .. -DPangolin_DIR=/usr/local/lib/cmake/Pangolin

实际上DSO的CMake对Pangolin是“有就编,没有就跳过”的逻辑,不存在必须装的问题。我第一次跑通的时候,系统里根本没有Pangolin,一切正常。

4. 用公开数据集把DSO跑起来

4.1 数据集准备:用哪种、目录长什么样

DSO官方README里推荐了两个公开数据集:一个室内MAV视觉惯性数据集,另一个是RGB-D格式的数据集。我两个都跑过,感受差别很大。

先说MAV视觉惯性数据集。它是在室内用无人机采集的,有双目、IMU和地面真值,但DSO只需要单目图像序列。目录结构通常是这样的:

mav0/ cam0/ data/ 1403636579763555584.png 1403636579813555456.png ...

DSO入口程序要求files参数直接指向data这一层,也就是图片文件所在目录。很多第一次玩的朋友把files=指到了mav0甚至cam0,结果程序启动后加载不到任何图像。

RGB-D数据集(TUM格式)结构类似,一般直接给你一个rgb/目录加一个rgb.txt时间戳文件。DSO看的是图片文件本身,时间戳文件主要由你事后做轨迹评估时用,运行时不需要。

4.2 相机内参文件到底怎么写

DSO需要一个文本文件作为相机内参,格式非常简单,一行八个数字:

fx fy cx cy d1 d2 d3 d4

前四个是焦距和光心,单位都是像素。后四个是畸变系数。很多人会在这里被卡住,因为MAV数据集的标定结果通常以yaml格式给出,例如这样:

cam0: camera_model: pinhole intrinsics: [460.806, 460.334, 365.892, 248.021] distortion_model: equidistant distortion_coeffs: [-0.003154, 0.001455, -0.000529, 0.000167]

把yaml里的参数变成DSO需要的txt,可以直接写:

458.654 457.296 367.215 248.375 0 0 0 0

上例是MAV数据集常用的内参值,我用它跑通了整个序列。但要注意:四个畸变系数全填0,相当于假设镜头没有畸变。MAV数据集本身畸变很小,对验证算法流程影响可以接受,但如果你用的是广角或鱼眼镜头采集的数据,一定要做相机标定,并把畸变系数按DSO支持的模型填进去,否则前端追踪会莫名发散。

4.3 运行命令与输出解读

确保编译产物在build/bin下,然后进入数据目录所在的某处,执行:

./bin/dso_dataset \ files=/abs/path/to/mav0/cam0/data \ calib=/abs/path/to/camera.txt \ preset=0 \ mode=1

这里解释几个关键参数:

  • files:图像序列目录的绝对路径,必须是图片文件直接所在目录。
  • calib:相机内参文件路径。
  • preset=0:使用默认参数集。preset后面会专门讲。
  • mode=1:关闭GUI窗口,只保留命令行输出。如果装了Pangolin想看到可视化,就用mode=0。

跑起来之后,你会看到程序先打印加载了多少张图片,随后进入逐帧处理流程。日志里有几个信息值得关注:

[TRACK] initialized [TRACK] frames: 120, points: 2048, iso: 0.123, mean energy: 0.045, ...

points是当前活跃的稀疏深度点数,mean energy是平均光度误差,数值越小说明当前帧和关键帧之间的光度一致性越好。如果这个值突然剧烈增大,说明相机运动太快或光照突变,系统很可能马上丢跟踪。

如果是GUI模式,你会看到一个窗口绘制出当前帧的灰度图叠加一层彩色点,那些点就是DSO正在优化的稀疏深度点。第一次跑的时候你会直观感受到,直接法的“地图”不是稠密点云,而是一层稀疏但覆盖全图的深度估计。

4.4 轨迹输出与简单评估

程序运行完整个序列后,会在当前工作目录生成一个result.txt,里面保存了每一帧的估计位姿。格式大概是时间戳 tx ty tz qx qy qz qw,这是DSO输出的默认轨迹格式。

拿到轨迹之后,再用公开的轨迹评估工具和真值对比。流程一般是:

evo_traj tum result.txt --ref groundtruth.txt -a

不过DSO输出的result.txt列顺序和标准TUM格式可能有差异,先打开文件看一眼头部再决定是否需要调整列顺序。真值文件如果也是TUM格式,对齐后就能得到ATE和相对误差,这是评判DSO在你数据上表现的最直接指标。

我第一次在MAV数据集的第一个序列上跑完,ATE误差大约在0.3米左右,对于纯单目里程计来说算是不错的结果。换个亮度变化比较大的RGB-D序列,结果就惨不忍睹,这正好印证了直接法对光照敏感的特性。

5. 调参数:同样的数据,换一组参数跑出不同效果

5.1 preset到底在调什么

DSO提供了一套预设参数,从0到3数字越大,系统跑的负担越轻:

preset含义适用场景
0全功能,默认打开重直方图化、3D点输出等普通研究测试,效果好但稍慢
1轻量模式,关闭部分冗余计算算力一般或追求速度
2更轻量批量跑实验
3最简模式,只保留核心跟踪大规模跑数据集

如果你只是验证“DSO到底能不能跑通”,直接preset=0就好。但如果要做消融实验或者批量跑几十个序列,我建议用preset=1或者preset=3,跑完再用结果写报告。

5.2 更细粒度的运行时参数组合

除了preset,我常用的还有几个独立开关:

./bin/dso_dataset \ files=/path/to/data \ calib=/path/to/camera.txt \ preset=1 \ nom3D=1 \ noRehist=1 \ quiet=1

这几个参数我拆开说:

  • nom3D=1:不保存和输出三维点坐标。能省一点内存和IO,跑纯轨迹评估时完全够用。
  • noRehist=1:关闭重直方图化。DSO默认会对每帧图像做直方图均衡,来对抗轻微的光照变化。关掉之后能省一些时间,但遇到光照突变时更容易丢跟踪。
  • quiet=1:只输出关键日志,对跑批量实验非常友好。

我的经验是:室内无人机数据(光照稳定)可以大胆开noRehist=1,速度会明显提升;手持相机在室内走来走去的数据,反而应该保持直方图均衡打开,因为它会频繁经过窗户、灯光等亮度变化区域。如果你用RGB-D数据集做测试,默认的preset=0是最稳妥的,因为那种数据的前后帧视差变化大,关闭太多优化模块容易崩。

还有一个容易忽略的点:DSO对图像分辨率比较敏感,如果原图很大(比如1080p以上),跑起来会吃力。可以把图片先压到640或752宽再喂给DSO,但记住相机内参也要按相同比例缩放,否则光心位置对不上,系统初始化就会失败。

6. 高频问题排查:从一条报错反推根因

6.1 编译期三连问:OpenCV、Pangolin、链接缺失

我把编译期最常遇到的几个问题列个表:

报错根因处理方法
CV_LOAD_IMAGE_GRAYSCALE undeclaredOpenCV 4移除了旧宏sed替换为cv::IMREAD_GRAYSCALE
undefined reference to cv::imreadCMake没有链接imgcodecs在target_link_libraries里补opencv_imgcodecs
Could not find Pangolin没有安装或版本过新不装即可,或显式指定Pangolin_DIR
Eigen alignment assertCPU指令集对齐问题一般不影响运行,重编时加-march=native
编译中内存被杀make线程太多改用make -j2

这些坑大多一两行命令就能解决,但如果不理解根因,会越改越乱。我自己就在Pangolin上浪费过一晚上:装了个新版Pangolin,结果和DSO的老代码不兼容,报一堆模板错误。后来把Pangolin卸了,世界安静了。

6.2 运行期最容易被忽略的目录与格式

运行期报错相对少,但一旦遇到就很隐蔽。

第一个高频问题是图像加载数量为0。检查方法很简单,直接看启动日志里有没有类似:

found 0 files in directory

原因九成是files指向的目录层级不对,或者目录里的图片命名不是纯数字序列。DSO对图片文件名有要求,一般要求是按时间戳命名的连续序列,比如1403636579763555584.png。如果你的图片是frame_001.png这种,需要先改名成纯数字再跑。

第二个问题是程序启动后立刻崩溃,提示找不到libzip.so。这不是你的错,是DSO代码里调用了libzip相关的库但系统没装。装好libzip-dev重新编译即可。

第三个问题比较隐蔽:calib文件格式错误但程序不报错,只是第一帧初始化永远失败。因为DSO不会严格校验内参文件格式,多了一个字符或少了一行,它都可能给你算出个不合理的初始化结果。遇到初始化反复失败,先检查内参文件的数字个数是不是恰好8个,每个数字是不是用空格分隔。

6.3 一个系统的适配折腾记录

我想起自己在一个比较新的系统上跑DSO的经历:编译器版本很新,OpenCV是4.x,装了一堆开发包。编译时遇到的第一波报错全是OpenCV老接口问题,用sed替换之后,又冒出来opencv_imgcodecs链接问题,补上链接后编译通过了。结果一运行又发现libzip版本不兼容,程序启动即挂。

那一次的处理思路其实是可以复用的:先编译,再跑最小数据,把报错信息拉满,然后逐层排除。DSO这个项目虽然老,但报错都还算直白,一步一步看总能定位到根因。最怕的是从网上抄一段“万能修复”,不假思索就应用,回头你可能根本不知道自己的环境到底少了什么。

6.4 跑完数据之后,轨迹文件去哪了

偶尔有人反馈“程序跑完了但没看到result.txt”。DSO输出轨迹文件的逻辑是:正常运行完整个序列、自然退出时才会落盘。如果你是中途用Ctrl+C强停的,文件可能没写好。

另外它写的是相对当前工作目录的result.txt,不是源码目录,也不是build目录。我习惯在数据目录旁边建一个run子目录,进去之后用绝对路径调用dso_dataset,这样所有输出文件都集中在run里,后面做批量实验也方便。

7. 我的几个实际体会

跑DSO这件事,光看文字总觉得难,真正动手过一次之后就会发现它比想象中简单,只是过程中的版本适配问题比较零碎。我个人的建议是:不要为追求“完美环境”而纠结太久,系统上有什么就用什么,缺什么补什么,OpenCV版本不兼容就按第3章的方法改,Pangolin装不上就先不装。第一次的目标只有一个——让dso_dataset把一个序列完整跑完,看到终端里出现最终的轨迹统计。

跑通之后再回头研究参数、研究GUI、研究怎么把轨迹和真值对齐,每一步的收获都会非常扎实。DSO的代码质量在SLAM圈子里是出了名的紧凑,你编译时接触到的每个模块,都会在后面读代码时再次相遇。如果看完这篇还是卡在某个报错上,多核对一下CMake的输出版本信息,多半是依赖版本和系统环境的问题,咬咬牙总能过去。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询