
D435i 这颗摄像头在 SLAM 圈子里几乎人手一颗原因很简单RGB、深度、IMU 三件套齐全USB 一插就能用价格也亲民。可真要拿它跑 ORB_SLAM2 的时候网上教程清一色是 ROS 版。ROS 版确实方便launch 文件一拉话题一订阅就完事但如果你不打算用 ROS或者只是想在裸 Linux 环境里快速验证单目 SLAM 的效果照着 ROS 教程操作反而会被一堆无关依赖拖住。这篇文章分享我用 RealSense D435i 单目模式直接跑 ORB_SLAM2 的完整方案全程无 ROS。librealsense 取流、OpenCV 转格式、ORB-SLAM2 的单目接口做追踪整个链路干净可控。文章会把方案思路、环境搭建、核心代码、调参经验和踩坑记录都过一遍适合拿到 D435i 后想快速跑通单目 SLAM 的开发者也适合需要把 ORB-SLAM2 集成进自有系统里、又不希望被 ROS 绑死的朋友。1. 方案剖析D435i 单目跑 ORB-SLAM2 的整体设计1.1 为什么放着深度不用偏选单目模式D435i 的深度传感器确实是很多人买它的理由但做单目 SLAM 时它的 RGB 传感器才是主角。D435i 的彩色相机是全局快门在 640x48030fps 下图像一致性很好运动模糊少这对特征点法 SLAM 特别重要。ORB-SLAM2 依赖特征点的提取和匹配画面清晰稳定特征点才能连续跟踪上。选单目模式还有个实际原因省掉深度相关的所有麻烦。RGB-D 模式下你必须处理深度图和彩色图的对齐、深度边缘的飞点、深度传感器的测量噪声以及两个传感器之间的外参标定误差。这些工作不是不能做但会分散你对 SLAM 算法本身的注意力。单目模式里D435i 就是一个普通 USB 摄像头标定只涉及一个传感器数据链路短出问题也容易定位。从算法验证的角度看单目也是 SLAM 里最苛刻的配置。单目能稳定跑通后面换双目或者 RGB-D 基本是无痛的反过来长期用 RGB-D 提供的深度尺度再回头调单目会非常不适应。所以如果你刚接触 SLAM我的建议是先单目把初始化、追踪、回环这套流程吃透再碰深度。1.2 无 ROS 方案的收益与代价ROS 版不是不能用而是多了一层中间件之后变量就多了。跑一个 ROS 版 ORB-SLAM2 之前你得有 roscore、相机驱动节点、话题通信、时间同步任何一个环境问题都要顺着节点链路排查。对于纯算法验证大部分 ROS 能力根本用不上反而是负担。无 ROS 的收益我整理成几条单进程内完成采集、转换、追踪数据流透明出问题用日志和调试器就能定位不依赖 ROS 版本和发行版换机器、换平台的迁移成本低少了话题的序列化和跨线程拷贝端到端延迟更小后续想把 SLAM 集成进自研框架这套代码结构可以直接迁过去代价也明确相机驱动要自己写可视化只能靠 ORB-SLAM2 自带的 Pangolin。好在 D435i 有官方 SDK这块工作量不算大。我自己用下来无 ROS 方案从零到跑通大概半天而纠结于 ROS 环境的那段时间反而远不止半天。1.3 数据链路从像素到地图只走三步整个系统的模块可以分成三层层次模块职责数据采集librealsense pipeline获取 RGB 帧并附带时间戳格式转换OpenCV Mat把 rs2::frame 转成 ORB-SLAM2 可识别的图像SLAM 主系统ORB_SLAM2::System追踪、局部建图、回环检测、全局优化、可视化数据流就是三步librealsense - cv::Mat - System::TrackMonocular(im, t)。这里有个关键点ORB-SLAM2 的单目接口只接收图像和时间戳两个参数这意味着任何能按序输出图像的摄像头都可以接进来。D435i、普通 USB 摄像头、工业相机、甚至离线视频文件接口完全一样。这也是无 ROS 方案可迁移性最强的地方底层算法根本不关心图像从哪来。ORB-SLAM2 内部由 Tracking、LocalMapping、LoopClosing 三个线程组成对外接口却极简。主线程只管按时喂帧地图生成、关键帧管理、回环修正、全局优化全在内部完成。这种设计让外部集成变得非常简单也正因为如此绕过 ROS 直接调用它的成本很低。2. 环境搭建依赖于版本匹配实战2.1 依赖清单与安装顺序ORB-SLAM2 本身依赖的库不算多但版本匹配有讲究。我这边长期使用的组合是Eigen 3.3.xheader-only装系统包就行Pangolin源码编译用于可视化OpenCV优先 3.4.x新版 4.x 需要打补丁librealsense建议 2.50 以上的版本安装顺序我建议按 Eigen - OpenCV - Pangolin - librealsense - ORB-SLAM2 来。Eigen 没什么坑sudo apt install libeigen3-devPangolin 编译前要把图形库装齐否则编到一半报错再回头补比较浪费时间sudo apt install libglew-dev libglfw3-dev libgl1-mesa-dev libegl1-mesa-dev git clone https://github.com/stevenlovegrove/Pangolin.git cd Pangolin mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease make -j$(nproc)librealsense 建议直接用官方 apt 源比源码编译省心固件工具和示例程序都会带上sudo apt install librealsense2-dev librealsense2-utils rs-enumerate-devices插上 D435i 后rs-enumerate-devices能看到设备信息就说明 SDK 工作正常。顺便确认一下固件版本太旧的固件建议用官方工具更新一次能减少 USB 兼容性方面的玄学问题。2.2 ORB-SLAM2 编译的 OpenCV 版本之坑ORB-SLAM2 这个学术开源版本的代码年龄不小了新编译器和新依赖库上编译总会遇到幺蛾子。最大的坑集中体现在 OpenCV 4.x 上老版 C API 被移除比如CV_LOAD_IMAGE_UNCHANGED部分源文件缺少#include opencv2/core/eigen.hppDBoW2 三方库在 C17 下对标准库类型别名的写法也会报错。如果你想省事两个选择直接用系统自带的 OpenCV 4.x然后手动打补丁。补丁内容基本就是把旧接口换成新接口、补头文件、把typedef typename改成using这类改动仓库的 issue 区都有现成方案改完重新编译就行或者自己源码编译一个 OpenCV 3.4.x一劳永逸我自己的开发机是源码编了 OpenCV 3.4.16 再配 ORB-SLAM2之后几乎没有再碰过兼容问题。如果你在 Ubuntu 18.04 上系统自带的 OpenCV 3.2 其实也够用编译 ORB-SLAM2 基本不会报错。依赖就绪后编译 ORB-SLAM2cd ORB_SLAM2 chmod x build.sh ./build.sh首次构建大约 10-20 分钟具体取决于机器性能。构建完成后记得解压词典文件cd Vocabulary tar -xf ORBvoc.txt.tar.gz这个 ORB 词典是跑任何数据都要用的漏掉它程序会直接报错退出。提示如果编译 g2o 时报 Eigen 对齐相关的错误多半是 Eigen 版本太新导致的。可以换回 Eigen 3.3.x或者给 CMake 加上-DEIGEN_DONT_VECTORIZE之类的兼容选项。别硬刚编译错误版本回退是最快的路。2.3 免棋盘格标定直接读取 D435i 内参单目 SLAM 最重要的输入之一是相机内参。D435i 的出厂标定存在设备内部用 librealsense 可以直接读出来根本不需要棋盘格。前提是必须在配置好流之后读取因为 SDK 已经根据你请求的分辨率把内参做了对应的缩放。读取内参的核心代码auto color_profile pipe.get_active_profile() .get_stream(RS2_STREAM_COLOR) .asrs2::video_stream_profile(); auto intr color_profile.get_intrinsics(); std::cout fx, fy intr.fx , intr.fy std::endl; std::cout cx, cy intr.ppx , intr.ppy std::endl; std::cout k1, k2, p1, p2, k3 ; for (int i 0; i 5; i) std::cout intr.coeffs[i] ; std::cout std::endl;把读出来的值填进 ORB-SLAM2 的 YAML 设置文件。D435i 彩色相机的畸变模型是布朗-康拉迪模型和 ORB-SLAM2 默认使用的针孔加畸变模型一一对应5 个畸变系数可以直接映射不需要转换。YAML 文件大致长这样%YAML:1.0 Camera.fx: 616.5 Camera.fy: 615.8 Camera.cx: 322.2 Camera.cy: 242.1 Camera.k1: 0.0 Camera.k2: 0.0 Camera.p1: 0.0 Camera.p2: 0.0 Camera.k3: 0.0 Camera.fps: 30.0 Camera.RGB: 0 ORBextractor.nFeatures: 1500 ORBextractor.scaleFactor: 1.2 ORBextractor.nLevels: 8 ORBextractor.iniThFAST: 20 ORBextractor.minThFAST: 7 Viewer.KeyFrameDensity: 40 Viewer.PointSize: 2 Viewer.CameraSize: 0.08 Viewer.CameraLineWidth: 3上面 fx/fy/cx/cy 只是示例每台设备的标定值都不一样务必从你自己的设备读取千万别图省事去下载别人分享的 YAML 直接用。内参不对的直接后果是投影模型失真地图扭曲、频繁丢追踪到时候排查起来比写配置麻烦得多。Camera.RGB: 0表示图像数据在内存中是 BGR 顺序对应取流时用RS2_FORMAT_BGR8格式。如果改成RS2_FORMAT_RGB8这里就要设成 1。ORB 特征提取本身就是灰度操作颜色顺序对追踪精度影响不大但保持配置和取流格式一致能避免后续做可视化或调试时踩坑。3. 核心代码桥接 D435i 与 ORB-SLAM23.1 取流、转格式、喂 SLAM 的最小实现整个桥接程序的核心就很短配置 D435i 彩色流、启动 SLAM 系统、循环取帧并调用追踪接口。下面这段是我实际在用的最小实现可以直接抄走改改#include librealsense2/rs.hpp #include opencv2/opencv.hpp #include System.h int main(int argc, char** argv) { if (argc 3) { std::cerr Usage: argv[0] ORBvoc.txt settings.yaml [bag_file] std::endl; return -1; } // 1. 配置 D435i 彩色流 rs2::pipeline pipe; rs2::config cfg; if (argc 4) { cfg.enable_device_from_file(argv[3]); // 离线回放 bag用于复现问题 } else { cfg.enable_stream(RS2_STREAM_COLOR, 640, 480, RS2_FORMAT_BGR8, 30); } pipe.start(cfg); // 2. 读取内参验证标定参数 auto color_profile pipe.get_active_profile() .get_stream(RS2_STREAM_COLOR) .asrs2::video_stream_profile(); auto intr color_profile.get_intrinsics(); std::cout fx intr.fx fy intr.fy cx intr.ppx cy intr.ppy std::endl; // 3. 启动 ORB-SLAM2 系统单目模式开启可视化 ORB_SLAM2::System slam(argv[1], argv[2], ORB_SLAM2::System::MONOCULAR, true); // 4. 主循环 while (true) { rs2::frameset frames pipe.wait_for_frames(); rs2::frame color frames.get_color_frame(); double t color.get_timestamp() / 1000.0; // 毫秒转秒 cv::Mat im(cv::Size(640, 480), CV_8UC3, (void*)color.get_data(), cv::Mat::AUTO_STEP); cv::Mat imGray; cv::cvtColor(im, imGray, cv::COLOR_BGR2GRAY); slam.TrackMonocular(imGray, t); } slam.Shutdown(); slam.SaveKeyFrameTrajectoryTUM(KeyFrameTrajectory.txt); return 0; }这段代码里有几个细节值得展开。第一cv::Mat(..., (void*)color.get_data(), cv::Mat::AUTO_STEP)是浅拷贝直接指向 RealSense 内部缓冲区没有复制数据。RealSense 帧的生命周期由局部变量frames管理只要在当前循环内用完就行。如果想把图像保存下来或者放到队列里异步处理一定要clone()深拷贝否则后续帧更新会覆盖前面的数据。第二灰度转换放在送入 SLAM 前做。ORB-SLAM2 内部收到 3 通道图像时也会自动转灰度但显式转换可以让你明确知道数据格式也方便后续调试时直接复用灰度图。第三退出前调用Shutdown()是很重要的一步。ORB-SLAM2 内部有三个线程在跑不等待线程结束就直接退出很容易触发段错误。跑完的轨迹文件会保存为KeyFrameTrajectory.txt这是你后续评估算法精度的核心产物。3.2 时间戳的处理细节时间戳是单目 SLAM 里容易被忽略的因素。ORB-SLAM2 使用时间戳计算帧间运动估计虽然多数代码路径只关心相对时间差但时间戳必须严格单调递增否则内部的状态机可能出问题。librealsense 返回的frame.get_timestamp()单位是毫秒基于硬件时钟。这里除以 1000 转成秒只是为了符合常见数据集的习惯。如果你更喜欢用毫秒也可以直接把单位调整到 YAML 里的Camera.fps匹配但建议全程统一用秒省得后面处理轨迹时还要来回换算。有一个隐藏坑需要注意如果你的主循环处理速度跟不上相机帧率wait_for_frames()内部会缓存帧导致你拿到的帧时间戳越来越旧端到端延迟越来越大。解决办法是每次取帧后主动把队列排空只保留最新一帧再送入 SLAMrs2::frameset frames pipe.wait_for_frames(); rs2::frameset latest frames; while (pipe.poll_for_frames(frames)) { latest frames; }这个“取最新帧”的模式在低端 CPU 上尤其有用。SLAM 追踪宁可偶尔跳帧也不能延迟越来越大因为时间戳上的延迟积累会让整个轨迹失真。3.3 编译运行与启动日志解读桥接程序的 CMakeLists 需要同时链接 OpenCV、librealsense 和 ORB-SLAM2 的库find_package(OpenCV REQUIRED) find_package(realsense2 REQUIRED) add_executable(d435i_mono main.cpp) target_include_directories(d435i_mono PRIVATE ${PROJECT_SOURCE_DIR}/../../../) target_link_libraries(d435i_mono ${OpenCV_LIBS} realsense2 ${PROJECT_SOURCE_DIR}/../../../lib/libORB_SLAM2.so )编译运行mkdir build cd build cmake .. make -j4 ./d435i_mono ../Vocabulary/ORBvoc.txt ../D435i_mono.yaml运行时如果提示找不到libORB_SLAM2.so八成是LD_LIBRARY_PATH没包含 ORB-SLAM2 的 lib 目录用export LD_LIBRARY_PATH/path/to/ORB_SLAM2/lib:$LD_LIBRARY_PATH解决。程序启动后Pangolin 窗口会弹出里面能看到当前帧、特征点、地图点和关键帧位姿。我把启动后的状态分为三个阶段初始化阶段画面只有当前帧和特征点状态显示为 INITIALIZING。这个阶段通常持续 1-3 秒正常工作阶段初始化成功后地图点突然出现状态变为 WORKING终端开始刷关键帧信息回环阶段走完一段路回到起始位置附近系统会触发回环检测地图会整体修正一次如果你发现状态长期停在 LOST 或者 INITIALIZING不要盲目跑下去先检查第 4 节里的初始化手法。4. 实操指南与参数调优4.1 一次性跑通的标准操作流程从拿到设备到稳定建图我整理了一套可以直接照抄的操作流程给相机上电等 1-2 秒让传感器和自动曝光先稳定下来启动程序确认 Pangolin 窗口出现左下角能看到当前特征点数量把相机对准纹理丰富的区域书架、海报墙、显示器都行避开大块白墙和重复纹理对着场景缓慢做“左右、上下”小幅平移千万不要只在原地转圈初始化成功后地图上会突然出现一批 3D 地图点和关键帧位姿开始正常运动时保持匀速转角处减速尽量避免剧烈晃动回到起点附近观察回环检测是否触发地图是否整体调整第 4 步是最容易卡住的环节。很多新手把“晃动相机”理解成“原地转动”但单目初始化需要平移视差旋转带来的视差变化不足以可靠估计本质矩阵。正确做法是让相机有明显的左右、前后位移幅度不用大但视觉上要能看到物体相对位置明显变化。我习惯先左右平移几下再前后推拉几下一般几秒钟内就能初始化成功。另一个容易忽略的点是场景选择。对着白墙、地板这类无纹理区域特征点数量不够初始化会一直失败。如果实在找不到理想场景可以在桌面上放几本书或者一张海报人为制造纹理。4.2 ORB 特征参数配置速查ORBextractor 的几个参数直接决定了追踪稳定性。nFeatures是每帧提取的特征点上限我在 D435i 的 640x480 图像上实测过几组配置nFeatures表现500特征稀疏快速运动时容易丢追踪1000室内场景和中等速度下勉强够用1500大多数场景下兼顾稳定性和帧率我的常驻配置2000特征多了但提取耗时长帧率反而下降特征点不是越多越好。2000 个特征在 640x480 下每帧提取要额外花几毫秒匹配耗时也增加最终帧率可能从 30 掉到 20 以下。SLAM 系统最怕的不是单帧质量差而是帧率不稳。稳定 30fps 下 1000-1500 个特征效果明显好于 20fps 下 2500 个特征。iniThFAST和minThFAST是 FAST 角点检测的阈值默认 20/7 在大多数室内光照下没问题。如果你的运行环境偏暗可以把iniThFAST降到 10让系统在暗光下也提取到足够角点。但阈值越低角点质量越差误匹配概率升高所以不要盲目往低调。先保持默认值跑通流程再根据实际追踪质量微调。4.3 单目尺度问题怎么看待单目 SLAM 没有真实的物理尺度轨迹和地图的单位是“相对单位”。D435i 明明自带深度传感器但单目模式下这套代码完全不碰深度数据所以尺度漂移是必然存在的它不是 bug而是单目几何的本质属性。怎么理解尺度漂移你实际走了 5 米轨迹上可能显示 4.8 米走了 20 米可能显示 22 米。在长走廊、特征深度分布单一的场景里这个误差会累积得更明显。我见过有人看到轨迹长度对不上就开始怀疑标定有问题排查半天其实完全是单目尺度导致的。如果你只是做算法验证尺度漂移可以接受重点观察轨迹的形态和回环效果。如果最终要做测距或者驱动实体机器人建议换 RGB-D 模式或者接上 D435i 的 IMU 做视觉惯性融合。折中的办法是先用单目跑出轨迹事后用深度信息对轨迹做一次尺度对齐。这种方法在算法评估阶段尤其好用可以让你纯看单目几何本身的精度而不受深度噪声干扰。5. 问题排查与避坑实录5.1 常见问题速查表把我在实际使用中遇到的高频问题整理成一张表方便快速定位问题现象原因与解决初始化失败地图一直为空状态停在 INITIALIZING平移视差不够对着无纹理墙相机在剧烈抖动。先静置一秒再缓慢平移频繁丢追踪状态反复切回 LOST运动太快内参不准特征点太少。降速、核对内参、调大 nFeatures地图大面积弯曲轨迹越走越歪单目尺度漂移累积。控制转角速度尽量制造回环机会帧率上不去只有 10-15fps特征点过多CPU 占用高OpenCV 优化不足。降 nFeatures 或分辨率画面忽明忽暗特征点数剧烈波动自动曝光在干扰。固定曝光时间关闭自动曝光USB 中断取流报错或程序退出换 USB 3.0 原生接口不要用 HUB 和前置面板检查供电5.2 自动曝光SLAM 的头号敌人D435i 的自动曝光在普通拍照场景下挺好用但在 SLAM 里是头号不稳定因素。曝光变化会改变图像整体亮度亮度的波动直接影响 FAST 角点响应导致同一区域的角点这一帧在、下一帧消失特征匹配连续性变差追踪质量肉眼可见地下降。我自己的做法是一上来就固定曝光rs2::color_sensor color_sensor pipe.get_active_profile().get_device().firstrs2::color_sensor(); color_sensor.set_option(RS2_OPTION_ENABLE_AUTO_EXPOSURE, 0); color_sensor.set_option(RS2_OPTION_EXPOSURE, 200); // 单位 us按环境光照调整曝光值怎么选先用 RealSense Viewer 打开自动曝光对着你要跑的主要场景看它稳定后的值然后把这个值写死。室内正常灯光下 D435i 的曝光通常在 100-500us 之间。如果场景跨度很大室内外切换固定曝光两边都顾不好这时候才考虑保留自动曝光但要做好频繁丢追踪的心理准备。另外注意手动固定曝光后刚到新场景的第一两秒图像可能过暗或过亮所以启动程序后先等一两秒再开始移动相机让传感器稳定下来。5.3 离线 bag 复现调试法SLAM 调试最头疼的问题是问题不可复现。同一条路线上一轮好好的下一轮就丢了。为了稳定复现问题我强烈建议用离线的 bag 回放。具体分两步。第一步用 realsense-viewer 录制彩色流realsense-viewer在界面里勾选彩色流点击 Record 录成.bag文件。第二步把 bag 路径传给桥接程序./d435i_mono ../Vocabulary/ORBvoc.txt ../D435i_mono.yaml /path/to/record.bag程序会用完全相同的帧序列回放这样你就可以反复调整 YAML 参数、改代码完全不用管相机位置、曝光、运动轨迹是否一致。这个技巧对调参效率的提升是质的。我后来调特征参数和曝光相关逻辑基本都靠回放一个 bag 能反复用几十次。录 bag 时有个注意点录了什么分辨率的流回放时就按什么分辨率用不要再在代码里enable_stream限制分辨率否则图像尺寸不匹配程序会直接出问题。5.4 性能优化备忘录如果你的最终运行环境不是高性能台式机下面几条优化建议值得关注Pangolin 可视化本身消耗不少 CPU 和 GPU。算法验证完成后把System构造函数的最后一个参数bUseViewer改成false负载会明显下降640x480 是性价比最高的分辨率。1280x720 下 ORB 特征提取耗时成倍增加对单目追踪帮助有限在嵌入式平台上可以先把图像降到 320x240 快速验证流程确认没问题再上全分辨率在拿到帧后立刻用 3.2 节里的“排空队列只取最新帧”策略避免延迟积累ORB-SLAM2 自身是多线程的如果发现 OpenCV 也在抢线程资源可以在主循环前调用cv::setNumThreads(0)关闭 OpenCV 内部多线程每次跑完建议把KeyFrameTrajectory.txt随手重命名存好文件名带上日期和场景备注。时间长了你会惊讶地发现很多看起来玄学的参数问题对比不同日期的轨迹文件就能看出规律。单目 SLAM 调参没什么捷径就是一次一次跑、一次一次对比、然后把有效参数沉淀成自己的配置文件。这套链路我维护了挺久最大的感受是SLAM 本身难但别让工具链成为难上加难的那一环。无 ROS 的核心价值在于你终于能直视数据流本身知道图像是哪来的、时间戳是什么、每一步做了什么。调试的时候没有中间层跳出来干扰你所有黑盒子只剩 ORB-SLAM2 自己的三个线程这和 ROS 节点之间互相等待的感觉完全不一样。拿到 D435i 之后先别急着装 ROS照着这篇文章跑通单目链路你会发现 ORB-SLAM2 离你比想象中近得多。