视觉惯性SLAM实验:VINS-Mono地图保存与evo精度评估全流程
2026/9/16 2:40:05 网站建设 项目流程

做视觉惯性SLAM实验,绕不开vins-mono。跑完一段数据集之后,你最关心的三件事基本是:轨迹有没有飘、地图能不能保存下来复用、用evo怎么把精度量化出来。这三件事看起来是三个独立问题,实际上是一整套流程——从建图到地图保存与重载,再到评测,缺一环都不行。我在EuRoC数据集上从头到尾跑过好几遍,把踩过的坑和可复现的步骤整理成一篇完整记录,适合刚把vins-mono跑通、正准备深入做定量分析和地图复用的朋友。

1. 先弄清楚vins-mono的“地图”到底是什么

1.1 vins-mono输出的不是栅格地图

很多新手拿到vins-mono第一反应是“我要保存一张地图”,但打开rviz之后发现只有稀疏的特征点、位姿轨迹和相机视野,并没有想象中的栅格地图、八叉树地图或者点云地图。这不是你配置错了,而是vins-mono本身定位就是视觉惯性里程计加回环检测,它的“地图”本质上是稀疏特征点云和关键帧位姿图,不是给导航用的稠密地图。

理解了这一点,后面的保存和重载才有的放矢。如果你需要的是八叉树地图或者栅格地图,那就得用vins-mono跑完轨迹之后,再把位姿和图像喂给RTAB-Map或者ORB-SLAM3这类带稠密建图的方案。但如果你只是想做SLAM精度评估、轨迹回放、位姿图重用,vins-mono自己输出的关键帧和轨迹就已经足够了。

1.2 真正需要保存的三类数据

实操中我会把vins-mono需要保存的东西分成三类,分别对应不同用途:

  • 轨迹数据:也就是vins_result_loop.csv或者vins_result_no_loop.csv,记录了优化后的相机位姿序列。这是做evo评测最核心的数据。
  • 关键帧位姿图:运行过程中pose_graph模块维护的关键帧索引、位姿、特征点信息,保存在pose_graph_map文件夹里。这是真正意义上的“地图”,可以用来做重定位或者回环加速。
  • 中间调试数据:比如相机内参标定结果、IMU偏差估计、边缘化信息等。这些一般不直接参与地图复现,但在排查问题时非常有用。

我在实际项目里最常用的是前两类。第三类更多是在算法调试阶段才会去翻。

1.3 为什么先想清楚“重载”再来谈保存

很多人在第一步就走了弯路——一上来就想把整个地图存下来,结果存出来的东西不知道往哪儿放。其实先想清楚“重载”要怎么用,保存策略就自然清楚了。

重载地图通常有两层含义。第一层是纯离线的轨迹重载,就是把之前跑出来的csv加载进来,和真实轨迹放在同一个坐标系下对比、画图、算误差,这个场景用evo就够了。第二层是真实的地图复用,比如把之前建的位姿图加载回来,在新一次运行中用之前的关键帧辅助回环检测。vins-mono的pose_graph_structure里的loadPoseGraph做的就是这件事,它会读取pose_graph_map文件夹并重建特征点的KD-tree,不过原版代码在加载后主要用于回环候选搜索,并不做完整的视觉重定位。

所以先确定你要的是哪一层“重载”。如果只是评测精度,那保存轨迹csv就够;如果你想做多任务的地图复用,那必须把pose_graph_map完整备份下来。下面我按这两条线分别展开。

2. 环境准备与数据格式转换

2.1 运行环境和数据集准备

跑vins-mono我用的环境是Ubuntu 16.04/18.04加ROS Kinetic/Melodic,这两个版本组合最稳。新版本Ubuntu上编译会遇到OpenCV和Eigen版本冲突,不是不能解决,但新手不建议一上来就折腾。编译步骤这里不赘述,vins-mono的GitHub仓库里写的很清楚,我只提醒一个点:编译前先确认cv_bridge和你装的OpenCV版本是否一致,否则运行时会出现奇怪的段错误或者图像订阅不上的问题。

