☰
ORB SLAM2跑自建图片序列:从数据集标定到evo轨迹评估全攻略
2026/10/20 16:43:54 网站建设 项目流程

简介:面向视觉SLAM入门与进阶开发者,这份资源演示如何用ORB SLAM2处理自己拍摄的图片序列数据集,解决从官方TUM数据集切换到自定义场景时的数据准备与参数配置难题。包体共4个文件,包括两个Python脚本、一段示例MP4视频和一份文本文件,压缩包仅4.38MB;脚本分别承担视频逐帧抽取为图片、按格式生成rgb.txt时间戳与路径列表的功能,txt则体现标准数据组织结构,示例视频可供直接测试流程。已有5381人学习,适合希望基于真实场景快速搭建ORB SLAM2运行流程的读者。通过参考该资源,可掌握自定义数据集目录的搭建要点,理解特征提取、关键帧选择、闭环检测等环节对输入数据的要求,从而减少数据格式与运行配置上的常见错误,更高效地复现定位与稀疏建图结果。

1. 用 ORB SLAM2 跑自己的图片序列数据集:能编译不代表能出轨迹

手头有台机器人,想用 ORB SLAM2 跑自己录的图片序列,结果网上的教程十有八九都在跑公开数据集,换到自己的数据上就翻车,报错和丢跟踪花样百出。这篇笔记把「把自己的图片序列喂给 ORB SLAM2」的全链路拆开讲:数据集怎么组织、相机参数怎么标定和填写、单目和双目模式分别改哪几个参数、哪些坑必踩,以及最后用 evo 验证轨迹精度。适合刚入门视觉 SLAM、手头只有一台相机和一串 PNG/JPG 的从业者。ORB SLAM2 的接口很直接,但数据集准备的细节才是真正决定成败的部分。

2. 数据集准备:命名、时间戳与相机内参是三个黑匣子

先明确一件事:ORB SLAM2 的可执行文件并不直接读取视频文件,它吃的是「图片序列 + 时间戳 + 相机参数」这三样。很多初学者拿 mp4 往里面塞,程序根本不吃这一套。正确姿势是把视频抽帧成图片序列,或者直接用相机 SDK 出图。

2.1 图片序列的目录组织与命名规则

公开数据集里常见的是rgb/目录放彩色图、depth/目录放深度图,图片名直接用时间戳浮点值。ORB SLAM2 的图像读取走cv::imread,它对路径里的空格、中文字符支持时好时坏,属于典型的「玄学崩溃」。我一般会先建立下面这样的目录结构:

your_dataset/ ├── rgb/ │ ├── 000000.png │ ├── 000001.png │ └── ... ├── depth/ │ ├── 000000.png │ └── ... ├── rgb.txt └── depth.txt

rgb.txt和depth.txt是给单目和 RGB-D 模式加载用的索引文件,每行两列:时间戳和相对路径。这里有一个很多人不知道的细节:ORB SLAM2 读文件时按文件里的顺序逐行处理,并不要求时间戳严格连续,但必须单调递增;如果时间戳回跳,系统内部的时间更新逻辑会出问题,地图点会被错误关联。

2.2 时间戳文件生成:格式、排序与重复值处理

如果你的图片名本身就是时间戳,可以用脚本扫目录生成rgb.txt。下面这段是绕不开的 Python 工具脚本:

import os, glob img_dir = "rgb" out_file = "rgb.txt" with open(out_file, "w") as f: f.write("# timestamp filename\n") for path in sorted(glob.glob(os.path.join(img_dir, "*.png"))): name = os.path.basename(path) ts = float(os.path.splitext(name)[0]) # 文件名即时间戳 f.write(f"{ts:.6f} {path}\n") print("done:", out_file)

逻辑说明:sorted(glob.glob(...))保证按文件名排序写入,避免目录返回顺序不稳定;float(os.path.splitext(name)[0])把1699999999.123456.png这类名字解析成秒级时间戳。如果你手里的图片名是000000.png这种连续编号,没有真实时间戳,就自己生成等间隔时间戳,比如从 0 开始、步长为 1/帧率。

