ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

无人机VIO部署避坑:SchurVins的yaml配置与实机调参实战

无人机VIO部署避坑:SchurVins的yaml配置与实机调参实战

简介:这是一份面向无人机视觉惯性导航开发者的 SchurVins 配置资源,解决在无人机上部署 VINS 算法时传感器参数、时间同步与飞行模式参数难以确定的问题,适合已有 ROS/VIO 基础、正在调试定位系统的研发人员。压缩包仅 2 个文件,均为 yaml 格式,整体大小约 4KB;两份配置分别面向 Euroc 公开数据集与 VIOStereo 立体相机+IMU 场景,既可用于仿真复现,也可用于机载部署。已有 236 人学习下载。配置文件中涵盖摄像头分辨率/帧率、IMU 噪声、相机-IMU 外参、特征提取数量及不同飞行模式下的调节项,有利于快速替换原有配置并理解 SchurVins 各项参数含义;尤其在室内悬停、低速巡航或高速飞行等场景下,还能参照其中的参数调整策略建立基线,结合实飞数据进一步迭代,减少从零编写 yaml 的试错成本。

1. 在无人机上部署 SchurVins,先卡住的从来不是编译而是 yaml

做无人机视觉惯性里程计的人,迟早都会遇到 SchurVins 这个项目。它是 VINS 系里把 Schur 消元做进后端优化的一个变体,在机载算力有限的平台上比原始版本更省,很多同学把无人机上的视觉惯性导航方案从 VINS-Mono 迁到 SchurVins,就是为了把 CPU 占用压下来。但实机部署和仿真跑通完全是两码事:台式机上改了 launch 文件能跑通,不代表换到机载电脑上还能稳;等真正把 yaml 配置文件换到无人机上,镜头内参、IMU 噪声、外参旋转矩阵三块参数只要有一处对不上,起飞十几秒就是发散或者漂移,炸机就在眼前。

我这个环境是在某团队的模拟项目X里搭的,前后被 yaml 折磨了将近一周。回头复盘,真正挡住人的不是源码编译,而是不知道 SchurVins 的 yaml 每个字段对应实机上哪一份标定数据,以及哪些参数是仿真默认值、哪些必须替换成真实值。这篇就把整个部署过程按我实际操作的顺序拆开:装什么版本、怎么编译、yaml 怎么改成实机可用,最后怎么验证它真的跑对了。适合手里有一台 Ubuntu 机载电脑、准备把 SchurVins 上无人机的新手,也适合在纯仿真里跑通但一上实机就出幺蛾子的熟手对照排查。

2. 编译环境与依赖:ROS、Ceres 和 Eigen 的版本搭配

2.1 先定版本:Ubuntu 18.04 配 Melodic 是最稳的组合

SchurVins 源码本身不挑编译器,但它依赖的 ROS 包、Ceres Solver 和 OpenCV 之间有版本联动。常见做法是先看自己手里的机载电脑装的是哪个 Ubuntu,再反推 ROS 版本。我这边排查过两次环境问题,一次卡在 Ceres 与 Eigen 的 API 冲突,一次卡在 OpenCV 版本导致特征提取节点崩掉,都是版本配套的锅。

如果是新刷系统的机载电脑,我一般直接装 Ubuntu 18.04 + ROS Melodic,这套组合踩坑资料最多,网上能搜到的报错基本都覆盖得到。Ubuntu 20.04 + Noetic 也能编译 SchurVins,但需要手动处理 Python 2/3 的依赖差异,多折腾半小时。无人机部署要的是可复现,不是追新,所以别在这个环节给自己加难度。

sudo apt update && sudo apt install -y ros-melodic-desktop-full sudo apt install -y ros-melodic-cv-bridge ros-melodic-image-transport ros-melodic-camera-calibration

第一行把 ROS 主件装全,desktop-full 比 base 多了 rviz、tf 等可视化工具,后续验证轨迹离开 rviz 就瞎了。第二行是 SchurVins 运行时需要的图像传输和相机标定相关包,其中 cv-bridge 版本必须和 OpenCV 编译版本一致,这是最常见的编译期报错来源。

ROS 装完后别忘了初始化 rosdep 依赖,很多直接 clone 源码后编译报“找不到某个 ros 包”的问题,都是 rosdep 没跑全。我用的是:

sudo rosdep init rosdep update