数据集方面,EuRoC MAV数据集是我最推荐的,它有真实的IMU数据、双目图像和毫米级定位误差的ground truth,做evo评测特别方便。下载后把MH_01到MH_05几个序列放在同一个目录里,用rosbag播放。注意bag文件里的topic名称要和vins-mono配置文件一致,EuRoC默认是/cam0/image_raw、/cam1/image_raw和/imu0,如果下载的是新版本bag,topic可能带了前缀,先用rosbag info看一下再改配置文件。

2.2 统一轨迹格式:把csv转成TUM

vins-mono保存的csv格式和evo默认使用的TUM格式并不完全一样,主要体现在时间戳单位和四元数顺序上。vins-mono输出的csv每行是:

timestamp(ns), tx, ty, tz, qw, qx, qy, qz

而TUM格式要求:

timestamp(s), tx, ty, tz, qx, qy, qz, qw

区别有两个:时间戳从纳秒换算成秒,四元数从“w在前”换成“w在最后”。很多人直接用csv跑evo报错,就是卡在格式上。

我一般用awk一条命令搞定转换,免去写Python脚本的麻烦:

awk -F',' 'NR>1{print $1/1e9, $2, $3, $4, $6, $7, $8, $5}' vins_result_loop.csv > vins.tum

这里NR>1是跳过第一行表头。原始csv的第5列是qw,所以输出时把它放到最后,就让第6、7、8列先输出,最后补$5,顺序正好变成qx, qy, qz, qw。转换之后用head -3 vins.tum检查一下,确认时间戳是毫秒/秒级别的小数,而不是十几位数的大整数,就说明单位处理正确了。

2.3 安装evo并确认版本

evo工具安装很简单,直接pip安装就行:

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

这里我建议加--no-binary evo,因为evo有些版本预编译的二进制包依赖匹配有问题,使用源码安装更稳。装完之后用evo --version确认版本,我目前用的是1.22.x,后面的命令都是基于这个版本测试的,如果版本太老,--plot_mode等参数的写法会有差异。

evo的核心概念不复杂,它就是把不同格式的轨迹统一成一个内部数据结构,然后计算两种误差:ATE(绝对轨迹误差)和RPE(相对位姿误差)。ATE衡量的是全局一致性,相当于把整条轨迹和真值对齐后算每个点的偏差;RPE衡量的是局部漂移,按固定步长计算相邻位姿之间的误差。评测一个SLAM系统,这两个指标都要看,单纯看ATE会忽略局部的跳变问题。

安装完成后建议跑一遍帮助命令熟悉参数:

evo_ape tum --help

重点看-a(对齐)、-s(尺度修正)、-r(误差类型)、--plot_mode(绘图模式)这几个参数的说明,后面实战都要用到。

3. 保存地图的完整实操

3.1 确认output_path并创建输出目录

vins-mono的保存逻辑非常简单,它会把运行结果写到配置文件里指定的output_path下。打开vins-mono的EuRoC示例配置文件,通常路径是:

~/catkin_ws/src/VINS-Mono/config/euroc/euroc_stereo_imu_config.yaml

里面有一行:

output_path: "/home/你的用户名/output/"

这一步最常见的坑就是忘记创建这个目录。vins-mono在保存文件时用的是std::ofstream直接向文件路径写数据,它不会自动递归创建目录。如果output_path指向的目录不存在,代码不会报错,只会静默保存失败,最后你翻遍整个文件夹也找不到任何csv文件。

正确做法是:

mkdir -p /home/你的用户名/output

确保目录存在,并且当前用户对它有写权限。我在自己的机器上习惯把output_path直接设为数据集同级的目录,比如/home/user/data/euroc/output/,这样每条数据的输出和原始bag在一起,后续处理不容易搞混。

3.2 正常跑完vins-mono会生成哪些文件

启动vins-mono跑完一段bag之后,正常情况下output_path下会生成这几个文件:

文件内容用途
vins_result_no_loop.csv未经过回环优化的轨迹对比回环前后效果
vins_result_loop.csv经过回环优化后的最终轨迹evo评测主力文件
pose_graph_map/关键帧位姿、特征点、回环信息的存档真正意义上的地图

启动命令大概是:

# 终端1 roslaunch vins_estimator euroc.launch # 终端2 roslaunch pose_graph pose_graph.launch # 终端3 rosbag play MH_01_easy.bag

跑完bag后,先别急着关节点,等2~3秒让后端线程处理完缓存,然后再Ctrl+C关闭pose_graph节点。我遇到过多次提前关闭导致vins_result_loop.csv最后几行没写进去的情况。关闭后检查vins_result_loop.csv的最后一行时间戳是否接近bag的结束时间,如果差了一截,说明保存不完整。

3.3 关键帧地图:pose_graph_map的手动备份

pose_graph_map文件夹是重载地图的核心,它里面包含:

  • keyframe_pose.csv:关键帧的位姿和时间戳
  • keyframe_index.csv:关键帧之间的索引关系
  • keyframe_point.csv:每个关键帧观测到的特征点在世界系下的坐标

如果只是做评测,这个文件夹可以不用管。但如果你想做“重载地图”的第二层——地图复用,这个文件夹必须和轨迹csv一起备份。我会在每次跑完数据后执行:

cp -r /home/用户/output/pose_graph_map /home/用户/data/euroc/MH_01_map/

并把对应的vins_result_loop.csv和配置文件也一起复制过去。这样一条数据就是一个完整的实验包,后续回放、对比、复现都很方便。

3.4 用tum格式导出地图轨迹

evoge的评测工具不认识vins-mono的csv格式,所以我在2.2里说要用awk转成tum。这里再补充一个细节:如果你跑到一半断电或者手动中断,csv可能最后有一行不完整的记录,awk转换时会把缺失字段当成0,生成一坨错误的轨迹点。稳妥的做法是转换前先用文本编辑器检查最后几行,或者用下面这个命令过滤掉异常行:

awk -F',' 'NR>1 && NF==8 {print $1/1e9, $2, $3, $4, $6, $7, $8, $5}' vins_result_loop.csv > vins.tum

NF==8这个条件保证只有完整8列的记录才会写出,不完整的行直接丢弃,能少踩很多坑。

同理,EuRoC的ground truth文件data.csv也要转成tum格式。EuRoC的ground truth每行格式是:

timestamp(ns), p_x, p_y, p_z, q_x, q_y, q_z, q_w

它本身四元数顺序已经和TUM一致,只需要转换时间单位:

awk -F',' 'NR>1{print $1/1e9, $2, $3, $4, $5, $6, $7, $8}' data.csv > gt.tum

注意EuRoC的data.csv第一行是表头,所以NR>1同样适用。转换完建议用wc -l看一下行数,和csv保持一致说明没有数据丢失。

4. 重载地图与evo评测实操

4.1 重载地图的基础思路

做完保存工作,重载地图就分两条路。

一条是代码层面的重载,也就是在vins-mono的pose_graph_node里调用loadPoseGraph,把之前保存的pose_graph_map读回来。这招在延续建图、多段数据拼接、回环加速这些场景下有用,但原版vins-mono的loadPoseGraph设计得比较简陋,加载完之后不会主动帮你做完整的重定位,你需要自己写代码去匹配当前帧和地图关键帧的特征点。新手不建议直接从这里入手,我的经验是先走通离线重载,再考虑在线复用。

另一条是离线重载,本质就是把保存的轨迹重新读进分析工具里做对比和评测。这也是我今天要重点讲的。离线重载不需要跑ROS,不需要启动相机节点,只要两个tum文件:一个是从vins_result_loop.csv转出来的估计轨迹,另一个是从EuRoC的data.csv转出来的真实轨迹。

我习惯在命令行里用evo_traj先看一眼两条轨迹的整体形状,确认坐标系方向一致、轨迹没有明显的跳点再往下算误差。

evo_traj tum gt.tum vins.tum --plot --plot_mode xy

如果估计轨迹和真实轨迹在xy平面上走向一致,只是位置偏移或者整体旋转,那说明初始化成功,只是世界坐标系定义不同,后面通过对齐参数就能纠正;如果走向完全对不上,多半是初始化阶段IMU没收敛,手里的数据本身就有问题,评测也无从谈起。

4.2 evo_ape计算绝对轨迹误差

