首页 / 资讯中心 / 文章详情

ORB-SLAM2跑自定义图片序列:从数据准备到轨迹评估

ORB-SLAM2跑自定义图片序列:从数据准备到轨迹评估 ★ FEATURED ARTICLE
简介面向视觉SLAM入门者与需要跑通自定义数据集的开发者这套实操资源围绕ORB SLAM2解决如何将自拍视频或图像序列整理成系统要求的输入格式进而完成定位与建图适用于机器人、无人机或AR场景。整个压缩包共4个文件大小约4.38MB包含两个Python脚本用于视频拆帧与rgb.txt生成、一段示例视频和一个rgb.txt示例覆盖从原始视频到ORB SLAM2输入文件的完整链路其中rgb.txt按‘图片路径 时间戳’格式记录每一帧是系统顺序读取图像的关键。目前该资源已有5381人学习适合作为自定义数据集上手的起步参考。借助示例与脚本读者可快速掌握图片命名与时间戳写法理解rgb.txt格式要求并据此修改ORB SLAM2的yaml配置参数在自己采集的场景中跑通初始化、跟踪、关键帧选择与稀疏建图获得轨迹和点云结果。1. ORB-SLAM2跑自己的图片序列先别急着把视频怼进去如果你第一次接触ORB SLAM2最容易踩的动作是拿手机拍一段视频然后想着用ORB SLAM2直接把这段视频变成相机轨迹。等真动手才发现ORB-SLAM2根本不吃视频流它只接受“图片序列数据集”——一张一张按时序排好的图片外加一份相机内参文件和一份时间戳。这个反直觉的点劝退过不少人其实把它整明白了剩下的事就是在数据组织和参数调试上花时间。这篇笔记我会从数据准备讲到轨迹评估把跑通自家图片序列的完整路径拆开适合准备在自定义场景里做视觉SLAM建图、或者在数据集上复现代码的开发者。2. 数据准备目录、时间戳、内参一个都不能少2.1 目录结构每个图片都是关键帧的候选样本ORB-SLAM2读取序列的方式比很多人想得简单它按目录顺序逐张读取图片目录顺序就是时间顺序。所以你的目录里只要有一堆按序命名的图片文件就行甚至不要求文件名连续只要能被排序。实际跑之前我会先确认几件事图片格式统一建议全部转成.png或.jpg不要混着来同一序列的图像尺寸完全一致不然内参没法用一个单目SLAM初始化需要两帧之间有足够视差所以采集时要让相机有平移或旋转别对着静止场景发呆。目录结构我一般这样组织my_data/ ├── images/ │ ├── 000000.png │ ├── 000001.png │ ├── 000002.png │ └── ... ├── timestamps.txt └── my_cam.yamlimages/放图片序列timestamps.txt记录每张图片的时间戳my_cam.yaml是相机内参。三个文件各管一件事后面评估轨迹时都会用到。2.2 生成时间戳按帧率给图片建一个虚拟时钟ORB-SLAM2的定位过程不依赖时间戳它需要时间戳的地方主要在轨迹评估和多传感器同步上。这里有个常见的认知误区时间戳不影响能不能跑通但影响评估结果准不准。如果你手里没有硬件时钟就用帧率去模拟。import os fps 30.0 # 采集帧率改成你实际用的 start_ts 1477059600.0 # 随便取一个起点表示采集开始时刻 image_dir my_data/images image_count len(os.listdir(image_dir)) with open(my_data/timestamps.txt, w) as f: for i in range(image_count): ts start_ts i / fps f.write(f{ts:.6f}\n)这段代码的核心是start_ts i / fps第 i 张图的时间戳等于起点加上 i 除以帧率单位是秒。输出保留 6 位小数精度足够大多数SLAM评估工具使用。如果你是从视频抽帧得到的图片更合理的做法是直接用video_timestamp frame_id / video_fps这样的时间间隔更真实。有一点要注意如果图片来自带硬件时间戳的采集设备比如某些相机同时输出图片和陀螺仪数据那就优先用硬件时间戳因为帧率模拟的时钟在长时间序列里会有累积误差。2.3 相机内参fx、fy、cx、cy 究竟从哪来内参是整个序列能不能跑出可信轨迹的“地基”。ORB-SLAM2靠相机内参把像素坐标换算到相机坐标系再估计位姿。内参错了轨迹不一定崩但会出现一种让人很头疼的“软漂移”——轨迹看起来在动跟真实路线对不上。拿一个常见的640x480图像来说yaml内参配置大概长这样%YAML:1.0 Camera.fx: 528.49 Camera.fy: 528.49 Camera.cx: 320.5 Camera.cy: 240.5 Camera.fps: 30.0 Camera.k1: 0.0 Camera.k2: 0.0 Camera.p1: 0.0 Camera.p2: 0.0fx和fy是焦距单位是像素cx、cy是主点坐标通常接近图像中心的整数或半整数。k1、k2、p1、p2是畸变系数如果直接用的普通摄像头没做标定可以先置零但轨迹精度会受一些影响。Camera.fps最好跟实际采集帧率一致我见过差值过大的配置导致关键帧插入频率异常轨迹上会出现密集的抖动点。内参来源有三条路一是查官方相机参数表二是用棋盘格跑一遍标定三是用同分辨率下别人标定过的近似值。前两种最可靠第三种只能拿来先跑通流程不能在精度敏感的场景下直接信。2.4 一条 Python 命令完成数据包整理上面三块都准备好后我会用一段小脚本把整个数据集整理到一起顺带生成一个最小的 yaml 骨架改改参数就能用import os, yaml image_dir my_data/images fps 30.0 start_ts 1477059600.0 # 统一图片命名按采集顺序从 0 开始 for i, fname in enumerate(sorted(os.listdir(image_dir))): ext fname.split(.)[-1] os.rename(os.path.join(image_dir, fname), os.path.join(image_dir, f{i:06d}.{ext})) # 生成时间戳 with open(my_data/timestamps.txt, w) as f: for i in range(len(os.listdir(image_dir))): f.write(f{start_ts i / fps:.6f}\n) # 生成内参 yaml 骨架 camera { Camera.fx: 528.49, Camera.fy: 528.49, Camera.cx: 320.5, Camera.cy: 240.5, Camera.fps: fps, Camera.k1: 0.0, Camera.k2: 0.0, Camera.p1: 0.0, Camera.p2: 0.0, } with open(my_data/my_cam.yaml, w) as f: f.write(%YAML:1.0\n) yaml.dump(camera, f)这个脚本把三个文件一口气生成完省去手工整理的繁琐。参数都在文件开头下次换数据集只需要改image_dir和fps。注意yaml.dump输出的键之间可能有空行ORB-SLAM2的yaml解析器一般不受影响但如果后面报解析错误就把yaml.dump(..., default_flow_styleFalse)改成显式逐行写入。3. 编译与配置从依赖安装到修改示例程序3.1 依赖清单OpenCV、Eigen3、Pangolin、g2o、DBoW2 缺一不可ORB-SLAM2的依赖不算少但固定装一次就能复用。常见依赖和版本建议我整理成了一张表依赖库用途版本建议OpenCV图像读取、特征提取3.x 或 4.x 都行注意源码接口变化Eigen3矩阵运算3.3 以上Pangolin可视化、轨迹显示0.6 以内新版接口变化较大g2o图优化后端源码构建随 ORB-SLAM2 一起编译DBoW2词袋回环检测源码构建同样随主项目编译经验上最容易出问题的组合是 OpenCV 4.x 配旧版 g2o会出现函数签名不匹配。我一般选择源码编译 g2o 和 DBoW2而不是用系统包管理安装这样版本可控。Pangolin 如果版本太新运行时可能在高分屏下黑屏先试试 0.6 左右的分支比较稳。3.2 编译三步走build.sh 在做什么ORB-SLAM2 仓库里自带build.sh真正的构建核心就三步编译 Thirdparty 里的 DBoW2编译 g2o最后编译主工程。命令行如下cd ORB_SLAM2 chmod x build.sh ./build.shbuild.sh内部会把 DBoW2 和 g2o 的build目录建好并执行cmake ../和make如果这些第三方库已经单独编译过重跑脚本也只会重新编译主工程。首次编译时间比较长建议用make -j4或-j8加速但同时在机器上跑编译会导致内存吃紧墙内小内存机器建议先-j2试跑一次。如果你不想用脚本手动执行也一样cd Thirdparty/DBoW2 mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease make cd ../../g2o mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease make cd ../../../build cmake .. -DCMAKE_BUILD_TYPERelease make注意这里统一用了Release模式调试信息少但速度提升明显。SLAM 是实时性敏感的任务Release 是唯一合理的选择。如果cmake阶段报找不到 OpenCV多半是 OpenCV 装到了非标准路径可以在cmake后面加-DOpenCV_DIR/path/to/opencv指定。3.3 修改示例程序把 KITTI 路径换成你的序列路径编译完成后ORB-SLAM2 自带的示例程序带的是 KITTI 数据集的参数和路径。直接跑自己的序列前需要改一下源码里的路径拼接逻辑。以单目示例mono_kitti.cc为例核心代码一般长这样int main(int argc, char **argv) { if(argc ! 4) { cerr endl Usage: ./mono_kitti path_to_vocabulary path_to_settings path_to_sequence endl; return 1; } // 创建单目 SLAM 系统最后一个参数表示开启可视化 ORB_SLAM2::System SLAM(argv[1], argv[2], ORB_SLAM2::System::MONOCULAR, true); // 读取序列路径这个目录下不能有子目录必须直接是图片文件 string sequencePath string(argv[3]); ... // 循环读取图片逐张送入 SLAM 系统 for (int i start; i end; i) { cv::Mat im cv::imread(sequencePath / to_string(i) .png, cv::IMREAD_UNCHANGED); SLAM.TrackMonocular(im, timestamp[i]); } }很多人在这一步直接把sequencePath写死成 KITTI 的绝对路径我的习惯是不动源码逻辑只把命令行参数传成自己的目录这样换数据集不用改代码重编译。如果你的图片命名不是连续数字比如带日期前缀就把读取循环里拼文件名的部分改成directory iterator按排序后的文件名读取。图片扩展名也必须在源码里对应好中间差一个.jpg和.png的不一致运行时会立刻报读取失败。4. 跑通第一个序列单目起步再切换双目与深度4.1 单目模式一条命令跑起来准备就绪后第一次跑通推荐用单目模式因为数据要求最低只要一台普通相机拍的图片序列就能验证整个链路。命令行如下./Examples/Monocular/mono_kitti \ ./Vocabulary/ORBvoc.txt \ ./my_data/my_cam.yaml \ ./my_data/images三个参数分别是词袋文件、相机内参 yaml、图片目录。ORBvoc.txt是 ORB-SLAM2 预训练好的词袋所有模式通用路径不要写错。运行后终端会弹出可视化窗口显示当前帧的跟踪点和相机轨迹。如果画面里绿色特征点足够多轨迹圆点逐渐画出来说明单目流程已经通了。单目模式有个特点尺度不确定。轨迹的绝对尺度没法从纯视觉估计出来所以轨迹看起来会“缩放”这是单目的通病不是配置问题。如果后续需要真实尺度需要引入双目、深度相机或者事后用地面真值对齐尺度。4.2 双目与深度模式时间戳同步才是真正的门槛从单目切到双目或深度模式数据准备的成本立刻高了不少。双目要两个相机同步曝光深度要 depth 图和 rgb 图对齐。ORB-SLAM2 的双目示例stereo_kitti.cc读取每一帧的彩色图、深度图和对应的左右目图时间戳要求更严格。./Examples/Stereo/stereo_kitti \ ./Vocabulary/ORBvoc.txt \ ./my_data/stereo_cam.yaml \ ./my_data/sequences/00双目的 yaml 里需要额外配置Camera.bf这是基线长度乘以焦距的值决定深度估计的尺度。深度模式同理在rgbd_tum.cc里需要提供 RGB 图与深度图的目录并且两张图的名字要严格对应。同步时间戳是我踩坑最多的地方如果 RGB 图目录和深度图目录用不同帧率采集比如 RGB 30fps、深度 15fps直接喂给 ORB-SLAM2 会频繁报“找不到对应深度图”轨迹在深度细节处呈现断崖式跳跃。解决办法是先把两路数据按时间戳最近邻对齐生成同步索引再喂进去。4.3 RGB 转灰度特征点数量变化很明显ORB 特征点提取是在灰度图像上做的。如果你的图片本身是 RGBORB-SLAM2 内部会调用cvtColor转灰度这步默认没问题。但如果你从原始采集设备拿到的图像是 RGB 且带色彩矫正、白平衡等增强转灰度后灰度分布会被拉偏特征点数量和质量都可能明显下降。对比一下就会发现同样一个场景原图直接送进去能跟踪 2000 个点先做一次均一化灰度变换可能只剩 1400 个。我的习惯是拿到任何序列后先统一转成灰度图再送进去这样中间的色彩处理完全可控import cv2, os, glob image_files sorted(glob.glob(my_data/images/*.png)) for idx, fpath in enumerate(image_files): img cv2.imread(fpath, cv2.IMREAD_GRAYSCALE) cv2.imwrite(fmy_data/gray/{idx:06d}.png, img)转灰度后文件占空间更小读取更快ORB-SLAM2 内部就少做一次色彩转换少一点不确定因素。如果你用的相机输出本身是灰度流就完全不用这步。5. 常见问题排查五个最容易翻车的现场5.1 启动即崩找不到图片或扩展名不统一现象运行后窗口一闪而过终端报Failed to load image at ...或者corrupted size vs. prev_size。原因最常见的是序列目录里的图片命名规则跟源码里to_string(i) .png这种拼法对不上比如图片实际是.jpg或者命名从 1 开始而你源码里start0。其次是目录路径写到了images/的上一级源码拼接后路径里出现了双斜杠某些系统解释没问题但 ORB-SLAM2 里的cv::imread对这种路径返回空矩阵。解决先用ls确认图片目录内容确保图片是纯文件没有子目录再把源码里的扩展名改成跟实际文件一致。最稳妥的做法是用脚本先把全部图片重命名成000000.png这类连续编号避免扩展名和起始序号双坑。5.2 轨迹漂移成迷宫内参错误或帧率乱现象程序不崩但跑出来的轨迹完全对不上真实路径或者出现成片的“锯齿”形抖动。原因内参fx/fy严重偏离真实值时ORB 特征点地图会被整体拉伸位姿估计看起来像在乱走。另一个常见原因是Camera.fps与实际帧率差太远关键帧插入频率紊乱轨迹上出现密集的不连续转折点。解决回到第 2 章先核对内参来源。临时拿不到标定参数时可以先用图像宽高和视场角近似估算fx width / (2 * tan(fov/2))至少让量级对得上。fps也改成实际采集帧率重新生成时间戳再跑一遍。这类问题表现出的“软漂移”很有欺骗性排查优先级高于优化参数。5.3 初始化失败画面静止或特征点太少现象运行时窗口显示在读取图片但一直不进入跟踪状态后面轨迹起不来或者报Not enough features。原因单目初始化需要两张图之间有足够的视差和足够的匹配点。如果采集时相机保持静止或者场景是白墙、空旷路面这种纹理稀少的区域ORB 提取的特征点数量太少匹配对数不达标就会一直卡初始化。解决采集时让相机缓慢移动保证画面有平移分量同时找有纹理的区域比如椅子腿、开关面板、树边。如果场景实在单调可以调小 ORB 特征提取器的阈值让低对比度区域也能提取到点但要注意特征点质量下降会带来跟踪抖动。5.4 用 evo 评估时轨迹对不齐时间戳单位不一致现象评估工具报Found no matching timestamps或者画出来的轨迹曲线错位严重。原因ORB-SLAM2 输出的时间戳单位是秒但有些评估工具要求纳秒比如 TUM 格式。如果你生成时间戳时用了秒而评估脚本或 ground truth 用的是纳秒时间轴就差了 9 个数量级匹配窗口内找不到对应点。解决写脚本时统一成一种单位我一般统一用秒并在转换轨迹文件时保持跟 ground truth 相同。如果工具要求纳秒把时间戳乘以1e9再转成整数输出水到渠成。5.5 加载完词汇库后卡死内存不足或线程抢满现象程序启动后能打印Vocabulary loaded!但紧接着像卡住了一样界面迟迟不出现或者 CPU 占满但画面冻结。原因ORB-SLAM2 的ORBvoc.txt词袋文件比较大加载时很吃内存。低内存机器在加载后紧接着跑特征提取可能长时间 GC 停顿或进程被系统 OOM 打掉。另一个原因是本地开了太多后台任务8 核机器被别的进程占到只剩零星资源而 SLAM 本身会默认起满所有线程。解决先看看词袋文件大小通常六百兆级别。内存少于 8G 的话关掉可视化窗口能省不少开销启动时把System构造函数最后一个true改成false。同时跑top确认 CPU 占用避免和其他重型任务同时抢。6. 验证与进阶轨迹可视化与评估一体化6.1 轨迹文件转 TUM 格式跑完一整个序列后ORB-SLAM2 会输出KeyFrameTrajectory.txt每行 8 个数时间戳、xyz、四元数。这个格式很多评估工具不认识要转成标准的 TUM 轨迹格式再去跟 ground truth 对比。转法很简单with open(KeyFrameTrajectory.txt) as f_raw, \ open(trajectory_tum.txt, w) as f_out: for line in f_raw: parts line.strip().split() if len(parts) ! 8: continue # TUM 格式也是 8 列时间 ts, x y z, qx qy qz qw f_out.write( .join(parts) \n)这段代码只是把空白分隔符归一化一遍不重排数据。因为 ORB-SLAM2 输出的关键帧轨迹并不是等间距时间戳TUM 格式的要求是“每行代表一个位姿”中间少几帧没关系。如果你想要帧级轨迹而不是关键帧轨迹用frame_trajectory.txt输出文件行数会多几倍。6.2 用 evo 做 APE 和 RPE 评估轨迹验证我常用evo工具包它支持 TUM 格式直接画图和算误差。命令组合如下evo_ape tum ground_truth_tum.txt trajectory_tum.txt -a -va --plot这里有三个关键参数-a做绝对位姿对齐单目轨迹要先对齐尺度再算误差不然 APE 会被尺度漂移拉爆-va逐帧打印两者的位姿误差分布--plot把误差曲线画出来一眼看出哪段轨迹漂移最大。如果想看轨迹上的稳定性用evo_rpe代替evo_apeRPE 算的是帧间相对位姿误差对回环优化好坏更敏感。实际使用顺序是先跑 APE 看全局偏差再跑 RPE 定位具体出问题的区段然后再回头改采集方式或相机参数。这一步我吃过亏第一次拿到像模像样的轨迹时直接以为成功了后来用 APE 一算 RMSE 有 0.8 米整个轨迹被放大了一圈。从那以后我每次跑自己的图片序列都会强制走一遍“原图转灰度、时间戳按秒统一、轨迹转 TUM、先用 evo 算一次 APE”这四步再开始调参数。数据准备多花十分钟后面少走一整晚弯路这套流程希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站