☰
VINS-Mono轨迹飘飞解决实录:双目+IMU标定与时间戳排查
2026/10/16 8:26:15 网站建设 项目流程

最近把S系列双目相机接到工控机上跑VINS-Mono,原本以为半个小时内就能看到漂亮的轨迹图,结果第一版跑起来,设备还没走两步,轨迹直接往天上一拐,接着像喝醉了一样在原地画圈。反复调了一周,最后发现问题的根子全在几个不起眼的配置项里。这篇就来记录一下这个“轨迹飘飞解决版”的完整过程,包括现象分析、标定细节和可直接复制的配置参数,给同样用S系列或者其它双目+IMU组合跑VINS-Mono的朋友一个参考。

先说结论:轨迹飘飞不是某一个参数坏了,而是标定外参、时间戳、IMU噪声、图像曝光等多个因素的叠加。单独改任何一个参数都只能压制一部分症状,只有把数据链路从头捋一遍,让VINS-Mono拿到“干净”的输入,轨迹才能稳下来。下面按我实际踩坑的顺序来写,没有废话,全是操作。

1. 先认清轨迹飘飞的现象和根因

1.1 飘飞不是一个现象,而是好几种症状

我一开始以为“轨迹飘飞”就只有一种画面,后来反复测试才发现它通常以四种形态出现,而且每一种对应的排查方向完全不一样。

第一种是竖直方向直接飞走。轨迹原本应该贴着地面走,结果跑了没几秒就往Z轴正方向冲,像是坐电梯直接上天。这种问题多半出在重力加速度对齐上,也就是IMU加速度计数据和相机坐标系之间的旋转关系没对齐。

第二种是原地打转,轨迹画成一团乱麻。设备明明在往前推,轨迹却像在绕圈,速度越快圈越小。这一般是IMU角速度和图像运动之间出现了系统性偏差,常见原因是外参里的旋转矩阵方向反了,或者角速度噪声参数设置得太小,让VINS过信IMU的角速度积分。

第三种是水平方向上缓慢漂移。设备走完一圈回到起点,轨迹却没有闭合,误差随时间累积越来越明显。这种通常不是突然爆掉,而是慢慢偏,常见原因是加速度计噪声偏大、陀螺仪零偏估计不准,或者图像帧率太低导致视觉约束不足。

第四种是运行几秒后直接跳到另一个位置,像瞬移一样。这种往往是图像特征点瞬间丢失,回环检测又错误匹配,或者是时间戳突然出现跳变,导致VINS把两帧图像当成了同一时刻的数据。

1.2 我的最终排查结论

我刚开始总想找一个“核心参数”,觉得改一改就能彻底解决。后来把标定、配置、launch文件全部重新走了一遍,才发现是四个问题叠在一起:出厂外参和实际安装位置存在毫厘级偏差、图像与IMU时间戳没有严格同步、IMU噪声参数沿用默认值、相机自动曝光导致图像亮度跳变。这四个问题单独拿出来,每一个都会引起小幅漂移,放在一起就直接“放飞自我”。

所以这个“解决版”要做的,不是打某个神秘补丁,而是把数据链路里最容易出错的环节全部校准到位。

2. 硬件与驱动准备:先让数据本身是对的

2.1 把数据频率和时间戳打到台面上核实

跑VINS-Mono之前,我建议先用rostopic把相机和IMU的原始数据频率都过一遍。S系列相机驱动起来之后,先看图像话题和IMU话题到底叫什么名字、频率是多少、时间戳是不是以秒为单位连续递增。这一步很多人会跳过,直接打开launch文件就开跑,飘飞之后再来找配置问题,其实顺序反了。

我自己实际用的命令是这样的:

rostopic hz /camera/color/image_raw rostopic hz /imu/data_raw rostopic echo /imu/data_raw -n 3