rosdep 的职责是把源码里 package.xml 声明的依赖自动拉齐,跳过这一步,后续 catkin_make 会随机报缺包。机载电脑如果在离线环境,rosdep 会失败,那就只能手动逐个 apt 安装缺失的 ros-xxx 包,比较痛苦,所以尽量在联网状态下先把环境装齐。

2.2 Ceres Solver 别用太新的版本

SchurVins 后端优化依赖 Ceres Solver,但它的代码是照着 Ceres 1.14 左右的 API 写的。新版 Ceres 把一些接口改了,比如Solver::Options里的参数类型调整,直接用最新版 2.x 编译会报一堆函数签名不匹配。

sudo apt install -y libgoogle-glog-dev libgflags-dev libatlas-base-dev libsuitesparse-dev cd ~/thirdparty && wget http://ceres-solver.org/ceres-solver-1.14.0.tar.gz tar zxf ceres-solver-1.14.0.tar.gz && mkdir ceres-solver-1.14.0/build && cd ceres-solver-1.14.0/build cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local && make -j4 && sudo make install

这里用 1.14.0 而不是最新版,就是避开 API 断代问题。编译参数里-j4针对机载电脑常见的四核处理器,如果 NUC 类平台有散热压力,可以降到-j2,Ceres 编译时间会长一点,但稳定性优先。装完后把 Ceres 的头文件路径记一下,后面 SchurVins 的 CMakeLists 会用到。

Eigen 建议直接用系统包,不要自己源码安装。SchurVins 里用到了 Eigen 的Sophus和Ceres内部类型,系统自带的 Eigen 3.3.7 和 Ceres 1.14 是验证过没冲突的组合。如果你之前装过其他版本的 Eigen 到/usr/local/include/eigen3,编译一旦出现 “Eigen/Eigenvalues” 找不到或者std::vector相关报错,先检查是不是多版本 Eigen 串了。

2.3 编译 SchurVins:catkin 工作区的标准流程

把 SchurVins 源码放到 catkin 工作区里编译,命令不长,但有几个细节值得注意。先建工作区再放包,不要直接在~/catkin_ws/src里散着放。

mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone <SchurVins仓库地址> SchurVins cd ~/catkin_ws && catkin_make

catkin_make默认只编译 src 下的所有包,如果你的 src 里还有别的包,第一次编译可能因为包间依赖顺序出问题。稳妥做法是单独编译 SchurVins 这一个包:

catkin_make -DCATKIN_WHITELIST_PACKAGES="SchurVins"

这个参数会告诉 catkin 只编译白名单里的包,避免其他包干扰。编译完成后 source 一下:

source ~/catkin_ws/devel/setup.bash

后半句补充:每次新开终端都要执行,或者写进~/.bashrc末尾,否则 roslaunch 找不到节点的可执行文件。编译成功后,devel/lib目录下会出现vins_estimator之类的可执行文件,这一步是后面所有 yaml 配置生效的前提。

3. 读懂 yaml 配置:相机内参、IMU 噪声和外参三块核心

3.1 camera 配置段:内参矩阵和畸变系数不是抄数据集默认值

SchurVins 的 yaml 配置文件里第一个大段是相机参数。这一段负责告诉前端特征提取器“镜头看到的世界是什么样的”。内参矩阵fx fy cx cy决定像素坐标和归一化平面坐标的换算,畸变系数决定图像边缘像素的校正方式。无人机上的相机通常和仿真数据集里的相机不是同一颗,直接沿用默认值等于告诉算法用一个错误的镜头模型。

我在模拟项目X的实机测试里,第一次就是把数据集自带的参数原封不动留在 yaml 里,结果开机后图像正常但特征点云迅速稀疏。原因很简单:畸变系数不对,矫正后的图像边缘扭曲,角点检测全都偏离。

camera: name: "cam0" fisheye: false distortion_model: "radtan" distortion_coeffs: [ -0.0032, 0.0018, -0.0004, 0.0001 ] intrinsics: [ 460.2, 459.8, 365.1, 242.7 ] resolution: [ 640, 480 ] timeshift_cam_imu: 0.02

fisheye字段要特别注意,无人机上用的广角相机很多是鱼眼镜头,但 SchurVins 的相机模型默认是针孔,如果你把fisheye留成 false 却装的是鱼眼相机,畸变校正环节就会彻底错乱。distortion_coeffs的四个值依次是 k1、k2、p1、p2,对应标定工具输出里的顺序,不要按 k1、k2、k3、k4 的鱼眼顺序填。