参数说明:ts:.6f保留 6 位小数,满足 ORB SLAM2 内部字符串转 double 的解析需求;如果你手动用 Excel 生成,记得别把时间戳列存成文本格式,否则程序读到空行就退出——这个坑我踩过不止一次。另外,采集中偶尔会出现重复时间戳,ORB SLAM2 对同一时间戳的多帧没有去重逻辑,跑出来的轨迹会原地跳变。我一般会在生成脚本里顺手去重:

seen = set() lines = [] for path in sorted(glob.glob(os.path.join(img_dir, "*.png"))): name = os.path.basename(path) ts = float(os.path.splitext(name)[0]) if ts in seen: continue seen.add(ts) lines.append(f"{ts:.6f} {path}\n") with open(out_file, "w") as f: f.writelines(lines)

2.3 相机内参标定:棋盘格与 OpenCV 一步出参

ORB SLAM2 的单目初始化和三角化都依赖准确的相机内参,畸变参数不对,前面还能勉强跑,后面必然丢跟踪。标定棋盘格是最粗暴也最稳的办法,用 OpenCV 就能完成。下面这段代码会扫描calib/目录下的所有棋盘格图片,输出内参和畸变系数:

import cv2, numpy as np, glob board_size = (8, 6) # 棋盘格内角点数 criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) objp = np.zeros((board_size[0] * board_size[1], 3), np.float32) objp[:, :2] = np.mgrid[0:board_size[0], 0:board_size[1]].T.reshape(-1, 2) objpts, imgpts = [], [] for f in sorted(glob.glob("calib/*.jpg")): img = cv2.imread(f) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, corners = cv2.findChessboardCorners(gray, board_size, None) if not ret: print("角点提取失败:", f) continue objpts.append(objp) corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) imgpts.append(corners2) ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera( objpts, imgpts, gray.shape[::-1], None, None ) print("fx fy cx cy:", mtx[0, 0], mtx[1, 1], mtx[0, 2], mtx[1, 2]) print("k1 k2 p1 p2:", dist[0][0], dist[0][1], dist[0][2], dist[0][3])

逻辑说明:objp是棋盘格角点的物理坐标,直接按格数生成;findChessboardCorners负责在每张图里找角点,再交给cornerSubPix做亚像素精化。没有做亚像素精化的角点,标定结果会明显偏噪,尤其影响畸变系数。

参数说明:board_size = (8, 6)必须与标定板实际内角点数一致,多一个少一个都会导致提取失败;(11, 11)是亚像素搜索窗口,太小则迭代不稳定,太大则越界。标定图片建议至少 15 张,覆盖中心、四角和旋转姿态,不然解出来的焦距和畸变会严重偏移。

标定完输出的fx fy cx cy k1 k2 p1 p2,就是要写进 ORB SLAM2 配置 YAML 的原始数据。下一章直接讲配置文件的每个字段怎么映射。

3. 配置与编译:参数文件怎么写、依赖版本怎么选

3.1 三种传感器模式的输入差异

ORB SLAM2 按传感器类型拆成三个可执行程序,输入接口差异很大,选错适配器是跑不起来的第一大原因。

模式可执行程序输入内容尺度来源
单目mono_tum单路 RGB 图片 + 时间戳无绝对尺度,轨迹有尺度漂移
双目stereo_euroc左右目图片 + 时间戳立体基线直接恢复深度
RGB-Drgbd_tumRGB 图 + 深度图 + 时间戳深度传感器直接测距

实际工程里,单目最容易上手但精度评估最麻烦;双目需要保证左右目时间同步;RGB-D 要处理深度图的噪声和无效像素。选型时要先想清楚自己手里的相机能稳定提供什么,不要硬套。

3.2 相机参数 YAML 详解