轨迹重载进evo之后,第一个要算的是ATE。ATE的核心逻辑是把估计轨迹和真实轨迹做全局对齐,然后计算每个对齐后位姿与真值的误差,最后汇总成RMSE、MEAN、MAX等指标。全局对齐这一手很重要,因为vins-mono输出的轨迹坐标系是自己定义的,不可能正好和EuRoC的ground truth坐标系重合,不对齐直接算误差毫无意义。

我常用的命令是:

evo_ape tum gt.tum vins.tum -va --plot --plot_mode xyz --save_results ate_mh01.zip

参数的含义:

  • -v: verbose,输出中间过程的详细日志
  • -a: 用SE(3)的Umeyama算法做刚体变换对齐
  • -s: 使用尺度修正,这一步在单目VINS里几乎是必须的,因为单目轨迹存在尺度不确定性;如果是双目配置,可以不加s
  • --plot --plot_mode xyz: 画三维轨迹和误差曲线图

如果你是单目跑的vins-mono,-a-s两个参数一起上,命令是-as,实测下来效果最稳。因为单目估计的轨迹尺度和真实尺度之间有未知比例,SE(3)对齐只能纠正旋转和平移,纠正不了尺度,必须用Sim(3)变换把尺度也一起估计出来。

执行完终端里会输出一行统计汇总,类似:

APE w.r.t. translation part (m) (with SE(3) Umeyama alignment) max 0.312 mean 0.124 median 0.118 rmse 0.141 sse 4.235

我一般优先看rmse和max两个指标。rmse代表了整体精度水平,max则告诉你单点最差偏移有多大,在机器人导航里这个最大偏移往往比平均误差更能反映系统是否可靠。

4.3 evo_rpe与evo_traj的组合使用

ATE看全局,RPE看局部。推荐把两个指标配合使用。RPE计算的是相隔固定步长(比如每隔1秒或者每隔10帧)的两个位姿之间的相对误差,它能反映出轨迹的局部抖动和短期漂移,对IMU噪声、视觉里程计短时漂移很敏感。

evo_rpe tum gt.tum vins.tum -va --plot --plot_mode xyz --delta 1 --delta_unit m

--delta 1 --delta_unit m表示按1米的位移间隔来计算相对误差,这是我现在常用的配置。如果你更关心时间维度上的漂移,可以改成--delta 1 --delta_unit s,按1秒时间间隔计算。

evo_traj除了画轨迹,还能干一件事:导出对齐后的轨迹。有时候我们需要在论文里展示估计轨迹和真实轨迹的对比,或者把对齐后的轨迹喂给其他分析工具,这时候用:

evo_traj tum gt.tum vins.tum --align --correct_scale --save_as_tum

--align --correct_scale后导出的估计轨迹就已经和真实轨迹对齐并修正尺度了,可以直接叠加在真实轨迹上画图。

4.4 多组结果横向对比

如果你跑了好几组数据,比如MH_01到MH_05,或者同一组数据用了不同参数,逐条跑evo_ape当然可以,但最后汇总对比会很麻烦。evo提供了一个专门做批量结果汇总的工具evo_res。

先把每组数据的评估结果保存成zip:

evo_ape tum gt.tum vins_mh01.tum -as --save_results mh01.zip evo_ape tum gt.tum vins_mh02.tum -as --save_results mh02.zip

然后汇总:

evo_res mh01.zip mh02.zip mh03.zip --plot

evo_res会自动横向对比每组数据的RMSE、MEAN、MEDIAN、MAX等指标,并用箱线图展示误差分布。这个方法对我写实验报告帮助很大,每次对比不同参数下的精度表现,就靠这个批量方式出图,不需要自己写一堆统计脚本。

还有一个实用技巧:输出表格。

evo_res mh01.zip mh02.zip --table

终端会直接打出一张Markdown格式的对比表,可以直接粘到笔记里,不用手动整理。

5. 踩坑记录与常见问题速查

5.1 时间戳不同步的问题

运行evo_ape时如果报错说两条轨迹的时间范围没有交集,或者报错说找不到配对帧,十有八九是时间戳问题。VINS输出csv的时间戳是相机帧的时间戳,EuRoC ground truth的时间戳是IMU或位姿的时间戳,两者并不严格对齐,但时间范围应该基本一致。先检查一下:

head -1 vins.tum tail -1 vins.tum head -1 gt.tum tail -1 gt.tum

如果gt.tum的起始时间比vins.tum晚了很久,说明你转换时时间单位出了问题。EuRoC csv的时间戳是纳秒,转换成秒要除以1e9,如果只除以1e6变成毫秒,时间就会大一千倍,轨迹完全错位。

另一个解决办法是用evo自带的插值配对机制。正常运行下,evo_ape会对估计轨迹和真实轨迹按照时间戳做线性插值配对,只要两边时间范围有重叠就能跑。万一某条轨迹在某个时间段丢了数据,插值出来的配对点可能不准确,这时可以先用尾部时间戳截取两段轨迹的公共时间范围再评测。

5.2 单目尺度不收敛导致误差大

跑单目vins-mono时,如果ATE的rmse动辄好几米,先别急着怀疑代码改坏了。很大概率是尺度没有收敛,或者初始化时IMU和视觉没有完全对齐。我的排查流程是:

  • 先看vins-mono运行时的终端输出,确认初始化是否成功,初始化阶段有没有出现“IMU-Cam geometry not fixed”之类的警告。
  • 播放bag时确保前几秒相机保持静止或者非常缓慢移动,帮助IMU收敛。
  • 评测时使用-as参数做Sim(3)对齐,把尺度误差从ATE里剔除掉,然后再看rmse是否降到米级以下。

如果用了-as之后误差从几米降到0.1米级别,说明算法本身没问题,只是尺度没有初始化好,这在单目VINS里是正常现象。

5.3 pose_graph_map保存失败

很多人备份pose_graph_map时发现文件夹是空的,或者里面只有一半文件。原因通常有两个:一是output_path目录不存在导致写入静默失败;二是bag播放完立刻关闭了pose_graph节点,大地图还没写盘就被中断。

我现在的标准操作是,bag播放完以后等3到5秒再关节点,关掉节点后手动检查pose_graph_map下的三个csv文件大小,都大于0才算成功。另外,如果你的数据集比较长,比如MH_05有接近6分钟,保险起见在关闭节点前用sync命令强制刷一下缓存:

sync

5.4 常见问题速查表

问题原因解决方法
vins_result_loop.csv不存在output_path目录不存在mkdir创建目录后再运行
csv转tum后轨迹形状混乱四元数顺序未调整确认TUM格式是qw在最后,用awk打印$6 $7 $8 $5
evo报“no matching timestamps”时间戳单位不一致或时间范围没有交集检查awk除以的系数,用head/tail检查时间范围
ATE误差极大单目尺度未对齐-as参数做Sim(3)对齐
pose_graph_map空文件节点提前关闭或目录不可写跑完等3~5秒再关节点,检查目录权限
evo_ape绘图乱码matplotlib中文字体问题设置export LANG=en_US.UTF-8或调整evo配置
bag播放速度太快导致初始化失败IMU未充分激励rosbag play --rate 0.7降速播放

这些坑我基本都踩过一轮,尤其是时间戳和scale问题,几乎每次换数据集都会遇到。建议读者在自己的机器上把上面几个检查命令存成一个脚本,每次处理新数据时自动跑一遍,能省掉很多排查时间。

6. 一点个人经验总结

现在我自己处理vins-mono数据时的标准流程已经固定:先建好output目录,跑完数据先检查csv完整性,再转成tum并用evo_traj快速看轨迹形状,形状正常再算APE和RPE,最后一组数据用evo_res汇总出对比表。整套流程熟练后,从一包raw数据到一张误差统计表,十分钟之内就能完成。

如果你后续想做得更深一点,我建议把vins-mono输出的轨迹和真值轨迹做差,画成时间序列曲线,这样能很直观地看到在哪一段飞行中漂移最严重,再回头对照bag里那段图像序列,往往能发现是快速旋转、光照变化还是动态物体遮挡造成的精度下降。这一招在我们排查定位失控问题时特别有效,比单纯看一个rmse数字信息量大得多。

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

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

立即咨询