rostopic hz能看出频率是否稳定。像IMU话题如果一会儿200Hz一会儿180Hz,VINS的预积分就会出现额外误差。rostopic echo则重点看header.stamp和header.seq。如果seq连续递增但stamp偶尔回跳或出现零值,说明驱动里时间戳处理有问题,需要在驱动层修复,而不是指望VINS-Mono自己扛过去。

这里还要注意一个细节:S系列相机的硬件时间戳可能和系统时钟不是同一个源。如果驱动设置不当,图像和IMU话题上的时间戳一个是传感器时钟,另一个是系统接收时钟,两种时间戳混在一起,哪怕频率完全正常,VINS也会认为数据时序错乱。

2.2 时间戳偏差到底要控制在多少才算安全

VINS-Mono的视觉惯性对齐对时间戳偏差非常敏感。很多资料会告诉你“毫秒级误差可以接受”,我在实际测试里发现这个说法太宽松了。图像时间戳和IMU时间戳之间的偏差如果稳定在3毫秒以内,影响还不明显;一旦到了10毫秒量级,轨迹就会在转弯时出现肉眼可见的横向漂移;超过20毫秒,旋转过程中直接飘飞。

我用S系列相机做的一个简单测试:把图像时间戳人为平移30毫秒,短距离直线行走的轨迹误差从0.5%涨到了4%以上,而且在大幅转动时出现Z轴跳变。这说明时间同步问题优先度极高,必须先处理。

验证时间偏差的方法很简单:在相机前快速晃动设备,录制一段bag,然后离线对比图像曝光中间时刻和IMU数据的变化趋势。也可以用VINS自带的在线时差估计参数estimate_td来粗评,但后面我会说明这个参数不能替代硬件层面的同步修正。

如果驱动层面无法直接统一时钟,退一步的办法是录bag时用record的--duration配合外部时间服务,或者写一个节点把IMU时间戳和图像时间戳按最小二乘拟合对齐。最省事的方式是直接用驱动提供的传感器时间戳,前提是驱动文档里明确说明图像和IMU共用一个时钟源。

3. 修改VINS-Mono配置文件,把每个参数讲明白

3.1 内参、畸变、外参:不能直接抄出厂标定

S系列相机出厂时会提供一份标定文件,里面包含左右目的相机内参、畸变系数,以及IMU到左目相机的外参。很多人直接把这些数值填进VINS配置里,我就是这么做的,结果轨迹飘给我看。

原因很简单:出厂标定是在特定温度、特定应力状态下得到的,相机模组在装配和运输过程中可能产生微小位移。特别是外参里的旋转矩阵,哪怕只偏了1度,跑几十米后横向误差就非常可观。IMU到相机的平移即使偏了1毫米,同样会在转动手腕时被放大成明显的漂移。

因此强烈建议拿到相机后自己重新标定一次。用棋盘格走一遍Kalibr抑或其它标定工具,算出来的内参、畸变、相机与IMU外参,比任何出厂值都可靠。如果你完全没标定经验,也至少要做一个“外参粗检查”:让相机固定,记录IMU静止时的重力方向,再对比相机观察到的静止环境,调整外参旋转部分使两者一致。

在VINS配置文件中,内参长这样:

cam0: camera_model: pinholedistortion distortion_coeffs: [0.0, 0.0, 0.0, 0.0] intrinsics: [450, 450, 320, 240]

这里intrinsics依次是fx, fy, cx, cy,distortion_coeffs顺序是k1, k2, p1, p2。如果用鱼眼镜头,要把模型改成equidistant或对应模型,否则图像边缘畸变会直接误导特征匹配。

外参部分放在VINS配置的body_T_cam0字段。注意这个矩阵表示的是IMU本体坐标系相对于相机坐标系的变换,还是相机相对于IMU的变换,不同版本VINS的约定略有差异,一定要确认清楚。如果方向写反,轨迹会立刻以螺旋形式飘走,这是我见过最直接的外参错误表现。

3.2 IMU噪声参数初始值怎么给