ORB SLAM2 的配置是 YAML 格式,它不区分单目和双目,只认字段名。一个常规的相机配置大概长这样:

%YAML:1.0 Camera.fx: 517.3 Camera.fy: 516.4 Camera.cx: 318.6 Camera.cy: 255.3 Camera.k1: 0.262 Camera.k2: -0.953 Camera.p1: -0.005 Camera.p2: 0.002 Camera.k3: 1.163 Camera.width: 640 Camera.height: 480 Camera.fps: 30.0 Camera.RGB: 1 ORBextractor.nFeatures: 1000 ORBextractor.scaleFactor: 1.2 ORBextractor.nLevels: 8 ORBextractor.iniThFAST: 20 ORBextractor.minThFAST: 7

逻辑说明:Camera.fx/fy/cx/cy直接来自上一章 OpenCV 标定的mtx。Camera.RGB表示输入图像通道顺序,1 代表 BGR 转 RGB 的顺序需要被修改,0 代表原始图像就是 RGB。这里的坑在于不同相机 SDK 的输出顺序不一样,多数工业相机输出的是 RGB,但cv::imread读进来是 BGR,设错之后颜色整体偏色,ORB 特征虽然照常提取,但地图色彩信息就乱了。

ORBextractor 段是特征提取的核心参数。nFeatures是全图希望提取的 ORB 特征数量上限,scaleFactor是金字塔相邻层的尺度比,nLevels是金字塔层数。纹理稀疏的室内环境,我会把nFeatures调到 1500 甚至 2000;纹理丰富的室外场景,800 就够,太多反而拉低实时性。iniThFAST和minThFAST是 FAST 角点的初始和最小阈值,默认 20/7 在室内弱纹理下会出现初始化困难,调到 15/5 能显著提高初始化成功率。

3.3 编译环境与常见版本坑

ORB SLAM2 编译本身不算复杂,最常翻车的是依赖版本。推荐在 Ubuntu 上装 CMake、OpenCV、Eigen 和图形显示库,然后直接编译:

cd ORB_SLAM2 mkdir build && cd build cmake .. make -j4

逻辑说明:编译过程会先构建Thirdparty下的特征提取、图优化等子模块,再链接主程序。-j4限制并行任务是怕内存不够,虚拟机 4 核以下建议-j2。

参数说明:如果用的是较新发行版,默认的 OpenCV 版本太高,接口已经变动,编译时会在Frame.cc、Initializer.cc这类文件上报一堆cv::相关错误。常见做法是降到 OpenCV 3.2 左右,或者直接在 CMakeLists 里指定OpenCV_DIR指向已安装的低版本路径。另一个高频坑是 Eigen 版本太新导致FindEigen3失败,通常手动指定EIGEN_INCLUDE_DIR即可。

如果编译过了但一运行就报Segmentation fault,先检查Vocabulary/ORBvoc.txt路径是否写对,以及运行时有没有传足三个参数。词汇表加载失败是段错误最常见的来源。

4. 单目与 RGB-D 跑通全流程:命令、参数与输出文件

4.1 单目模式:最小可用配置

单目是最省设备的模式,只要一个普通 RGB 相机。运行命令如下:

./Examples/Monocular/mono_tum \ Vocabulary/ORBvoc.txt \ Examples/Monocular/MyConfig.yaml \ /data/your_dataset

逻辑说明:三个参数分别是词汇表路径、相机配置路径、数据集根目录。数据集根目录必须包含rgb.txt,且rgb.txt里写的图像路径是相对数据集根目录的。

参数说明:MyConfig.yaml里除了相机内参和 ORBextractor,还需要注意Viewer.KeyFrameSize这组可视化参数,它们不影响轨迹精度,只影响显示效果。默认值可以直接用,不需要改。

跑起来后观察终端输出:初始化成功会弹出Map initialized,之后每隔一帧打印一条Tracking state: OK。如果一直提示TRYING TO INITIALIZE,说明前两帧之间匹配点不够,原因通常是运动幅度太大、纹理太淡、焦距填错,或者minThFAST设得过高。

