资讯详情

无ROS环境下Realsense D435i单目跑通ORB_SLAM2的完整实践

📅 2026/10/16 5:48:25 | 华诺云谱 👁 阅读
无ROS环境下Realsense D435i单目跑通ORB_SLAM2的完整实践
最近我在做传感器选型评估时需要在没有ROS的环境里用Realsense D435i单目跑ORB_SLAM2。查了一圈资料发现大部分教程都默认有ROS环境先装realsense-ros、再起roscore、还要处理TF和launch文件。对于只想验证单目SLAM效果的场景来说这一整套属实有点重。我把D435i通过librealsense的C接口直接读取彩色帧喂给ORB_SLAM2的TrackMonocular最终得到一条可保存的轨迹和实时地图窗口。整个过程不依赖ROS编译和运行都在一个普通终端里完成非常适合刚接触视觉SLAM、手头有D435i但不想被ROS环境劝退的同学。这篇就把我这个无ROS版的完整路线、依赖版本、参数配置和踩过的坑都写清楚。1. 为什么我要抛开ROS让D435i直接对接ORB_SLAM21.1 这是一个真需求还是一个伪需求遇到过不少朋友一上来就问想用D435i跑ORB_SLAM2是不是必须装ROS其实ORB_SLAM2本身的主程序只是一个普通C可执行文件它官方示例里的单目模式是从文件夹按顺序读取图像序列比如TUM格式和KITTI格式。换句话说它只要求外部提供一个能持续输出图像帧的数据源。D435i正好可以通过librealsense的C接口直接把彩色帧转成cv::Mat完全不需要中间再包一层ROS通信。我最初也被固有思路带着走先试了带ROS的集成包光是把彩色流发布、订阅、格式转换走通就花了一个下午后面还面临一堆版本匹配问题。后来我把需求拆开想单目SLAM本质上只关心“图像帧时间戳相机内参”这三个东西ROS在这个链路里并没有提供不可替代的能力反而增加了环境复杂度。于是我把ROS从方案里拿掉整个流程立刻清爽很多。1.2 有ROS和无ROS差在哪儿如果你正在两种路线之间犹豫可以从下面几个维度评估对比维度无ROS直连基于ROS的realsense-ros方案环境安装量只装librealsense和算法依赖需要ROS本体、桥接包、各种依赖树数据链路直接调用SDK拿帧简单直接相机节点发布话题再订阅话题调试难度终端打印即可定位问题需要排查节点运行状态和话题频率部署体积可以做到一个可执行文件需要整套ROS运行时支持多传感器扩展自己维护数据同步逻辑ROS的tf和时间同步机制更成熟适合场景算法验证、快速原型、轻量部署机器人整机、多传感器协作、长期维护这个表不是一个“谁替代谁”的结论。我的判断是如果你只是想快速评估D435i在某个场景下单目定位稳不稳定或者想专心研究ORB特征点提取、地图构建、回环检测这些算法本身无ROS路线会节省大量时间。但如果你后面要做的是一台完整的机器人需要把相机与底盘、激光、导航模块组合起来那ROS的通信框架确实能省掉很多重复劳动。别把无ROS当成万能方案想清楚自己处在哪个阶段再选。1.3 哪些场景更适合走无ROS直连结合我自己的实践三条路线的适配场景非常清晰。第一传感器选型评估。我这次就是在几个相机之间做对比每换一个相机只需要改数据读取层和内参配置无ROS的轻量结构让横向对比变得很快。第二嵌入式或低配置机器上的算法原型。不需要后台常驻一堆节点进程一个可执行文件跑起来资源占用可控。第三课程设计或毕设里的SLAM入门。初学者很容易被ROS的话题通信绕晕直接看ORB_SLAM2的代码反而更好理解特征点匹配和位姿优化的关系。2. 数据适配层怎么写librealsense到TrackMonocular的距离只有几十行2.1 最小可运行的读取循环D435i的数据读取核心是librealsense的pipeline机制。下面这段代码是我一直保留的最小模板#include librealsense2/rs.hpp #include opencv2/opencv.hpp #include System.h using namespace std; int main(int argc, char** argv) { if (argc ! 3) { cerr 用法: mono_d435i 词典路径 配置文件路径 endl; return -1; } // 1. 配置并启动D435i彩色流 rs2::pipeline pipe; rs2::config cfg; cfg.enable_stream(RS2_STREAM_COLOR, 640, 480, RS2_FORMAT_BGR8, 30); pipe.start(cfg); // 2. 初始化ORB_SLAM2单目系统 ORB_SLAM2::System slm(argv[1], argv[2], ORB_SLAM2::System::MONOCULAR, true); // 3. 主循环拿帧 - 转Mat - 送入SLAM for (;;) { rs2::frameset frames pipe.wait_for_frames(); rs2::video_frame color_frame frames.get_color_frame(); if (!color_frame) continue; cv::Mat img(cv::Size(640, 480), CV_8UC3, (void*)color_frame.get_data(), cv::Mat::AUTO_STEP); double timestamp color_frame.get_timestamp() / 1000.0; slm.TrackMonocular(img, timestamp); } slm.Shutdown(); return 0; }这段代码逻辑并不复杂但有个细节值得注意cv::Mat构造时传入了color_frame.get_data()的指针也就是说这个Mat和D435i内部帧缓冲区共享内存没有发生拷贝所以性能开销很低。但这也意味着你不能把这份Mat存到容器里等下一帧再处理因为下一帧到来后缓冲区内容会被覆盖。正确用法是每一帧拿到后立即使用就像上面的循环体里做的那样。2.2 时间戳到底该用哪个ORB_SLAM2的TrackMonocular第二个参数是时间戳单位是秒会被写进关键帧和TUM轨迹文件。D435i帧对象的get_timestamp()返回的是硬件时钟的毫秒数所以我在代码里除以1000转成秒。不过这里有一个实际现象D435i刚启动时硬件时间戳的起点不是0而且不同固件版本下会有差异。如果只拿来做单目SLAM轨迹展示影响不大因为VSLAM关心的是帧间时间间隔不是绝对时间。但如果你后面要把轨迹和别的传感器真值对齐建议做一个零偏校正static double t0 0.0; double ts color_frame.get_timestamp() / 1000.0; if (t0 0.0) t0 ts; double timestamp ts - t0;这样轨迹时间轴从0开始后续做时间对齐会省很多事。我最早没有做这个处理后来要跟一个外部测量设备对比时间差才发现轨迹起点是一个很大的随机数值计算时间偏移时非常别扭。2.3 为什么不选RGB8而选BGR8OpenCV里Mat默认是BGR通道顺序所以我在enable_stream里直接指定RS2_FORMAT_BGR8这样SDK输出的彩色帧不需要额外转换就能生成一个正常的BGR图像。如果选了RS2_FORMAT_RGB8就得手动做cv::cvtColor不仅多一次像素遍历还容易搞混通道顺序。ORB_SLAM2的配置文件里有一个Camera.RGB参数这个参数决定了它内部显示和保存地图时按什么通道解释。为了避免颜色错乱导致特征点可视化看起来异常我建议让链路保持为BGR并将配置文件里的Camera.RGB设为1。实际测试中如果通道顺序反了ORB特征点依然能提出来SLAM也能跑但彩色地图的颜色会是偏色状态排查问题时容易误判。2.4 只开彩色流还是同时开深度标题既然说是单目深度流就没有必要开。librealsense在显式启用彩色流之后默认不会自动打开深度流所以上面的代码里没有额外写深度配置。把深度流关掉的好处很明显降低USB带宽占用、减少功耗、减少SDK内部对齐计算。D435i的深度模块在工作时会有一定的散热和功耗在某些USB控制器上彩色和深度同时开启会导致帧率波动单开彩色流则稳定很多。如果你以后想从单目切到RGB-D模式再在cfg里补上深度流配置并对深度帧和彩色帧做对齐那是后话。单目的第一步代码越少越好先把链路跑通。3. 无ROS编译清单版本搭配和CMake里的几个坑3.1 依赖版本建议ORB_SLAM2这个项目虽然出来时间不短但代码风格比较依赖老版本的视觉库。我的建议是不要盲目追求最新版本稳定复现比版本新更重要。下面是我用下来最稳的组合依赖推荐版本说明OpenCV3.4.x4.x也能用但要改几处APIPangolin最新稳定版可视化窗口依赖需要GLEW等库Eigen系统默认主要是头文件ORB_SLAM2里大量使用librealsense2.50以上要包含CMake配置文件供find_package查找g2o、DBoW2项目自带不需要单独安装源码在Thirdparty目录OpenCV版本是最大的分水岭。如果你是全新环境没有历史包袱直接装OpenCV 3.4.x最省心。ORB_SLAM2源码里有不少老式头文件写法比如#include opencv2/features2d/features2d.hpp这种路径在OpenCV 4里已经不存在会导致编译失败。用3.4版本基本一遍过。3.2 编译顺序先Thirdparty后主工程从源码仓库把ORB_SLAM2拉下来之后第一件事不是直接cmake而是先编译自带的三方库。项目根目录下有现成的build.sh它会依次编译Thirdparty里的DBoW2和g2o再编译Examples。如果愿意也可以手动分步执行cd Thirdparty/DBoW2 mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease make -j4 cd ../../g2o mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease make -j4为什么要先编译这两个库因为ORB_SLAM2的顶层CMakeLists会依赖它们生成的头文件和库文件主工程的链接阶段需要找到这些目标。顺序不对的话后面主工程cmake时会报找不到Missing dependencies。这里我遇到过编译器较新、Eigen对齐报错的情况一般加上-DCMAKE_BUILD_TYPERelease就能解决不要用默认的Debug模式。3.3 OpenCV 4带来的改动清单如果你机器上已经装了OpenCV 4又不想为了ORB_SLAM2再装一份3.4可以接受改几处代码。我整理出的必要修改点第一顶层CMakeLists.txt里搜索OpenCV的部分原来写的是find_package(OpenCV 3.0 QUIET)如果没找到会回退到2.4在OpenCV 4环境下要改成明确查找4.x并且建议删掉后面的回退逻辑。第二涉及旧头文件路径的全部调整比如把features2d/features2d.hpp改成features2d.hpp把nonfree/nonfree.hpp这类路径一并清理。第三旧的颜色转换宏CV_RGB2GRAY、CV_GRAY2BGR、CV_BGR2GRAY要改成OpenCV 4的cv::COLOR_RGB2GRAY这种新枚举。改完之后大体能编译但我要坦白说一句如果你不是对OpenCV 4有硬性依赖真的建议直接用3.4。这十几个小改动本身不复杂但分散在多个源文件里漏改任何一个都要重新编译排查时间成本远高于多装一个旧版本库。3.4 新增mono_d435i入口文件后的CMake修改我自己没有去改官方示例文件而是在Examples/Monocular目录下新增了一个mono_d435i.cc然后在同目录的CMakeLists.txt里添加一个可执行目标find_package(realsense2 REQUIRED) add_executable(mono_d435i ./mono_d435i.cc) target_link_libraries(mono_d435i ${PROJECT_NAME} ${OpenCV_LIBS} realsense2)这样做的最大好处是不碰原有的mono_tum、mono_kitti等示例避免自己的修改影响已有代码。编译时需要在CMake里把librealsense的CMake模块路径告诉cmake。如果librealsense是默认安装到/usr/local的通常不会出问题如果自定义了安装路径要手动指定cmake .. -DCMAKE_BUILD_TYPERelease -Drealsense2_DIR/your/path/lib/cmake/realsense2我记得第一次编译时就是因为没有设置这个变量cmake报找不到realsense2当时一度以为是SDK没装好折腾了一会儿才发现是CMake模块搜索路径没对上。4. 把相机内参写进配置文件之前先搞懂这几个概念4.1 内参从哪里读取D435i的彩色相机内参不是需要自己盲猜的librealsense提供两种方式查看。第一种是命令行工具终端里直接执行rs-enumerate-devices输出信息里有一整段Color stream的Intrinsic参数包括fx、fy、ppx、ppy还有distortion model和coefficients。第二种是在代码里实时读取rs2::stream_profile color_stream pipe.get_active_profile().get_stream(RS2_STREAM_COLOR); rs2::video_stream_profile vsp color_stream.asrs2::video_stream_profile(); auto intr vsp.get_intrinsics(); std::cout fx intr.fx fy intr.fy cx intr.ppx cy intr.ppy model intr.model std::endl;这里有一个非常重要的认知内参是和分辨率强相关的。D435i在640x480下读到的fx、cx是一个值切到1280x720后完全变掉。所以不要拿着出厂标称内参在不同分辨率下通用一定要在你要跑的那个分辨率下读取。我实际拿到的示例设备在640x480下约为fx386.3、fy386.3、cx322.5、cy245.4但每台相机都有个体差异直接照抄别人的参数不是一个好习惯。4.2 畸变模型差异与ORB_SLAM2的匹配D435i彩色镜头官方给的是反畸变模型和ORB_SLAM2配置文件里那套K1/K2/P1/P2径向切向模型并不完全是一回事。但好消息是D435i彩色镜头畸变非常小实测中直接当零畸变模型使用对ORB特征点提取和匹配的影响可以忽略。所以配置文件里的畸变参数全部填0是成立的做法。如果你以后要对精度有更高要求可以用棋盘格通过OpenCV做一次完整标定把Brown模型的系数求出来填进去。标定时要注意几点画面中棋盘格角度要丰富不要老是正面拍保证焦距和实际运行场景有重叠最好固定D435i的自动曝光和自动白平衡再标定否则不同亮度下角点提取可能会有差异。标定完如果发现重投影误差很大优先检查棋盘格尺寸是否写错、角点数量是否按领先提取规则设置的。4.3 完整的单目配置文件参考我用的配置文件是YAML格式基于ORB_SLAM2的mono_tum配置改的。这里给一份可直接用的模板%YAML:1.0 Camera.fx: 386.3 Camera.fy: 386.3 Camera.cx: 322.5 Camera.cy: 245.4 Camera.k1: 0.0 Camera.k2: 0.0 Camera.p1: 0.0 Camera.p2: 0.0 Camera.width: 640 Camera.height: 480 Camera.fps: 30.0 Camera.bf: 30.0 Camera.RGB: 1 ThDepth: 40.0 Viewer.KeyFrameSize: 0.05 Viewer.KeyFrameLineWidth: 1.0 Viewer.GraphLineWidth: 0.9 Viewer.PointSize: 2.0 Viewer.CameraSize: 0.08 Viewer.CameraLineWidth: 3.0 Viewer.ViewpointX: 0.0 Viewer.ViewpointY: -0.7 Viewer.ViewpointZ: -1.8 Viewer.ViewpointF: 500.0Camera.bf在单目模式下本来用不到但配置文件里必须保留这个字段否则程序读取时可能解析失败。ThDepth是单双目共用的深度阈值参数单目下对纯视觉的过程影响不大保持默认就好。Viewer下面那一段是可视化窗口的显示参数我一般只改ViewpointF调整视野远近。4.4 单目初始化为什么相机不能静止这是单目SLAM和RGB-D模式最明显的区别。RGB-D可以从深度图像直接恢复空间点单目只能依靠多个视角之间的视差进行三角化来产生初始地图。相机静止时连续帧的画面几乎完全一样特征点匹配再多也产生不了有效视差程序就会一直停在Initialization状态地图始终是空的。我最初把D435i摆在桌面上对着前面等了几分钟都没有初始化成功一度怀疑是配置写错了。后来才意识到单目的这个固有特性。正确姿势是拿起相机对着有明显角点和纹理的墙面或桌面区域做几组左右、前后平移中间带一点小幅度旋转几秒后屏幕就能从Initialization跳转到Tracking状态。初始化成功后要马上把运动降下来稳定跟踪一段时间再逐步加快如果一开始就剧烈晃动很容易在刚起步阶段触发Tracking Lost。5. 实测中那几个让我反复怀疑配置的问题5.1 过曝与白平衡特征点一多一少的变化D435i的彩色相机默认是自动曝光和自动白平衡。在室内向阳环境下自动曝光很容易让画面过曝窗户或天空部分整片发白。过曝区域里ORB算法提取不到足够的角点特征点被迫集中到阴影部分很容易导致匹配分布不均和跟踪不稳。我建议在启动管道后手动把自动曝光和自动白平衡关掉。librealsense的传感器设置接口有两种写法一种是通过索引先找到传感器更稳妥的是遍历设备上的所有传感器判断谁支持这个选项就设置谁auto sensors pipe.get_active_profile().get_device().query_sensors(); for (auto s : sensors) { if (s.supports(RS2_OPTION_ENABLE_AUTO_EXPOSURE)) s.set_option(RS2_OPTION_ENABLE_AUTO_EXPOSURE, 0.0f); if (s.supports(RS2_OPTION_ENABLE_AUTO_WHITE_BALANCE)) s.set_option(RS2_OPTION_ENABLE_AUTO_WHITE_BALANCE, 0.0f); if (s.supports(RS2_OPTION_EXPOSURE)) s.set_option(RS2_OPTION_EXPOSURE, 100.0f); }RS2_OPTION_EXPOSURE的单位是微秒具体数值要根据环境亮度调整。我的经验是在室内日光灯下从几十微秒到一两百微秒之间试室外强光下要往下压画面以不过曝、运动时没有明显拖影为准。环境暗时快门会变慢手稍微一晃就糊单目SLAM对这种运动模糊非常敏感宁可比正常稍暗一点也要保住清晰度。5.2 反复Tracking Lost之后我按这些顺序查跑单目ORB_SLAM2最影响心态的就是Tracking Lost。我经历过几次反复丢失之后总结出一个排查顺序先看运动速度。刚初始化完就快速甩动必丢。单目系统对突然的大平移和旋转非常敏感尤其是旋转过快时相邻帧的共同视角太少特征点跟踪自然断裂。再看纹理条件。对着白墙、天花板、空旷地面谁来了都救不了这是数据源的问题不是代码问题。然后查内参。如果fx、fy填错投影计算和特征匹配的分值计算都会偏表现出的症状就是明明场景正常但跟踪非常脆。接下来是图像方向。D435i如果安装角度特殊画面可能是倒的或镜像的ORB_SLAM2没有现成的翻转参数需要在取图后用cv::flip处理。要注意的是不要试图通过改内参里的cx、cy来纠正画面方向那个方向完全不对。最后检查时间戳。如果时间戳出现跳变或回退系统内部的速度模型就会崩轨迹会突然乱掉。我见过一次因为USB带宽不足导致帧时间戳间隔忽大忽小的情况换成USB 3.0接口后问题消失。5.3 第一帧空白图问题有一个看起来很小但容易忽略的坑D435i刚启动时前几帧彩色数据可能会是空的或者只有部分有效数据。如果不加判断直接传给SLAM可能引发空指针或异常行为。上面的代码里我用了一句if (!color_frame) continue;把空帧过滤掉了这比在SLAM内部做保护要更直接。还有个相关场景是程序退出。ORB_SLAM2内部有跟踪线程和可视化线程退出前必须调用slm.Shutdown()否则Pangolin窗口可能残留线程没有正常释放下次运行时会偶发段错误。我自己最早的一版代码为了省事直接return结果运行两三次就崩一次加上Shutdown之后再也没有出现过。5.4 时间戳不平滑会带来什么单目VSLAM用时间戳来维护帧间速度估计。D435i的彩色流在某些USB控制器下帧间隔会有轻微抖动这在纯单目模式下影响不大因为系统自身有局部BA优化能够容忍少量时间误差。但如果你后面打算把IMU数据融合进来或者和其他传感器做紧耦合时间戳的平滑性和基准一致性就非常重要。D435i的硬件时间戳在刚启动时有一段不稳定窗口我建议程序里做前面说的零偏校正并且等相机稳定跑几秒后再开始SLAM初始化。另外不要混用硬件时间戳和系统时间戳选一种一直用到底。ORB_SLAM2官方示例的数据集时间戳是统一的而实时相机场景要把这个问题单独拎出来想清楚。6. 从单目到后续成体系的扩展方向6.1 开通深度流切到RGB-D模式D435i的优势之一就是能切换RGB-D模式。如果你发现单目初始化太依赖运动方式或者拿到的轨迹没有真实尺度可以考虑用ORB_SLAM2的RGB-D模式。无ROS环境完全可行用librealsense的rs2::align让深度图和彩色图对齐到同一视角后把两张图同时传给System的RGB-D构造函数即可。不过这里有个提醒RGB-D模式虽然能直接获取深度恢复尺度但D435i深度图的边缘和反光区域容易产生空洞在做ORB_SLAM2输入时可以先用深度值有效性掩码过滤掉无效区域的误匹配。单目模式没有深度信息反而少了一类噪声两者在精度和鲁棒性上的取舍值得专门做实验对比。6.2 想用IMU的话要换支持单目惯导的开源系统D435i自带IMU但ORB_SLAM2本身不支持单目IMU融合它的单目版本就是纯视觉方案。如果你确实想做视觉惯导融合可以考虑后续开源的ORB_SLAM3这类支持单目IMU的项目。这类系统无ROS的编译思路与ORB_SLAM2非常相似依赖库也基本一致迁移成本在可控范围内。但要注意单目IMU融合对IMU零偏、时间偏移非常敏感。D435i内置IMU的噪声并不小而且出厂标定不一定和你的使用环境完全匹配所以在做紧耦合之前最好先做一些静止和运动数据采集评估一下陀螺仪和加速度计的数据质量再决定融合权重。6.3 嵌入式平台部署的几个提醒如果你不是在一台性能强劲的台式机上跑而是准备部署到低功耗板子有一些现实问题绕不开。640x48030fps对CPU占用不算低ORB_SLAM2在树莓派这种级别的平台上往往跑不满30帧更实用的做法是把分辨率降到320x240或者在主循环里主动丢帧让SLAM的输入稳定在15到20帧左右。D435i的数据接口一定要用USB 3.0口。USB 2.0带宽不足以支撑稳定的640x480彩色流实测中会出现彩色流帧率波动甚至自动中断。如果板子的USB 3.0口不够可以考虑调整分辨率到更低规格但不要指望USB 2.0下能稳定跑满30帧彩色。电源也要注意D435i在启动时会有瞬时电流劣质Hub容易导致设备掉线优先直接插主板接口。我现在的做法是把无ROS这套编译脚本和入口代码固化成自己的模板后面再评估其他相机时只需要替换内参配置和数据读取层就能快速判断这款相机适不适合跑单目视觉SLAM。整个过程跑下来最大的体会是对一个SLAM问题先想清楚“数据从哪来到哪里去”再决定要不要上中间框架往往能省去很多不必要的环境负担。最后提醒一句如果你也是第一次拿D435i跑单目ORB_SLAM2不要贪心先640x48030fps加上手动左右平移初始化把流程跑通再去折腾参数和扩展。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