VINS配置里的acc_n、gyr_n、acc_w、gyr_w这四个参数决定了VINS对IMU数据有多信任。给太大会让视觉主导,轨迹容易抖动;给太小则会让IMU积分主导,漂移会迅速累积。

用S系列这类消费级IMU时,我常用的初始经验值如下表:

参数含义经验值
acc_n加速度计噪声密度0.05
gyr_n陀螺仪噪声密度0.005
acc_w加速度计随机游走0.0005
gyr_w陀螺仪随机游走0.000005

这四个值的单位是连续时间密度,不是离散采样的标准差。如果驱动输出的IMU数据做了滤波平滑,噪声密度会比原始指标更小,这时可以适当调低gyr_n和acc_n;如果数据里明显有振动干扰,就要调大一点。VINS在静止时会自己估计零偏,所以噪声参数不用特别精确,但也不能差一个量级以上。

我踩过的一个具体坑是:把gyr_n从0.005改成0.0005后,轨迹在旋转时表现出“过刚”的倾向,明明已经停下来,轨迹还会维持一小段惯性漂移。后来恢复默认值并配合准确外参,情况立刻好转。

3.3 estimate_extrinsic设0还是设1

VINS的estimate_extrinsic参数控制是否在线估计相机与IMU外参。设成1表示让VINS在运行过程中自行优化外参,设成0则直接读取配置里的body_T_cam0。

很多教程为了省事,建议直接把estimate_extrinsic设成1。我的实际经验是:在标定准确度未知的情况下,设为1确实能让轨迹不飘得那么夸张,但它收敛到的结果未必是真实外参,而且会在某些运动激励不足的场景下收敛到错误局部解。更稳的操作是分两步走:先用一段弱光照、多旋转的bag,把estimate_extrinsic设为1,运行完成后记录它估计出的外参;然后把收敛后的外参写回配置,并把estimate_extrinsic设回0正式跑。

为什么最后要设回0?因为在线外参估计需要充分的旋转激励,一旦常规使用中运动类型单一,VINS就会反向干扰外参,导致结果反而变差。固定外参至少让系统少一个自由变量,稳定度会高很多。

这里还要注意,外参通常涵盖旋转和平移两部分。VINS对旋转的敏感性远高于平移,所以哪怕平移给得粗略一些,旋转部分也必须精确。我拿到的出厂标定文件里旋转矩阵看起来是单位阵附近的值,实际标定后发现绕Z轴偏了约1.8度,这就是轨迹旋转飘飞的重要原因。

3.4 estimate_td到底要不要开

estimate_td控制是否在线估计图像和IMU的时间偏差。如果驱动已经做到5毫秒以内的同步,我建议设成0,避免额外估计造成抖动。如果不太确定时间同步质量,先设成1,让VINS自己把时间对齐,轨迹会有明显改善。

我用S系列实际测试时发现,estimate_td在视觉初始化成功后大概能修正10毫秒量级的偏差,但修正过程会伴随短暂的前几秒轨迹晃动,之后趋于稳定。如果你在启动时看到轨迹刚起步有点扭曲,过了两三秒又好了,这个参数通常在起作用。

需要注意,estimate_td不能替代驱动层的时间同步修复。如果时间戳本身就有时而连续时而跳变的坏点,在线估计反而可能被坏点带偏。所以规范顺序依然是:先保证驱动层时间戳干净,再决定是否开在线估计。

4. 实操流程:从头到尾跑通一个解决版

4.1 录制数据的三条铁律

跑VINS-Mono解决飘飞,不要一上来就拿实时数据试,后面改参数会很痛苦。我习惯先录一段bag,在固定环境里反复测试配置,每改一个参数都回放同一段数据对比轨迹。这样能确保看到的变化是命令引起的,而不是环境变化造成的。

录制bag的时候有三条铁律,任何一条不满足都可能为后续排查埋雷。

第一,启动录制后保持设备完全静止至少2秒再开始移动。VINS初始化阶段需要静止来估计重力方向和陀螺仪零偏,如果一上来就动,初始零偏就会带偏整条轨迹。哪怕后面算法再有回环修正,起始段的误差也会持续影响全局。