intrinsics四个数顺序是 fx、fy、cx、cy,很多标定工具输出顺序是 fx、fy、cx、cy 没错,但有的工具会先给 cx、cy 再给 fx、fy,填反了画面中心会偏。resolution必须和相机驱动实际发布的图像尺寸一致,如果相机输出 640x480 但 yaml 里写 1280x720,特征提取部分虽然不报错,但内参对应的像素坐标全部错位。timeshift_cam_imu是相机和 IMU 之间的时间偏移,标定后通常会在 0.005~0.05 之间,后面会细说。

3.2 imu 配置段:噪声参数的数量级决定能不能初始化

IMU 配置段是 yaml 里最容易让人忽略的部分,因为它看起来只是几个数字。但 SchurVins 的初始化极其依赖加速度计和陀螺仪的噪声模型,写错数量级,VIO 初始化永远失败,或者初始化成功后几十秒内漂移。IMU 噪声参数通常在标定报告里给出的单位是连续时间域,比如陀螺仪噪声密度,加速度计噪声密度,这个单位是m/s^2 / sqrt(Hz),不是传感器手册里的m/s^2。

imu: frequency: 200 gyroscope_noise_density: 0.0002 gyroscope_random_walk: 0.00001 accelerometer_noise_density: 0.002 accelerometer_random_walk: 0.0005 gravity: 9.810

frequency要和机载电脑实际收到的 IMU 消息频率一致。很多飞控内部 IMU 是 1kHz 输出,但机载电脑订阅之前被降频到 200Hz,如果 yaml 却写了 400,时间戳积分会快一倍,轨迹直接飞出去。gyroscope_noise_density一般范围在 0.0001~0.003 之间,单位是rad/s / sqrt(Hz),如果标定报告给的是角度随机游走(单位是rad/sqrt(s)),需要先换算再填。我遇到过有人把两个参数填反了,陀螺仪随机游走填成 0.0002、噪声密度填成 0.00001,结果初始化非常勉强,起飞后偏航角持续漂移。

gravity默认 9.810,这是赤道附近的重力值。如果测试场地在中纬度,9.795 更准确,但这个差异对 VIO 初始化影响很小,不必过度纠结。真正要命的是坐标系里重力的方向定义,SchurVins 默认 z 轴向下,如果你的 IMU 坐标系 z 轴向上,初始化会把重力方向搞反,表现为起飞瞬间位置跳变。

3.3 外参 body_T_cam0:从标定结果到 yaml 的坐标系转换

外参是相机和 IMU 之间的相对位姿,SchurVins 用的是body_T_cam0这个字段,命名里的 body 指 IMU 机体坐标系。这一段如果配错,相机和 IMU 的观测就无法在空间上对齐,表现最典型的是旋转时轨迹画圆弧而不是直线。

body_T_cam0: rows: 4 cols: 4 data: [ 0.999, 0.008, -0.011, 0.025, -0.008, 0.999, -0.003, -0.015, 0.011, 0.003, 0.999, -0.005, 0.0, 0.0, 0.0, 1.0 ]

这是 4x4 齐次变换矩阵,前三行前三列是旋转部分,第四列前三个数是平移部分,单位是米。这里最容易踩的坑是两个:第一,标定工具输出的外参矩阵有可能是相机到 IMU 的变换关系T_cam_imu,而 SchurVins 要的是T_imu_cam,需要对矩阵求逆再填进去;第二,有些标定工具把坐标系定义成 z 向前,但 SchurVins 的 body 坐标系是 x 向前、z 向上,旋转部分差一个 90 度的轴置换,轨迹会整体翻转。

我在实机调试时习惯先打印外参矩阵验证逻辑:把相机放在 IMU 右侧约 5 厘米处,那么平移部分的 x 值应该在正负 0.05 左右,符号取决于坐标系 x 轴方向。如果标定出来的平移值明显大于实际装机距离,多半是坐标系定义对不上。

4. 从仿真默认值到实机可用:替换参数的操作流程

4.1 先拿到实机标定的三份数据

配置 yaml 之前,先要准备好三份数据:相机内参、IMU 噪声、相机到 IMU 的外参。没有这三份,后面所有修改都是盲改。相机内参和畸变系数用棋盘格标定板跑一轮即可,常见工具是 kalibr,它会同时输出相机内参和外参。IMU 噪声参数需要静态采集一段至少两小时的 IMU 数据做艾伦方差分析,很多飞控厂商会给出芯片级噪声参数,但板级安装后供电噪声会变大,所以尽量用实测值。