程序结束时会在当前目录生成两个文件:

  • KeyFrameTrajectory.txt:关键帧轨迹,TUM 格式
  • CameraTrajectory.txt:逐帧估计轨迹,TUM 格式

这两个文件是后续 evo 评估的输入,建议跑完立刻复制出来改名归档,否则会被下次运行覆盖。

4.2 RGB-D 模式:深度图因子和对齐

RGB-D 模式比单目多一个深度通道,运行命令:

./Examples/RGB-D/rgbd_tum \ Vocabulary/ORBvoc.txt \ Examples/RGB-D/MyRGBD.yaml \ /data/your_dataset

RGB-D 要求数据集根目录同时有rgb.txt和depth.txt,两者的图像数量和时间戳必须能够对应上。ORB SLAM2 读取深度图时会按时间戳匹配,匹配窗口内找不到对应深度帧就跳过。

深度图的坑主要在单位。深度传感器保存深度图时,有的输出单位是毫米,有的是米。在 YAML 里要写对DepthMapFactor:

Camera.depthmapfactor: 5000.0

逻辑说明:如果深度图像素值本身就是米,那就设成1.0;如果像素值是毫米,要除以 5000 转成米;如果是 16 位深度图且值为 0 表示无效测距,系统会在读取时跳过这些无效点。

参数说明:Camera.depthmapfactor填错的最明显表现是地图上出现大量漂浮在空中的点云,或者轨迹高度抖动。调这个参数不需要重编译,改完 YAML 重新运行即可。

4.3 双目模式:基线与视差参数不能乱填

双目模式的开销比单目大,但能直接恢复尺度。命令如下:

./Examples/Stereo/stereo_euroc \ Vocabulary/ORBvoc.txt \ Examples/Stereo/MyStereo.yaml \ /data/stereo_dataset

双目数据集需要按左右目目录组织,类似stereo_dataset/left/和stereo_dataset/right/,并且左右目录里的文件名完全一致,这样程序才按同名配对。双目配置在普通相机参数外还多了两个关键字段:

Camera.bf: 40.0 Camera.ThDepth: 40.0

逻辑说明:Camera.bf是基线长度乘焦距,单位是像素,不是米。比如基线 0.12 米,fx是 500,那bf = 0.12 * 500 = 60。填成 0 或填成米会导致初始化时深度恢复完全错乱。Camera.ThDepth是区分近处和远处特征点的阈值,单位同样基于bf。阈值越大,系统越倾向把远处特征点都当作远点处理,影响三角化策略。

参数说明:对室内小场景,ThDepth建议 40;对室外长走廊、开阔场地,可以调到 60 以上,否则远处的点会一直不参与局部地图优化。我一般先用默认值跑一段,再看地图点数量和跟踪质量决定增减。

5. 避坑排查:跑自己的数据必踩的四个坑

5.1 路径与时间戳类

现象:程序一启动就Segmentation fault,或者终端直接打印Error opening file然后退出。

原因:第一优先级是cv::imread读不到中文字符或带空格路径下的图片;第二优先级是rgb.txt里的时间戳排序不单调,导致某帧的时间戳小于前一帧,触发内部断言。

解决:数据集根目录和图片路径全部改成英文、无空格;执行前用脚本检查时间戳序列是否单调递增:

awk 'NR>1 && $1<=prev {print "bad line:", NR} {prev=$1}' rgb.txt

如果输出bad line,重新执行第二章的去重和排序脚本。

5.2 初始化卡死与开局丢跟踪

现象:程序一直输出TRYING TO INITIALIZE,或者初始化成功后十几帧内立刻Tracking lost。

原因:前三帧运动过大,两帧之间的视差超出搜索范围;或者场景是白墙、天空这类低纹理区域,ORB 特征点数量不足以计算基础矩阵和单应矩阵。