第二,关闭相机的自动曝光和自动白平衡。S系列相机的驱动里如果默认开启自动曝光,移动过程中图像亮度会不断变化,特征点的像素梯度也跟着变,严重影响光流跟踪的稳定性。我处理的方式是在驱动参数里固定曝光时间和增益,并确认室内光照恒定。

第三,录制环境要保证有足够纹理。纯白墙、大面积玻璃、空荡走廊都是VINS的坑。纹理不足时特征点数量掉到几十个,光流稍微一断,轨迹就开始飘。至少保证画面里有二十个以上稳定角点,最好能覆盖图像的四个象限。

录制命令可以参考:

rosbag record /camera/color/image_raw /imu/data_raw -O s_walk.bag

4.2 launch文件与topic名替换

VINS-Mono自带的示例launch文件默认订阅的是相机的仿真话题,直接跑肯定报找不到话题。需要新建一个launch文件,把图像话题和IMU话题替换成S系列实际发布的名称。我的做法是复制一份示例launch,然后只改配置路径和话题名,尽量不动原有结构。

launch文件里大致是这样:

<launch> <arg name="config_path" default="$(find vins_estimator)/../config/s_series/s_robust.yaml" /> <arg name="vins_driver" default="" /> <node name="vins_estimator" pkg="vins_estimator" type="vins_estimator" output="screen"> <param name="config_file" type="string" value="$(arg config_path)" /> <param name="output_path" type="string" value="$(find vins_estimator)/output_path" /> </node> </launch>

注意上面的launch文件里没有直接写图像话题名,VINS-Mono的图像和IMU订阅话题是在配置yaml里通过image_topic和imu_topic指定的。所以更关键的修改位置在yaml:

image_topic: "/camera/color/image_raw" imu_topic: "/imu/data_raw"

如果你在S系列驱动里看到的话题名带命名空间,或者图像是双目的两个话题,请把要用的左目话题名填进去。不要想当然地把两个话题都塞给VINS-Mono,它是单目视觉惯性算法,只接受一个图像输入。

4.3 启动VINS-Mono并保存轨迹

改完配置后,按这个顺序启动:

roslaunch vins_estimator s_series.launch roslaunch vins_rviz vins_rviz.launch rosbag play s_walk.bag

启动顺序上,先把VINS和Rviz起来,再播放bag。如果bag播放太快,VINS可能出现初始化没完成就开始运动的情况,可以用rosbag play --pause控制播放速度,等Rviz里出现了稳定的特征点轨迹和重力方向再恢复播放。

轨迹最终可以保存下来,VINS-Mono提供保存路径的服务:

rosservice call /vins_estimator/save_path "{}"

调用后会在output_path下生成vins_result_path.txt。这个文件保存的是每个时间戳对应的位置和姿态,可以直接用来和真值比较,也可以用EVO之类的工具画误差曲线。

一个经验是,保存轨迹时要注意VINS输出的话题时间戳和bag时间戳是否一致。如果二者有个固定的偏置,画图之前先把时间对齐,否则会认为轨迹严重漂移,其实是时间栅格错位。

4.4 怎么判断轨迹算不算正常

轨迹飘飞解决没解决,不能只看“没飞上天”就说成功。我用三个标准来验收。

第一个标准是静止起点。回放bag的前两秒,VINS刚完成初始化,轨迹应该基本停在一个点上,抖动幅度小于1厘米。如果静止时轨迹都在匀速移动,说明陀螺仪零偏或者加速度计重力度量有问题。

第二个标准是直线段。在桌面上缓慢推动设备走一条直线,轨迹应该是一条接近直线的平滑曲线,不能出现明显的波浪状摆动。波浪状摆动通常意味着图像特征点选择太分散、外参旋转不准,或者图像和IMU时间戳偏差仍然存在。

