☰
VINS-Mono初始化深度解析:视觉惯性系统稳定运行的基石
2026/10/16 8:13:10 网站建设 项目流程

搞视觉惯性导航的朋友,十有八九都跟VINS-Mono打过交道。但很多人在跑通公开数据集之后,换上自己的设备或者自己录的数据,系统就开始不干人事了——要么轨迹一会儿飘到天上,要么跑着跑着直接发散,要么前端明明在跟踪但地图越建越歪。我早期调试的时候,经常把这些锅甩给后端优化,反复调权重、调核函数,折腾一两个星期没什么实质进展,最后回头老老实实通读一遍初始化流程,才发现问题几乎都出在这一步。

VINS-Mono初始化流程是整个系统的基石。它解决的不只是“系统动起来之前需要预热”这种工程问题,而是从数学上决定了后续状态估计器的尺度、重力方向、初始速度这些全局量能不能落在正确数值附近。这篇东西我打算掰开揉碎讲一遍:初始化要解哪些未知量,为什么非解不可,每个子模块在干什么,以及我实际调试中遇到过哪些坑、怎么定位。适合正在啃代码的人,也适合那种“系统能跑但总感觉哪里不对劲”的排查型选手。

1. 初始化在干什么:从“无尺度视觉轨迹”到“有物理单位的系统状态”

1.1 单目相机天生的“尺子缺失”问题

先想一个最简单的问题:单目相机拍两张照片,通过特征点匹配和几何关系,你能算出相机从位置A移动到位置B的方向,也能算出一个等效的平移向量,但这个向量乘上任意正数,两张图的重投影关系完全一样。换句话说,单目视觉给出的轨迹是一个只能确定到“相似变换等价类”的解,单位是像素还是米,它自己说不清楚。

VINS-Mono的核心是融合视觉和惯性测量单元。惯性测量单元里的加速度计测的是比力,陀螺仪测的是角速度,这些数据天生带着物理单位(米每秒平方、弧度每秒)。但有意思的是,加速度计数据只有在系统中存在加速度变化时才真正有用,而视觉给出的轨迹虽然没尺度,却有比较可靠的形状和方向。把两者拧到一起,让视觉给惯性提供方向,惯性给视觉补上尺度,这就是初始化阶段的核心逻辑。

类比一下:你拿到一张人物照片,构图匀称、姿势自然,但你看不出这个人的实际身高。旁边放了一个体重秤——它不能直接告诉你照片里人有多高,但如果照片里那个人正好在称重、或者你能连续几帧观察到脚底和地面的相对运动,那你就能推断出一个真实的比例尺。惯性和视觉的关系,差不多就是这么回事。

1.2 初始化要交付的“货物清单”

初始化不是一个单独的函数调用,它是一整套解算过程。它最终要给出以下几个量:

输出量含义为什么必须由初始化来定
重力向量在世界坐标系下的方向把加速度计读数从“机体坐标”搬到“世界坐标”时需要的基准后续所有加速度积分都依赖它
尺度因子视觉轨迹与真实物理轨迹的比例关系没有它,位置全是像素单位
窗口内各帧的初始速度惯性积分需要初始状态后端优化时是重要状态量
陀螺仪偏置的粗估计陀螺零偏导致旋转漂移后续紧耦合优化需要初始值
视觉坐标系到惯性坐标系的外参校准结果两个传感器的安装关系整个系统都在这个变换基础上运行

你可以把初始化理解成“给系统睁开眼睛的过程”。在睁开眼之前,视觉在动、惯性也在动,但它们各自各说各话;初始化做完之后,所有传感器数据被拉到同一个坐标系、同一个尺度、同一个物理世界里,紧耦合优化才有意义。

1.3 为什么必须用滑窗,而不是攒一堆数据离线搞

VINS-Mono是实时系统,不可能把几十秒的数据攒下来做最小二乘。初始化实际运行在一个滑动窗口里,常见配置是维持最近的约20帧关键帧。这个设计思路跟很多实时视觉里程计一致:既要保证局部的几何约束足够稠密,又要控制优化规模,避免计算延迟把前端拖垮。

滑动窗口带来的一个实际问题就是:如果窗口里的运动不充分,初始化解出来的量就不稳定。早年文档里经常强调“初始化前让设备缓慢平移”,根因就在这。这个问题后面会专门展开。

2. 纯视觉SfM:先用几何把位姿骨架钉住

2.1 为什么第一步反而是“纯视觉”而不是“惯性优化”

刚接触VINS-Mono的人容易有个误解:既然是视觉惯性融合,初始化就应该把两类数据同时丢进一个优化问题里。实际流程不是这样。初始化第一步是用纯视觉的方式,把窗口内每一帧的相机位姿和若干三维点恢复出来,后续惯性数据才有资格掺和进来。