解决:把rgb.txt降采样,比如每 3 帧取 1 帧,让帧间运动更平滑;同时把ORBextractor.minThFAST从 7 降到 5,必要时把nFeatures提升到 1500。如果在纯旋转状态下初始化,无论参数怎么调都大概率失败——初始阶段需要平移分量,这是原理上的限制,跟参数无关。

5.3 地图点数量骤减与轨迹跳变

现象:跟踪一段时间后,终端打印特征点数从几百掉到几十,随后某个拐弯处轨迹突然跳变或丢失。

原因:除了纹理原因,最常见的是畸变参数不对。焦距有误差可以直接观察出来,畸变参数不对则会在图像边缘产生明显的距离误差,导致特征匹配失败集中在视野边缘。

解决:先检查标定图片是否覆盖图像边角,重新标定一次,重点看k1和k2是否在合理范围内;如果畸变很小但参数在零点附近乱跳,可以直接把p1/p2置 0,只保留k1/k2,很多工业镜头在这种简化下反而更稳。

5.4 单目轨迹尺度不对

现象:跑出来的轨迹形状正确,但位移量完全不符号,同一段路重复跑,轨迹长度差别巨大。

原因:这不是 bug,单目 SLAM 本身看不到绝对尺度,所有轨迹都在「任意尺度」下重建。真实距离 1 米和 5 米,单目跑出来可能都是长度约为 1 的轨迹。

解决:单目结果只能做位姿形状评估,不能直接用于里程计。要恢复尺度,要么加一个已知物体长度做后处理缩放,要么改用双目或 RGB-D。做精度评估时,必须用相似变换对齐轨迹,不能直接用欧氏距离比较。

6. 用 evo 验证轨迹精度:对齐、ATE 与可视化

6.1 安装 evo 与轨迹对齐

ORB SLAM2 输出的是 TUM 格式轨迹,真值可以用高精度定位系统或者公开数据集自带文件。为了对比两者,需要先把单目的尺度不确定性处理掉。evo 提供了自动对齐功能,安装后直接使用:

pip install evo --upgrade --no-binary evo

安装完成先看一眼主工具是否可用:

evo_ape --help

6.2 计算 ATE 与 RPE 并绘图

绝对轨迹误差 ATE 反映整体轨迹的偏差,相对位姿误差 RPE 反映局部运动的平滑度。用 evo 跑这两项只需要两条命令:

evo_ape tum groundtruth.txt KeyFrameTrajectory.txt -va --plot --plot_mode xyz evo_rpe tum groundtruth.txt KeyFrameTrajectory.txt -va --plot --plot_mode xyz

逻辑说明:-v输出详细结果,-a表示自动进行相似变换对齐。对齐是单目评估的关键,因为单目轨迹存在任意尺度,不对齐直接算误差,结果会大得离谱。--plot生成图窗,--plot_mode xyz表示在三维空间中把两条轨迹同时画出来,方便直观看出哪一段偏差最大。

参数说明:如果只想看数值,不加--plot即可;要保存图片和统计结果,可以追加:

evo_traj tum KeyFrameTrajectory.txt --ref groundtruth.txt -a --plot_mode=xyz --save_results result.zip

看到 ATE RMSE 在厘米级还是米级,能快速判断整条链路是否可靠。一般在一个 20 米左右的室内场景,双目或 RGB-D 的 ATE RMSE 应该控制在 0.1 米以内;单目经过对齐后也能到 0.1 米量级,但如果对齐后 RMSE 仍然大于 0.5 米,基本可以断定内参或时间戳有问题,而不是算法本身的问题。

以前我图省事,拿手机随意录了一段视频,抽帧后也懒得标定,直接跑单目,出来的轨迹在后半段完全飘走,用 evo 画图才发现尺度漂移已经大到没法看。从那以后,我每次换相机或换环境,都强制走一遍「棋盘格标定 → 抽帧生成时间戳 → 小段测试 → 全量跑 → evo 复盘」这条链路,再没在数据格式上浪费过时间。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询