第三个标准是回到原点。走一个四边形回到起点,理想情况下轨迹终点和起点应重合;实测中漂移在总路程的1%到2%以内就算可以接受。如果超过这个量级,先不要改VINS内部参数,回去检查标定和时间戳。

我实际跑S系列时,把这三条标准全部跑通后,轨迹已经完全不会“上天”,缓慢行走和旋转都能保持稳定,放在室内的钢筋桌子上反复平移和转动,终点误差控制在0.8%左右。虽然不能和专业惯导比,但作为视觉惯性里程计已经进入可用状态。

5. 常见问题速查表

5.1 症状、原因和对策对照表

整理了一张我在实际运行中遇到的问题表,遇到同类现象可以直接按行排查。

现象最可能原因优先处理方式
轨迹往Z轴方向飞升外参旋转矩阵错误或重力对齐异常重新标定外参,检查body_T_cam0方向
原地打转,画圈陀螺仪与外参方向不一致,时差偏大检查时间戳偏差,修正外参旋转
水平方向缓慢漂移IMU噪声过大或相机帧率低调低acc_n/gyr_n,提升图像帧率
刚起步几秒后轨迹跳变静止初始化不充分,或自动曝光开启录bag前静止2秒,固定曝光参数
转弯后轨迹明显歪时间戳偏差大于10毫秒驱动层同步,开estimate_td
纹理稀少处轨迹乱飞特征点数量不足换纹理丰富场景,或降低特征点阈值
回放同一bag轨迹不一样时间戳回跳或多线程调度抖动核对驱动时间戳,保证数据源稳定

5.2 我的排查顺序

如果你不想一张表一张表看,我给出一个可以直接照做的排查链条,按这个顺序走,能省掉至少一半的时间。

第一步,看时间戳。把rostopic echo打出来,确认时长是否连续递增,偏差是否在几毫秒内。

第二步,看外参。用标定工具重新标定,或者至少用静止时的重力和视觉方向粗对齐,把外参旋转部分修正到1度以内。

第三步,查噪声参数和estimate_extrinsic。按上一节的经验值填入,在线估计外参一次并固化。

第四步,关自动曝光。录一段固定曝光、纹理正常的数据,重复播放观察轨迹。

第五步,严格静止初始化。每次回放bag的前几秒不要立刻播放,用--pause控制节奏。

这个顺序的核心逻辑是先确保传感器数据本身无歧义,再让VINS去优化。如果一上来就调VINS内部参数,很容易陷入“调好A又带歪B”的循环。我亲测过,先处理时间戳和外参之后,有一半的飘飞现象已经自然消失。

6. 个人心得与几个实用习惯

这套“解决版”最终落地时,我对S系列相机的感受是:硬件本身不差,但默认数据和VINS所需的输入之间还有一段“调试距离”。厂商给的标定参数只能作为起始参考,真正想在具体场景里跑出稳定轨迹,标定和时间同步这两个基本功绕不开。

我还养成一个习惯:每次修改配置前先在文件里加注释,记录本次改动和当时的轨迹截图编号。VINS参数联调很容易出现“各参数相互补偿”的假象,比如外参调错时把IMU噪声调大,视觉主导反而会让轨迹勉强不发散,但代价是细微抖动和回环闭合变差。如果没有记录,过两天再回看时根本不知道当时为什么这样改。

另一件值得做的事,是把最终验证通过的一套配置单独存成一个文件夹,连同launch文件、bag段和轨迹截图一起归档。以后换一台同样的设备,可以把这套配置作为起点,再针对新设备的标定差异做增量调整,而不是从零开始踩坑。

如果你后续想进一步压低漂移,还可以考虑给S系列加回环检测的触发条件,或者在场景中布设一些标志物辅助视觉特征。不过那已经超出“解决飘飞”的范围了。先把这篇里的数据链路、标定和时间同步处理好,至少九成以上的轨迹飘飞问题都能落地稳住。剩下的,就是算法参数精细调优的漫长了。

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

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

立即咨询