原因很简单:视觉几何的旋转信息非常可靠。对极几何给出的本质矩阵、基础矩阵,其旋转部分不随尺度变化,计算稳定性好;而惯性数据在没有重力方向和速度初值前,没法直接用于位姿求解。先用视觉把“形状”搞出来,再用惯性把“物理刻度”加进去,这种拆分降低了求解难度,也让每一步的误差可解释。

2.2 从帧间匹配到本质矩阵

具体做法上,初始化会从窗口内选择合适的帧对,做特征匹配,然后估计本质矩阵。这位置有几个细节值得留意:

  • 特征点必须均匀分布,不能全堆在画面某一个角落。实际实现里一般有网格化特征提取策略,保证每个区域都有点。
  • 帧对选择上,系统会考察特征视差,视差太小说明两帧之间几乎没有几何约束,算出来的本质矩阵噪声极大。
  • 本质矩阵有5个自由度,一般用5点法或8点法求解,实际工程中经常带有RANSAC,把错误匹配压掉。

这一步出来的是一堆“相对旋转”和“相对平移方向”。它们只代表形状,不代表尺度,先别急着拿这些数据去做任何跟米、秒有关的事。

2.3 后续的位姿传播与三角化

有了基准帧对的本质矩阵,接下来就可以用类似PnP的方式逐渐恢复窗口内其余帧的位姿,然后三角化出三维点。VINS-Mono这部分其实沿用了视觉里程计的经典处理管线:先两两帧估计相对运动,再用增量式PnP往外扩,最后对窗口内的位姿和三维点做一次局部BA微调。

这里有个工程上容易忽略的点:三角化的深度恢复常常有浮点误差,尤其是基线很短时。这会导致三维点深度方向的噪声被放大。一个通用做法是在BA中加一个逆深度参数化,把深度的相对误差变小。VINS-Mono的滑动窗口优化里也类似,逆深度比绝对深度好收敛得多。

2.4 退化场景与单应矩阵的坑

纯视觉几何有个著名的退化问题:如果两帧之间只有纯旋转、或者场景近似平面(比如对着白墙、桌面平移),本质矩阵求解会退化,甚至得到错误解。初始化阶段如果没处理好,后面的惯性融合喂进去的视觉位姿骨架就是歪的,系统从起点就输在了跑线上。

因此这一阶段通常会有退化检测:如果本质矩阵解出来的平移量接近零,或者平面场景检测出单应矩阵更合理,系统不会强行继续,而是推迟初始化或者换帧对。这个检测逻辑非常关键,我后面会结合实际调试过程再说一段。

3. IMU预积分与陀螺仪偏置:给视觉轨迹补上旋转参考

3.1 预积分到底“预先”积了什么

先说明一下为什么不能直接对惯性测量单元的原始加速度和角速度逐帧积分。你要是在每一帧都从上一帧的绝对状态开始重新积分,那计算量会随窗口线性增长,而且重复积分放大了数值误差。预积分的思路是:把相邻两帧之间惯性测量单元数据产生的相对位移、相对速度、相对旋转提前算好,作为“测量值”缓存起来,跟全局绝对状态解耦。

打个比方:你坐火车从北京到上海,车窗外的风景你在每一站都重新测量一次位置,既浪费又难以保证一致性;而预积分相当于你只在出发和到达时各记一次总位移,中间怎么颠簸都折进一个整体观测里。

具体在初始化里的应用是,视觉SfM给出的是相对位姿,预积分给的是相对惯性运动,两者之间存在一串约束关系。初始化就是把这些约束关系组织起来求解。

3.2 陀螺仪偏置估计:靠旋转一致性反推

这一步的数学目标非常干净。对窗口内任意相邻两帧,视觉SfM给出了一个相对旋转;惯性预积分也给出了一个相对旋转,它依赖未知的陀螺仪偏置。真实世界只有一个旋转,所以这两条渠道算出的相对旋转应当一致。把误差写成关于偏置的函数,最小化所有帧对误差的平方和,就得到了偏置的粗估计。

这里有个让我当时琢磨了好一会儿的细节:为什么用旋转就能稳定解偏置,而平移暂时不能用?原因在于旋转观测对偏置非常敏感。陀螺仪偏置直接影响角速度积分结果,而角速度积分又是旋转的来源,一丁点零偏积累几秒就会在旋转上显出明显漂移。视觉旋转没有这种累积,所以两边一对比,偏置就被“逼”出来了。

3.3 为什么初始化阶段先放过加速度计偏置

跟陀螺仪偏置不同,加速度计偏置在初始化中通常被忽略,或者只在后续紧耦合优化中一起估计。原因不是不能估计,而是不好估计。

