做视觉SLAM的都知道,跑通一个建图定位流程只是第一步,真正让人头大的是后面那句“到底准不准”。上周我帮同事调一段机器人的定位结果,放到数据集自带真值上一比,RMSE直接飘到0.8米,排查了半天才发现是时间戳没对齐,估计轨迹和真值差了半个周期。这种坑,如果没有一个顺手的评估工具,排起来非常痛苦。
这篇是视觉SLAM笔记第3篇,重点整理轨迹评估工具evo的安装与使用:从为什么必须评估,到数据格式区别,再到常用命令和常见报错,一次性把轨迹评估这一关打通。evo是目前视觉SLAM圈子里用得最多的离线评估工具之一,能读多种轨迹格式、计算ATE和RPE、生成带误差着色的轨迹图,适合做算法研究、系统调参、以及论文实验数据输出的各种场景。下面写的都是我自己在项目里跑过的流程,可以直接照着抄。
1. 为什么轨迹评估是视觉SLAM里绕不开的一关
1.1 轨迹是SLAM的“成绩单”
SLAM系统在干什么?本质上就是一边估计传感器自身的位置姿态,一边构建环境地图。不管是视觉里程计、闭环检测还是后端优化,最后落在用户面前的,通常是一条随时间变化的位姿轨迹。所以评价一个SLAM系统好不好,最直接的方式就是“把这条轨迹拿出来和标准答案对一对”。
标准答案就是真值轨迹,英文叫ground truth,通常来自高精度运动捕捉系统、激光雷达建图结果或仿真环境。真值不是绝对没有误差,但它的精度比你要评估的SLAM系统高至少一个数量级,可以当作参考基准。评估要做的事情很简单:把SLAM输出的估计轨迹和真值轨迹放在同一时间轴、同一坐标系下,逐点比较位姿差异,算出各项统计指标,再画成图让人一眼看出哪里漂了、哪里跳了。
我见过不少人跑通了一个开源SLAM,看到终端刷出一堆关键帧就觉得很兴奋,结果问他“精度是多少”,答不上来。这就是典型的“只会跑、不会评”。轨迹评估不是可有可无的加分项,它是衡量系统是否达到设计指标、改动算法后是否有提升的唯一手段。没有评估,你的SLAM永远只是“看起来能跑”。
1.2 ATE与RPE:两个评价维度的区别
轨迹评估里有两个绕不开的术语:ATE(Absolute Trajectory Error,绝对轨迹误差)和RPE(Relative Pose Error,相对位姿误差)。这两者经常被混用,但它们的物理含义和适用场景完全不同。
ATE衡量的是整条轨迹的全局一致性。计算方式是把估计轨迹和真值轨迹做对齐之后,逐位姿求平移误差,得到一组误差值,再统计标准差、均方根误差等指标。ATE能直观反映系统在全局坐标系下的定位精度,比如“走了50米后最终偏了多少”。它适合用来评价闭环效果和整体漂移,论文里最常见的那个RMSE表格,大多数时候就是ATE的RMSE。
RPE衡量的是局部帧间误差。它不拿整条轨迹和真值比,而是比较“间隔一段时间或一段距离的两个位姿之间的相对变换差”。比如相隔1秒的两帧,估计轨迹给出的相对运动是向右转了5度、走了0.2米,真值给出的是向右转了4.8度、走了0.19米,那么这两个差值就是局部的相对误差。RPE更适合评价里程计短时间内的平滑性和局部精度,对闭环调整之类的大范围修正不太敏感。
我做实验的时候习惯两个都算:ATE看大局,RPE看细节。如果ATE很差但RPE很好,说明系统局部运动估计没问题,问题出在累积漂移或闭环;如果ATE很好但RPE很差,说明全局被外部约束拉住了,但局部估计抖动明显。两个指标互相补充,比单看一个全面得多。
| 指标 | 比较对象 | 反映的问题 | 常用场景 |
|---|---|---|---|
| ATE | 整条轨迹与真值逐点对比 | 全局漂移、闭环效果 | 论文精度表、系统整体评估 |
| RPE | 局部相对变换与真值对比 | 局部平滑性、里程计精度 | 里程计调参、前端优化 |
2. evo能干什么,为什么我最终留下它
2.1 一次性覆盖格式解析、指标计算、可视化
evo是一个用Python写的轨迹评估工具包,功能可以概括成四块:格式解析、轨迹编辑、指标计算、可视化。格式解析是它的基本功,TUM格式、KITTI格式、EuRoC格式、ROS bag格式都能直接读,省去了自己写解析脚本的麻烦。轨迹编辑包括时间戳截取、坐标变换、轨迹拼接、格式转换等。指标计算就是上面说的ATE和RPE,支持多种对齐方式和参数选择。可视化则可以把两条轨迹画在一张图里,还能按误差大小给轨迹着色,方便直观定位误差大的区段。
我最喜欢evo的一点是,它把所有评估相关的操作统一成了几个命令行工具,不需要打开Python解释器写脚本。输入是轨迹文件,输出是终端里的指标表格和一张图,中间过程全部封装好。用惯之后,我想从TUM格式换成KITTI格式做对比,只需要换一个命令参数,不需要重写代码。
2.2 与早期脚本工具对比的一次实测
在没有evo之前,很多人用的是TUM数据集官方提供的一套Python评估脚本,比如evaluate_ate.py和evaluate_rpe.py。那套脚本本身没大问题,但用起来有几个别扭的地方:一是只能吃TUM格式,换成KITTI或EuRoC就得自己先转格式;二是只输出文本指标,画图要另外调matplotlib;三是参数不统一,不同脚本之间的调用习惯完全不同。
我实测过一个场景:手里有一份某开源SLAM跑出来的关键帧轨迹和一份EuRoC数据集真值,官方脚本要先写一段代码把EuRoC CSV转成TUM文本,再设置一堆路径参数,最后输出一个txt。而evo这边,直接一条命令读EuRoC格式,另一条命令读TUM格式,再加一个对齐参数就能出指标和轨迹图。整个流程从十分钟缩短到三十秒,而且是可复现、可批量执行的命令行。从那以后我就把所有轨迹评估都固化到了evo上。
| 能力点 | 早期脚本工具 | evo |
|---|---|---|
| 输入格式 | 主要是TUM | TUM、KITTI、EuRoC、bag |
| 指标计算 | ATE/RPE分开脚本 | 统一evo_ape/evo_rpe |
| 绘图 | 需要另外写代码 | 自带matplotlib绘图 |
| 批量对比 | 不方便 | evo_res一键合并 |
| 参数化对齐 | 有限 | 支持SE3、Sim3等多种对齐 |
3. evo安装:流程与版本避坑
3.1 前置环境准备
evo本质上是若干Python包的综合体,依赖numpy、scipy、matplotlib、pandas、seaborn这些常见科学计算库。安装前建议先把Python环境理清楚,我见过太多人在系统Python里直接pip install,结果把一堆项目环境搞乱了。
如果你平时不跑其他深度学习的代码,系统Python还够用;但只要机器上还有其他项目,我强烈建议建一个独立的虚拟环境,把evo和SLAM其他工具隔离。建环境的命令很简单:
python3 -m venv evo_env source evo_env/bin/activateWindows下就是:
python3 -m venv evo_env evo_env\Scripts\activate有conda环境的也可以用conda创建一个干净环境。这一步不是必须的,但能避免很多“装完evo之后OpenCV突然报错”之类的连锁反应。尤其是numpy这个包,evo对它的版本要求和其他视觉库经常打架,我后面会专门讲一次踩坑经历。
3.2 安装、检验、目录定位
环境准备好之后,安装evo就是一个pip命令的事:
pip install --upgrade pip pip install evo --upgrade --no-binary evo里面--no-binary evo这个参数比较关键,它让pip不要直接抓二进制的预编译包,而是从源代码构建。这么做的好处是可以避免一些平台相关的二进制兼容问题。如果你的网络比较快、编译工具链齐全,通常一两分钟就能装完。如果只装预编译包也能用,但出诡异报错时,回退到源码安装往往能解决。
装完之后验证一下:
evo pkg evo --versionevo pkg会输出evo的安装路径、版本号、依赖包列表,看起来像一串环境报告。evo --version则会显示版本信息。看到这些命令正常响应,说明安装成功。
如果出现“command not found”或“evo: 未找到命令”,多半是Python的Scripts目录没在PATH里。虚拟环境激活后一般不会碰到这个问题;如果是系统级安装,检查一下pip所在的目录是否已加入PATH。
4. 数据格式准备:TUM、KITTI、EuRoC三兄弟
4.1 三种格式的字段差异
用evo评估之前,首先要确认你的轨迹文件属于哪种格式。evo把数据格式做得很有辨识度,但前提是你自己得认得出来。最常见的三种格式是TUM、KITTI、EuRoC。
TUM格式来自TUM RGB-D数据集,是最常见也最好懂的纯文本格式。每一行8个数:时间戳、x、y、z平移量、四元数qx、qy、qz、qw旋转量。时间戳单位是秒,例如一行数据大概长这样:
1403636579.522000 0.000 0.000 0.000 0.0000 0.0000 0.0000 1.0000KITTI格式来自KITTI数据集,每行12个数,本质是一个3x4的变换矩阵按行展开。它把旋转和平移拼在一起,没有时间戳。如果你看到一行数据里前9个数字像一个旋转矩阵、后3个数字像平移量,那基本就是KITTI格式了。
EuRoC格式和TUM很像,也是时间戳加平移加四元数,但它的CSV文件带表头,时间戳单位是纳秒,字段名称类似#timestamp[ns] p_RS_R_x [m] q_RS_R_x []这种带方括号的写法。一眼看上去字段名特别长,这是它和TUM的一个明显区分点。
| 格式 | 每行字段 | 时间戳 | 典型文件 |
|---|---|---|---|
| TUM | 8列:时间戳、xyz、四元数 | 秒 | 关键帧轨迹、数据集真值 |
| KITTI | 12列:3x4变换矩阵展开 | 无 | 双目/激光里程计输出 |
| EuRoC | 带表头CSV,字段多 | 纳秒 | 无人机数据集真值、IMU数据 |
4.2 格式转换操作
不同格式之间互转,是评估流程里经常要做的一步。evo提供了一条命令直接完成,不需要自己写解析脚本:
evo_traj tum keyframe_traj.txt --save_as_kitti traj.kitti这句话的意思是把TUM格式轨迹转成KITTI格式并存到traj.kitti文件里。类似地,你还可以用--save_as_tum把KITTI或EuRoC转成TUM格式。需要注意的是,从没有时间戳的KITTI格式转成带时间戳的TUM格式时,evo没法无中生有时间信息,所以转换前要确认你的源数据是否真的缺少时间戳。
我自己常用的习惯是:所有待评估轨迹统一转成TUM格式作为中间层,因为TUM格式既带时间戳又容易被其他工具读取。真正要出KITTI风格结果时再转成KITTI格式输出。这样格式链路比较统一,不容易搞混。
5. 对齐原理与可视化参数
5.1 Umeyama对齐:把两套轨迹先“叠”在一起
很多人第一次用evo时有一个困惑:为什么我的SLAM轨迹明明跑得很好,ATE却大得离谱?原因多半是两条轨迹不在同一个坐标系下,或者尺度不一致。
拿单目视觉SLAM举例,相机轨迹是在一个任意定义的初始坐标系里重建出来的,没有绝对的物理尺度;真值轨迹则在另一个坐标系里,单位是米,初始朝向也不一样。如果直接把这两个坐标系的轨迹逐点相减,误差当然大到没有意义。所以你需要在比较之前,先把估计轨迹通过旋转、平移、缩放,把它尽可能贴合到真值轨迹上,这一步就是对齐。
evo使用的对齐算法是Umeyama算法,它本质上是求解一个最优刚体变换或相似变换,让两条轨迹对应点之间的距离平方和最小。用大白话说就是:先尽量把两条轨迹“叠”在一起,再来比较它们剩余的形状差异。这个“叠”的过程是在评测框架内完成的,不是SLAM系统的算法部分,所以不会“作弊”;它评估的是轨迹形状的相似程度,而不是坐标系的绝对一致性。
在evo里控制对齐的参数是-a和-s。-a是使用SE3刚体变换对齐,只做旋转和平移,不缩放;-s是同时修正尺度,适合单目这类尺度不确定的轨迹;两者合起来用就是-as,对应Sim3相似变换对齐。双目、RGB-D这类有明确物理尺度的系统,一般用-a就够了;单目系统,尤其是没有尺度约束的,建议用-as。
5.2 画图常用的几个参数
evo画图功能很实用:一句--plot或-p就能弹出一个窗口展示轨迹。常用参数里,--plot_mode控制投影模式,xyz画三维立体轨迹,xy、xz、yz画二维投影。我一般先看三维全貌,再看二维投影,更容易定位误差发生的位置。
另一个好用的参数是--plot_colormap_max_threshold和--plot_colormap_min_threshold,它们控制轨迹上颜色的阈值范围。evo默认把误差大小映射到颜色条上,红色大、蓝色小;但默认范围可能被个别离群点撑得很宽,导致大部分轨迹都呈现同一种颜色。手动指定阈值后,误差分布会更清晰。还有个--plot_colormap可以选色彩映射风格,比如jet、viridis,视觉习惯因人而异,可以自己试。
如果你不想要弹窗,而是直接把图保存成文件,可以用--save_plot参数,后面接文件名,例如--save_plot traj.png。在服务器上跑实验没有图形界面时,这个参数非常重要。
6. 核心命令实战:从单条轨迹到批量对比
6.1 evo_traj:先把轨迹本身看清楚
实际评估前,我习惯先用evo_traj看一眼轨迹文件本身长什么样,确认格式没问题、轨迹长度合理、有没有明显跳变。最简单的命令:
evo_traj tum keyframe_traj.txt -p --plot_mode=xyz这条命令会把TUM格式轨迹画成三维图。如果轨迹文件能正常解析,终端会打印出类似“Loaded XX poses”的信息,同时弹出图像窗口。看到轨迹形状与你运行时预期一致,再继续下一步。
如果我想把估计轨迹和真值一起画出来对比,可以这样:
evo_traj tum keyframe_traj.txt groundtruth.txt -p --plot_mode=xyz --align --correct_scale在evo_traj里也可以加--align和--correct_scale进行对齐,这样两条轨迹会先对齐再显示,能明显看出它们形状一致但存在细小的偏移。这一步非常直观,通常一眼就能判断系统大致表现得怎么样。
6.2 evo_ape:绝对轨迹误差的计算
评估ATE,使用evo_ape命令。基本用法是:
evo_ape tum groundtruth.txt keyframe_traj.txt -va --plot --save_results ate.zip命令行的参数顺序是格式、真值文件、估计文件。-v是详细输出,-a是做SE3对齐,--plot绘图,--save_results把结果保存成zip。终端最后会给出一个统计表格,包含maximum、mean、median、rmse、sse、std等指标。论文里常见的RMSE就是其中一行。
单目系统这里要加-s参数修正尺度:
evo_ape tum groundtruth.txt keyframe_traj.txt -as --plot-as同时做旋转、平移和缩放对齐。如果你不确定系统尺度是否可靠,直接先用-as,再看结果里修正出来的缩放系数是否接近1。如果缩放系数偏离1很多,说明单目尺度漂移比较明显。
6.3 evo_rpe:局部误差的计算
评估RPE,使用evo_rpe命令。RPE需要指定比较相邻帧的时间间隔或距离间隔,用--delta参数控制。一个常用组合:
evo_rpe tum groundtruth.txt keyframe_traj.txt -r trans_part --delta 1 --delta_unit frames -va --plot --save_results rpe.zip-r trans_part的意思是只计算相对平移误差,--delta 1表示间隔1帧,--delta_unit frames表示单位是帧而不是米或秒。如果你想看每间隔多少米产生的相对误差,可以把--delta_unit换成meters并设置对应的--delta值。-r还有其他选项,比如rot_part只算旋转误差、full算完整的位姿误差、angle_deg算旋转角度误差。多数场景下trans_part最直观。
RPE输出的统计表格里也会有RMSE、Mean等指标。我在调前端里程计参数时,会反复跑这条命令,观察RPE的RMSE有没有下降。它比ATE对局部算法改动敏感得多,改一个小参数就能看出变化。
6.4 evo_res:多组结果横向PK
做算法对比时,手里往往有多个版本的轨迹文件:改动前的、改动后的、加了闭环的、换了一组特征的。来回跑evo_ape再人工记表格很麻烦,evo提供了evo_res命令把多组评估结果合并成一张对比表。
需要先把每组实验的ATE结果保存成zip,方法是在evo_ape命令里加--save_results参数。然后运行:
evo_res ate_v1.zip ate_v2.zip ate_v3.zip -p --save_table results.csv这条命令会把所有zip里的指标统计汇总成一张表,按实验名分行,各项统计指标按列出,还能画成误差对比图。我在写实验报告时,经常用这个命令一次性生成多组对比,再导出CSV文件放进报告里,效率很高。
evo_res有一个容易忽略的小细节:zip文件的命名会决定表格里的行名。保存结果时最好把文件名取得能对应上实验配置,比如ate_loop_closed.zip、ate_no_loop.zip,否则之后看表格还得回忆这个zip是哪个版本,非常浪费时间。
7. 常见问题与排查实录
7.1 高频问题对照表
用evo这么久,我遇到过不少报错,也帮别人解决过很多类似问题。整理一个高频问题对照表,基本覆盖最常见的坑。
| 现象 | 可能原因 | 处理办法 |
|---|---|---|
安装后找不到evo命令 | Python Scripts目录不在PATH | 确认虚拟环境已激活,或把pip脚本路径加入PATH |
| 解析轨迹时报“无法识别格式” | 文件前几行有注释或格式不标准 | 删除注释行,确保字段数与格式要求一致 |
| ATE数值巨大、轨迹图完全错开 | 两条轨迹坐标系不一致 | 加上-a做刚体对齐,单目再加-s |
| 轨迹有时间偏移,首尾对不上 | 时间戳没有对齐 | 检查时基单位,TUM用秒、EuRoC用纳秒,必要时用参数截取重叠时间区间 |
| 绘图报LaTeX相关错误 | 系统LaTeX环境与matplotlib冲突 | 执行evo_config set plot_usetex false关闭tex渲染 |
| 图弹不出来只有文本输出 | 服务器或远程终端没有图形界面 | 用--save_plot保存图片,用evo_res时记得保存结果 |
| 不同实验的结果无法对比 | 没有保存zip,或保存路径混乱 | 每次实验固定生成zip,用有含义的文件名管理 |
| RPE输出变化不明显 | 间隔选取不合适 | 调整--delta和--delta_unit,找到能体现问题的尺度 |
这里我想重点说一个容易被忽略的问题:时间戳单位。TUM格式的时间戳是秒,EuRoC是纳秒,如果你把一个EuRoC真值文件按TUM格式硬读进去,时间戳会差十亿倍,轨迹匹配自然一塌糊涂。官方数据都带明确说明,但到了实际项目里,你自己记录的轨迹文件可能就没有那么规范了。所以我建议拿到任何轨迹文件后,先打开看前几行,确认时间戳量级和字段结构,再往评估流程里送。
7.2 几点个人经验
我会以实操中最常用的一套固定流程来收尾,这不是什么玄学技巧,只是一些踩过坑之后沉淀下来的习惯。
第一,所有的评估结果都要保留原始输入文件。有人会只保存zip和图片,几个月后想复盘时发现原始轨迹文件已经没了,数据不可复现。我的做法是每个实验建一个目录,里面放轨迹文件、真值文件、命令脚本、结果zip,命令脚本就是要跑的那几条eval命令。这样任何人拿着这个目录都能复现出同一张评估表。
第二,绘图打开前先设好配色范围。默认的色条范围经常被个别离群点拉爆,导致看不出误差的空间分布,我每次都会指定--plot_colormap_max_threshold和--plot_colormap_min_threshold,让颜色分布和误差的实际量级匹配。
第三,调参对比时要固定评估参数。比如--delta、--delta_unit、是否加-as,这些只要换一个,结果就不具备可比性。我见过不少人对比两次实验,结果一次用了SE3对齐、一次用了Sim3对齐,然后在群里讨论半天“为什么误差变化这么大”,其实根本没有可比性。把评估命令写死,只允许轨迹文件变化,结论才干净。
第四,偶尔会遇到“装完evo,其他依赖被升级导致项目报错”的情况,这是很经典的numpy、scipy版本连锁问题。如果你机器上还要跑其他视觉项目,最稳妥的办法就是把evo放进独立虚拟环境,或者至少用pip freeze记录依赖状态,出问题能快速恢复。
我自己的体会是:轨迹评估工具看着简单,但用顺了之后,它其实是你判断SLAM算法好坏的一把标准尺子。没有这把尺子,一切“感觉变好了”都不算数。把这套流程固定在日常实验里,你会发现自己对SLAM系统的理解会比单纯跑demo快很多。