这三份数据的获取顺序有讲究。先标定相机,再静态采 IMU 数据,最后用 kalibr 联合标定外参。不能反过来,因为外参联合标定需要一个相对精确的相机内参作为输入。我见过有人用相机出厂手册里的内参直接联合标定,结果外参的平移值和实际装机差 3 厘米,VIO 精度大打折扣。

4.2 逐步替换法:一次只改一个参数块,改完就跑数据包验证

拿到标定数据后不要一次性全部写进 yaml,除非你有把握每项都对。更稳的替换策略是:先改相机内参和畸变,保留默认 IMU 和外参,跑一遍公开数据集或自采的 rosbag。此时如果初始化成功但在飞行中漂移,说明相机部分基本正确。再把 IMU 噪声替换掉跑同一份 rosbag,如果初始化变快且轨迹更平滑,说明这组参数合理。最后替换外参,重点观察旋转时的轨迹一致性。

roslaunch schurvins schurvins.launch rosbag play ~/bag_files/test_data.bag -r 1.0

运行时核心节点是vins_estimator,launch 文件里会指定读取哪个 yaml 参数文件。-r 1.0保持 rosbag 原速回放,不要用-r 2.0加速回放,因为加速后的 IMU 时间戳会被压缩,VIO 初始化会因为帧间运动过大而失败,这会和 yaml 参数本身的问题混在一起,没法判断是哪边出了错。跑完成后用rostopic echo /vins_estimator/odometry查看输出频率,如果低于 10Hz 说明后端优化压力大,优先检查是不是外参旋转部分装反了导致残差过大。

4.3 常用参数对照:三种典型无人机平台的参考值

这里给出我在实际配置中见过的三组典型参数区间,不是标准答案,仅作为判断 yaml 数值是否离谱的参考。第一类是小尺寸穿越机 + 低成本 CMOS 相机,intrinsics 的 fx fy 通常落在 250~500 之间,IMU 噪声密度在 0.0002 量级;第二类是中型无人机 + 全局快门相机,fx fy 通常在 450~700,噪声密度 0.0001~0.0003;第三类是带云台的大轴距平台,相机和 IMU 距离较远,外参平移部分可能达到 0.1~0.3 米。

如果填完 yaml 后轨迹在静止时缓慢漂移,首先怀疑 IMU 噪声数值比真实值小了十倍以上。噪声填得太小会让滤波器过度信任 IMU,静止时积分漂移全被当成真实运动。反过来,噪声填得偏大,初始化阶段会一直无法收敛。这个尺度感只能靠多试几次建立,没有捷径。

5. 避坑记录:SchurVins yaml 配置的五个翻车现场

5.1 现象:起飞就发散,轨迹直接冲天

第一次上实机,解锁起飞后 rviz 里的轨迹不到五秒就朝一个方向飞出去,遥控器切回手动后才能稳住。看/vins_estimator/odometry输出的位置数据,垂直方向每帧增长半米多,明显不正常。原因是 IMU 加速度计的噪声密度我填成了标定文件里accelerometer_random_walk的值,两个数字差了十的倍数。随机游走是低频偏置变化,噪声密度是高频噪声,把低频参数填到高频位置,滤波器对加速度计高频噪声完全没概念,初始化阶段就错估了重力加速度。解决方法是翻出艾伦方差分析结果逐项对照单位,把accelerometer_noise_density填成m/s^2 / sqrt(Hz)那一列。从那以后我每次拿到标定报告,先把参数的单位写在 yaml 注释里。

5.2 现象:画面正常但特征点稀少,前端总是丢帧

图像话题在 rviz 里看起来没有异常,但feature_tracker节点发布的特征点只有十来个,勉强够初始化。排除了光照和纹理因素后,问题出在resolution写错。相机驱动实际发布的是 1280x720,yaml 里却留了数据集默认的 752x480,特征提取的 ROI 区域错位,图像边缘的特征被丢弃。解决方法是把resolution改成和sensor_msgs/Image的宽高一致,同时确认intrinsics里的 cx、cy 是按同一分辨率下标定的。如果图像尺寸变了但内参没重标,画面中心会偏移,特征点集中在一侧。遇到这个问题先跑一下rostopic echo /camera/image_raw/width看实际值,别猜。

5.3 现象:直线飞行变弧线,旋转时轨迹失真