加速度计偏置和重力在当前窗口的短期运动中是高度混淆的。传感器静止时,加速度计读数是重力加偏置的合成;系统在做匀加速运动时,你很难区分到底是真实的线性加速度还是偏置引起的读数偏移。在初始化这么短的观测窗口里,如果把加速度计偏置一起设成未知数,大概率解出一堆互相纠缠的垃圾值。实际工程里,加速度偏置一般假设很小,属于小量,相比之下重力和运动加速度是大量,等后端优化用更长时间窗口再把偏置逐渐收敛出来。

提示:如果你在代码注释或者文档里看到“假设加速度计偏置在初始化阶段为零”,别觉得这是草率,这是权衡后的合理工程决策。

4. 重力与尺度联合求解:初始化的重头戏

4.1 从预积分“位移”方程里凑出一个线性系统

这是整条初始化流程里最核心、也最容易让初学者晕掉的一段。结论先行:VINS-Mono把尺度、速度、重力方向全部塞进一个线性最小二乘框架里,一次性解出来。

怎么构造这个方程组?我不打算把全部公式抄上来,但可以把逻辑链路讲透。预积分给出了相邻两帧之间的相对速度变化、相对位移,这些量由上一帧的速度、重力、加速度项组成。因为视觉SfM已经给出了位姿,把每个时刻的相机位姿代进去,剩下的未知量就是尺度因子、每一帧的速度、重力向量。对窗口内每一对相邻帧都列一个等式,未知量堆成一个向量,然后用最小二乘解算。

这个过程中有一个非常典型的技巧:把“尺度”和“重力”放在同一个方程里求解,是因为它们在视觉惯性观测中彼此耦合。如果不联合估计,先单独解尺度再单独解重力,误差会很大。这种耦合本质上来自单目视觉缺尺度、惯性又缺重力方向,两个缺陷正好互相补刀,联合估计才能把两个量一起锁在正确值附近。

4.2 重力细化:解出方向之后还要“修模”

线性最小二乘解出来的“重力向量”,只是一个三维向量,其模长通常不等于标准重力加速度值。而现实世界中重力加速度的数值是已知的,模长大概在9.8左右。所以要做一步所谓重力细化。

方法是把重力向量写成一个已知粗方向的标量乘积累,加上一个扰动,这个扰动被约束在垂直重力方向的小切平面上,只有两个自由度。然后在这个参数化下重新求解,再迭代。这一步会显著改善重力方向的精度,进而改善整个系统在垂直方向的稳定性。

我见过有人复现的时候跳过这一步,理由是“反正后面紧耦合优化会修正”。实测结果就是初始化完成后,轨迹在垂直分量上慢慢下沉,越跑越偏。重力细化是很便宜的一步,几十次迭代而已,在该做的地方做掉,后面能省一堆麻烦。

4.3 速度恢复:每个窗口时刻都补上初速度

把尺度和重力确定之后,窗口内每一帧的速度就是现成的了。因为预积分公式里,只要知道初始速度、重力、每一帧的策略加速度,就能直接算出来。速度初值直接影响惯性积分,如果初始速度解错,跳变点会在后端优化里表现为加速度突跳,有时会被误认为传感器故障。

这一步骤我自己的感觉是:实现上没什么高深的东西,但数必须要从前面那一个大方程组里带出来,而不是重新另起炉灶再解一遍。VINS-Mono代码里顺序是先把重力、尺度、以及带速度的联合向量解出来,再从这里直接切出各帧速度。理解了这个顺序,看代码就不会乱。

5. 坐标系切换与全局细化:从视觉坐标系搬回惯性坐标系

5.1 视觉坐标系和惯性坐标系之间的旋转

初始化进行到这里,视觉SfM给出的位姿还挂在纯视觉坐标系里,惯性测量单元预积分数据又在惯性坐标系里。两者之间有一个固定的旋转和平移。这个外参在系统配置里一般有初始值,初始化阶段要做的是在这个基础上修正,或者至少确定可靠的旋转对齐。

方法不复杂:视觉轨迹提供了一系列旋转,惯性预积分也提供了一系列旋转。在尺度无关的情况下,两者的旋转序列应该差一个固定旋转,这个固定旋转就是坐标系变换。所有相对旋转的加权平均对齐可以写成一个简单的最优化问题,在实际实现中甚至可以利用四元数平均来解。

5.2 尺度作用到整张视觉地图

尺度因子解出来之后,需要把视觉坐标系里的所有相机位置、三维点坐标全部乘以尺度因子,转换到真实的物理单位下。这一操作要同步作用到位姿和地图点,不然视觉和惯性数据会“错位”。