机架悬停时位置稳定,但以恒定速度向前飞,轨迹却向一侧弯。这是外参旋转部分配错的典型表现。用 kalibr 联合标定输出的外参矩阵直接粘贴进 yaml,没有检查坐标系定义,结果相机和 IMU 之间的旋转关系差了一个轴的负方向。SchurVins 对 body 坐标系的定义是 x 前、y 左、z 上,而 kalibr 默认的 IMU 坐标系可能是 z 上但 x 右,中间差一个 90 度偏航旋转。解决方法是检查外参矩阵的列向量,确保旋转矩阵的第一列对应 body 坐标系的 x 轴方向投影。没有把握时,把标定结果先在 MATLAB 里做一次坐标轴转换再填入。

5.4 现象:launch 启动后立刻提示参数文件加载失败

报错内容类似Failed to open parameter file,但排查后发现路径是存在的。原因是 launch 文件里用了相对路径,而 roslaunch 是以当前工作目录为基准解析的。从机载电脑的另一个目录启动 launch,就找不到 yaml。解决方法是把 launch 文件里的arg改成绝对路径,或者用$(find schurvins)宏定位包路径。

<arg name="config_path" default="$(find schurvins)/config/real_drone.yaml" />

这种写法自动定位到包目录,无论从哪里启动都不会迷路。我把这个配置从../config/xxx.yaml改成上面的形式后,再也没出现路径问题。另一个隐蔽坑是 yaml 文件使用了 tab 缩进,ROS 的参数解析器对 tab 会报格式错误,但错误信息往往在 yaml 解析之前就中断了。

5.5 现象:IMU 和相机的时戳不同步,初始化反复失败

yaml 里的timeshift_cam_imu初始给了 0.02 秒,但飞控发布 IMU 消息的时间戳和相机驱动的时间戳是各自独立时钟,相差接近 60 毫秒。VIO 初始化时需要在时间上对齐相机帧和 IMU 积分,时差不稳定就会表现为“有时候能初始化,有时候卡住”。解决方法是先把两个话题的时间戳对齐。常见做法有两种:第一,在相机驱动里改用与 IMU 同一个时钟源,比如用sensor_msgs::TimeReference把时间源切到飞控的 PPS 对齐结果上;第二,用工具估算固定时延,把timeshift_cam_imu调到实测值。我用的是后者,先rostopic echo分别抓几帧时间戳,算出延迟均值,再更新到 yaml。如果是新装的机载电脑,一定优先检查系统时间是否同步过 NTP,我见过机载电脑重启后系统时间偏差好几秒的,那已经不是 yaml 能解决的问题了。

6. 装完怎么验证跑通:回放数据包对比真实轨迹

配置完成后,第一步验证方法是用自己采集的 rosbag 做离线回放。采集 rosbag 时,让无人机在原地做几个方向的旋转,再在平地上走一个矩形路径,每个动作重复两遍。然后把 rosbag 按 0.8 倍速回放,打开 rviz 看轨迹。重点观察两个指标:一是静止悬停时轨迹的抖动幅度,零漂移状态下位置输出应该在厘米级波动;二是矩形路径闭合后终点和起点的偏差,如果偏差超过路径总长度的 5%,说明配置精度不足,需要回到第 4 章逐步替换法检查。

第二轮验证是实时飞行前的最后一道关卡。把机载电脑装在无人机上,先不装桨,手持无人机做小幅移动和旋转,观察 rviz 里的轨迹是否跟随手部运动的方向和幅度。这个环节能暴露大部分外参和时延问题。具体操作是:手持无人机朝向正前方移动 30 厘米,rviz 里的轨迹应当同样向前移动约 30 厘米;若轨迹出现滞后或反向,优先检查timeshift_cam_imu和外参平移项的符号。

最后一关是验证 VIO 与真实状态量的一致性。跑一个短距离的缓慢矩形飞行,同时记录飞控的姿态估计,对比偏航角输出。SchurVins 的偏航角如果和外参配置无关但和 IMU 校准有关,所以偏航角误差大时回到 IMU 噪声参数检查。我一般习惯把整个验证过程写成命令行清单,每跑完一项就打个勾。从那以后我每次部署新无人机,都强制自己走完整套流程,哪怕只是换了一颗镜头也要重跑一遍验证,因为相机内参一变,外参基本也得跟着重标。希望这套流程能帮你在 yaml 配置这一步少折腾几天。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进