这一步在工程上有一个容易漏掉的细节:相机到惯性测量单元的平移向量也参与了尺度变换。很多人只记得给位姿乘尺度,却忘了外参平移量也跟着换单位。如果这里没处理好,系统前几十帧的重投影误差会莫名变大,甚至直接触发异常检测。

5.3 初始化成功与否,怎么判

VINS-Mono的初始化不是“跑一遍就一定成功”。它是带校验的:如果某一阶段不收敛,系统会放弃当前窗口、清空滑窗重新积累数据。所以看日志时,如果发现窗口反复被清空,别急着怀疑代码有bug,大概率是运动激励不够或者数据质量太差。

一些常见校验手段包括:重投影误差是否小于阈值、尺度是否在一个合理范围、重力解算后的方向是否跟预期接近。还有一点,系统会统计窗口内的视差大小,视差太小直接拒绝进入初始化流程。这个机制导致了一个很常见的现象:设备启动后先静止或者只做小幅晃动,系统一直初始化失败,反复“等数据”,直到给了明显的平移运动才被唤醒。

提示:调试自己的机器时,先做一段5到10秒的缓慢平移加一点旋转,再观察日志确认初始化成功。很多“系统不工作”其实只是你没给它足够的初始化运动。

6. 踩坑记录:我复现初始化流程时遇到的典型问题

6.1 静止或纯旋转开局:初始化永远不成功

这是新手最容易撞上的第一堵墙。我早期用某开发板测试时,把设备放在桌上静止了十几秒才开始动,往日志里一看,初始化模块一直报告视差不足、本质矩阵退化,窗口被反复丢弃。

根本原因就是纯几何的固有限制:没有平移就没有视差,也就没有可靠的深度信息。纯旋转时对极几何是退化的,连“方向”都解不准,后面的惯性融合更是无源之水。解决办法谈不上什么高深技巧,就是在工程上保证设备启动后有一个明确的平移分量,不要指望系统能凭空恢复深度。

6.2 特征点太少或被动态物体带偏

另一个常见坑是初始化窗口内的特征点都被动态物体占了。比如对着人来人往的大厅初始化,墙面、行人的特征点匹配一旦存在大量动态点,本质矩阵的RANSAC就可能被带偏,最后视觉骨架歪掉,初始化出来尺度明显不对。

定位方法也简单:把初始化的视差信息、参与解算的帧对和匹配内点数量打印出来,很容易看出某一段帧的特征点分布极端不平衡。解决思路是加特征点均匀化约束,以及在视差统计阶段对特征点深度分布做一次过滤,优先保留能够稳定观测到的静态点。

6.3 初始化“成功”了,但一进优化就发散

更让人头疼的是:日志显示初始化通过了,姿态和位置也开始输出了,结果跑个十几秒轨迹就朝着一个方向飘。我复盘过几次,几乎都指向同一个来源:陀螺仪偏置初始估计不准,或者视觉SfM阶段出现帧对漂移。

陀螺仪偏置不准时,初始化阶段给不出可靠的旋转,后续紧耦合优化里大量旋转残差会积累成系统性误差。这种问题往往在短时间测试里看着没问题,时间一长就暴露。我的经验是,碰到这种情况先看旋转残差序列是否存在明显的单调趋势,如果有,回头检查初始化阶段陀螺仪偏置求解的收敛条件和数据量是否充足。

6.4 不同场景下的调参思路

最后给点实际操作方面的思路。初始化相关参数主要集中在滑窗长度、帧间视差阈值、特征点数量、收敛判定阈值这几类。

参数室内小场景倾向室外大场景倾向
滑窗长度可以稍短,视差更容易积累稍长,避免单帧噪声过大
视差阈值加大,防止微小视差带偏适中,保证远点参与约束
特征点数量增加并强调均匀分布保持数量,但过滤低视差特征
收敛阈值收紧,几何精度要求高放宽,防止过于频繁失败

不同场景的特性确实不一样。室内近处特征点多、视差大,但逆深度噪声也大;室外的远点可以稳住旋转,但对平移估计贡献小。初始化阶段如果能根据场景特点调整参数,成功率会有直观提升。

VINS-Mono的初始化流程,拆开看其实是五个相对独立的步骤:纯视觉SfM打底、惯性预积分做桥、陀螺仪偏置对齐旋转、重力和尺度联合求解、坐标系切换收尾。每一段单独拿出来都不是特别复杂,但错误会跨模块累积,这也是为什么很多人直接从后端优化坑起永远查不出根因。我自己调试的习惯是先确认初始化是否稳定通过,再看前端质量,最后才碰后端权重。顺序反了,多半白忙一场。

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

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

立即咨询