ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

VINS-Mono代码注释:相机标定与四种相机模型实战解析

VINS-Mono代码注释:相机标定与四种相机模型实战解析 简介面向视觉惯性导航研究与机器人自主定位开发者的VINS-Mono代码注释包覆盖单目相机与惯性测量单元融合定位、建图及运动估计的核心实现适合具备机器人系统基础的研究生、算法工程师及SLAM入门者研读。压缩包采用7z格式共164个文件主要包含C头文件与源文件、相机标定相关代码、启动与参数配置、可视化配置及PDF说明文档整体约41.28MB。目前已有647人浏览学习。注释在关键模块附近补充了中文讲解包括预处理、特征检测与匹配、惯性数据预积分、滑动窗口优化、回环检测以及关键帧与内存管理并针对多种相机模型和标定实现做了详细梳理。借助这份注释版源码读者可以快速建立从传感器数据到位姿输出的完整认知缩短阅读原始工程的时间也便于后续在无人机、机器人等自主导航场景中迁移应用。1. VINS-Mono 代码注释包先把视觉前端和标定链路理顺拿到这份 VINS-Mono 代码注释包我第一反应不是去翻 estimator 里的后端优化而是先把 camera 目录完整过了一遍。因为在无人机、机器人和 AR 场景里真正卡住开发的往往不是滑动窗口优化本身而是第一步就错了镜头模型选错、标定参数不对后面所有视觉惯性融合都在错误数据上打转越往后越难排查。这个包里包含了 Chessboard.cc 棋盘格角点检测、CameraCalibration.cc 标定主流程、intrinsic_calib.cc 入口以及 PinholeCamera、EquidistantCamera、CataCamera、ScaramuzzaCamera 四种相机模型实现。适合正在复现 VINS 系列算法、或者被鱼眼镜头标定折腾过的开发者把这套链路读透能省下大量调参和排错时间。2. 包内文件与四种相机模型先看懂投影边界再谈标定2.1 从文件清单反推模块边界很多初学者拿到压缩包就急着编译结果在茫茫代码里迷路。我的习惯是先列文件清单反推这套代码的职责边界。解压之后你会发现这套注释包并不是 VINS-Mono 全仓库而是把视觉前端和相机标定相关的核心文件单独抽了出来。这个边界很重要它意味着你不需要去读后端优化的大半个 estimator先把标定搞明白后面看任何 VINS 变种都会轻松很多。文件职责对应模块intrinsic_calib.cc标定程序入口main 函数、命令行参数解析CameraCalibration.cc标定主流程采集角点、初始化内参、迭代优化Chessboard.cc棋盘格角点检测亚像素提取、角点排序PinholeCamera.cc针孔相机模型投影与反投影EquidistantCamera.cc等距畸变模型鱼眼镜头标定CataCamera.cc折反射全向相机模型全景/超广角镜头ScaramuzzaCamera.cc多项式相机模型强畸变镜头标定CostFunctionFactory.cc代价函数工厂Ceres 求解器的残差构建gpl.cc通用数学工具多项式求根、SVD 等底层运算从表格可以看出来这套注释包围绕的是「标定入口 相机模型 优化残差」三条线。gpl.cc 是 OpenGV 里带过来的通用数学工具主要处理多项式求根这类脏活读的时候扫一遍就行不用逐行抠。真正要细读的是四个相机模型类和 CameraCalibration.cc因为它们决定了后面 VINS 系统里每个视觉残差是怎么计算出来的。2.2 四种相机模型投影关系决定了标定结果的上限相机模型的本质是把一个三维空间点映射到二维像素坐标反投影时再把像素坐标映射回归一化平面或者单位球面。VINS-Mono 后端做视觉惯性优化时每一步迭代都要调用模型的前向投影来计算重投影误差所以模型选对了残差才线性化得好优化才容易收敛。这四个模型覆盖了从普通镜头到鱼眼、全景的常见硬件场景。PinholeCamera 就是我们最熟悉的针孔模型内参是 fx、fy、cx、cy畸变用 k1、k2、p1、p2 这类多项式表达。它适合视场角在 100 度以内的普通镜头也是默认首选。EquidistantCamera 走的是等距投影关系式是 r f * theta即像素半径和入射角成正比适合视场角 120 度到 180 度的鱼眼镜头。CataCamera 对应折反射全向相机用一个统一球面模型来描述适合全景视觉系统。ScaramuzzaCamera 则是用多项式去逼近复杂畸变对畸变特别剧烈的镜头更稳标定板出现在画面边缘时也不容易发散。选型时我有一个很土但有效的办法先看镜头标称 FOV再准备一组同样的标定图分别用 PINHOLE 和 EQUIDISTANT 跑一遍比较重投影误差 RMS。如果 PINHOLE 的 RMS 超过 1 个像素而 EQUIDISTANT 明显更低那就别犹豫直接用鱼眼模型。这种做法听起来像玄学其实背后是畸变模型表达能力的问题模型表达不了大 FOV优化器硬拟合只会把内参带偏。2.3 注释代码怎么读从 intrinsic_calib.cc 反推完整流程读这套代码的正确顺序不是按文件名排列而是按数据流走。从 intrinsic_calib.cc 的 main 进入它会读取配置文件实例化一个 CameraCalibration 对象然后调用 run 方法。run 方法里做的事情可以拆成四步第一步遍历输入的标定图像用 Chessboard.cc 里的检测逻辑找棋盘格角点第二步把角点坐标和棋盘格的世界坐标对应起来第三步根据默认相机模型初始化内参第四步调用 CostFunctionFactory 创建重投影误差的代价函数交给 Ceres 迭代优化。所以你看CostFunctionFactory.cc 并不是一个独立功能它是把「重投影误差」包装成 Ceres 能吃的残差块。注释里通常会标明每个残差块对应哪个相机模型这就给了你一个很好的阅读抓手先看模型类里的 lift 和 project 函数再看工厂里怎么把误差组合起来。我一般会先快速扫一遍注释里的 TODO 和注意字样这些位置往往藏着作者踩过的坑比如某类镜头在边缘会失效、某个参数对初始化特别敏感。把这些标记记下来比从头到尾读代码效率高得多。3. 把标定工程跑起来从 7z 解压到输出内参的完整链路3.1 Linux 下解压 7z 与工程目录准备这个包是 .7z 格式很多人习惯解压到一半发现命令报错然后开始怀疑压缩包损坏其实只是没装 p7zip 工具。Linux 上最常用的做法是先装 p7zip-full再用 7z 命令解压注意 -o 参数指定输出目录时路径和参数之间不要加空格这是新手最容易翻车的地方。# Debian/Ubuntu 安装解压工具 sudo apt-get install p7zip-full # 解压并保留原目录结构-o 后直接跟路径不能有空格 7z x VINS-Mono代码注释.7z -o~/workspace/vins_mono # 查看解压结果 ls ~/workspace/vins_mono如果你是在 Windows 上做代码阅读同样先把 7z 解压成普通目录再拖进 IDE。网上常搜的「pycharm添加7z」本质上是两件事要么用 7-Zip 先解压要么装一个能读压缩包的插件。但看这套 C 源码我更推荐解压后用 CLion 或者 VS Code 加 C/C 插件打开PyCharm 对 C 工程的支持很有限硬拖进去只会看到一个没法跳转的文本列表。3.2 棋盘格数据采集与配置文件改法跑标定之前数据质量决定了一大半结果。棋盘格我建议用不对称设计比如 11 列乘 8 行格子边长 20 到 30 毫米打印出来贴在硬纸板或者铝板上表面不要反光。采集的时候让棋盘格在画面里占到三分之一以上覆盖中心、四个角落和不同距离一共拍 10 到 20 张就够了。别用那种小到只剩一小块的标定板照片角点检测器会直接罢工。配置文件里最需要改的是 model 字段和内参初始值。初始值不用特别准它只影响收敛速度不影响最终结果但模型字段选错了就完全是另一回事。下面是一个典型的 cam0.yaml 示例model: EQUIDISTANT intrinsic: [500.0, 500.0, 320.0, 240.0] distortion: [0.0, 0.0, 0.0, 0.0] chessboard: cols: 11 rows: 8 square_size: 0.03model 字段对应代码里的相机模型枚举鱼眼镜头就写 EQUIDISTANT普通镜头写 PINHOLE。intrinsic 依次是 fx、fy、cx、cy 的初始猜测cx 和 cy 可以先填图像分辨率的一半。distortion 初始全零即可。chessboard 里的 cols 和 rows 必须和标定板实际角点数一致少一个多一个都会导致检测出的棋盘格形状错乱。square_size 的单位是米标定板格子边长 3 厘米就写 0.03这个值直接关系到尺度写错的话标定结果看着正常但后续视觉惯性系统的尺度会整体偏移。3.3 编译、运行与产物解读编译依赖主要是 OpenCV、Ceres Solver 和 Eigen。Ceres 建议用和 VINS-Mono 发布时匹配的版本2.x 和 1.x 在部分 API 上有差异遇到编译错误先查版本不要盲目改代码。编译和运行的命令如下mkdir -p build cd build cmake .. make -j4 # 运行标定-i 指定配置文件--view 实时查看角点检测效果 ./intrinsic_calib -i ../config/cam0.yaml --view--view这个参数在调试阶段非常有用它能实时画出检测到的棋盘格角点方便你确认每一帧的检测质量。跑完之后终端会输出内参矩阵、畸变系数和重投影误差 RMS类似下面这种格式fx: 516.23 fy: 519.87 cx: 322.41 cy: 243.76 distortion: [0.0123, -0.0412, 0.0011, -0.0008] RMS reprojection error: 0.31 pxRMS 是衡量标定质量最直接的指标。我的判断标准是低于 0.5 像素说明模型选对了、数据质量也过关0.5 到 1 像素之间还能接受但要检查是不是个别边缘图像拖后腿超过 1 像素就得回头查模型选择、标定板平整度和角点检测质量。这套输出参数后面要直接填进 VINS-Mono 的相机配置文件里所以这一步一定要稳。4. 从标定到视觉惯性融合注释里藏着的关键路径4.1 特征检测与光流跟踪标定代码和运行时代码的分工很多读者会被包里的 Chessboard.cc 误导以为 VINS-Mono 运行时就是用棋盘格做视觉定位其实不是。Chessboard.cc 只服务于标定阶段负责从标定图像里提取棋盘格角点。真正跑 VINS 的时候视觉前端用的是 feature_tracker 模块默认做法是 FAST 角点检测加 KLT 稀疏光流跟踪正文里提到的 SIFT、ORB 属于特征方案的泛称VINS-Mono 实际默认走的是光流路线因为光流在计算效率和帧间一致性上更适合实时系统。这部分注释的价值在于它让你看清了「标定用的角点」和「运行时的特征点」是两套完全不同的东西。标定角点要求亚像素精度和严格的几何排序运行时特征点只要求可重复和被稳定跟踪。理解这个分工你就不会在改代码时把两套逻辑混在一起。特征点数量上限、最小可跟踪距离这些参数在 feature_tracker 的配置里都能找到一般把特征点数上限设在 150 到 200 之间既保证约束充足又不会拖慢优化。4.2 IMU 预积分与视觉观测的时间对齐IMU 预积分是 VINS-Mono 的核心技巧之一。IMU 频率通常是 100 到 200 赫兹而相机只有 30 帧左右如果每一帧优化都把两帧之间所有 IMU 测量重新积分一遍计算量会爆炸。预积分的做法是预先对两帧之间的 IMU 测量做积分得到相对旋转、速度和位移增量这样优化迭代时只需要更新预积分增量对应的残差不用重复积。注释里你会在 CostFunctionFactory 相关代码中看到预积分残差因子它和视觉重投影残差一起进入滑动窗口优化。时间对齐是预积分能正确工作的前提。视觉特征和 IMU 测量必须按照统一时间戳组成观测对如果相机和 IMU 的时间戳没有同步最常见的表现是初始化一直失败或者系统跑起来之后视觉位姿和 IMU 积分出来的位姿互相打架。我一般会在预处理阶段先比较两路数据的时间戳差确认系统能接受的最大时间偏移再决定是调硬件同步还是做软件插值。这一步做扎实了后面初始化成功率会明显提升。4.3 滑动窗口优化、关键帧与回环检测的触发逻辑正文里提到的状态估计路线会用到 EKF 或 UKF但在 VINS-Mono 的工程实现里主角是滑动窗口里的非线性最小二乘优化也就是基于 Ceres 的 Bundle Adjustment。滑动窗口会固定维护最近若干帧每进来一个新关键帧就把最老的帧边缘化掉边缘化的信息转换成先验约束保留下来。这样既控制了计算量又不会粗暴丢失历史信息。回环检测是纠正长期漂移的关键。VINS-Mono 用词袋模型对当前帧和历史关键帧做匹配一旦检测到回到曾经到过的场景就触发一个四自由度的位姿图优化把累积漂移拉回来。注释包里虽然没直接包含回环检测的完整实现但理解了相机模型的投影链路你就知道回环帧的视觉特征走的是同一套投影函数任何标定误差都会被放大到回环优化里。所以我一直强调回环失效先查标定不是先查回环模块这是被很多人忽略的排查方向。5. VINS-Mono 避坑手记标定发散到回环失效的五个坑5.1 标定阶段的坑坑一鱼眼镜头用了 PINHOLE 模型。现象是重投影误差 RMS 超过 2 个像素内参里的 fx、fy 出现明显异常比如偏离标称值 20% 以上把标定结果喂给 VINS 之后地图整体扭曲。原因是针孔畸变模型只擅长表达小视场角的镜头畸变鱼眼边缘的强畸变会被优化器强行拉直为了掩盖边缘误差焦距就被带偏了。解决方法是把 model 改成 EQUIDISTANT 或者 SCARAMUZZA 重新标定然后对比两版 RMS。我一般会一次性跑两个模型直接用数据说话而不是靠肉眼猜镜头类型。坑二棋盘格角点检测失败或者角点顺序错乱。现象是程序报找不到棋盘格或者检测出的角点连线是斜的、棋盘格形状扭曲。原因通常是标定板反光、图像分辨率过高导致角点模糊、或者配置文件里的 cols 和 rows 跟实际标定板不一致。解决方法是换磨砂材质的标定板、关掉强补光把图像缩放到合适分辨率最后再核对配置里的角点数。还有一个小细节每张图里棋盘格太小也不行占画面三分之一以上是比较稳的经验值。坑三标定板不平整导致边缘标定结果飘。现象是中心区域重投影误差很小但画面边缘误差明显偏大。原因是打印纸贴在弯曲的表面上棋盘格本身的几何就不是平面。解决方法是把标定板平贴在玻璃板或者铝板上采集时也不要手持边缘导致弯曲。这个坑很容易被误判成相机模型问题白白浪费半天时间换模型重标。5.2 系统联调与工程链路的坑坑四相机和 IMU 时间戳不同步。现象是初始化反复失败系统跑起来后轨迹发散或者视觉和 IMU 的位姿估计有明显冲突。原因是相机帧率和 IMU 频率不匹配数据流没有做时间戳对齐预积分增量对应的时间区间和图像帧对不上。解决方法是先检查两路传感器的时间戳再用 kalibr 之类的工具标定相机到 IMU 的外参和时间偏移。预处理阶段如果同时开了多个传感器线程还要确认缓冲队列长度避免帧堆积。坑五依赖版本不对导致编译失败以及 7z 解压后中文注释乱码。现象是编译时报找不到 Ceres 头文件、OpenCV 版本冲突或者解压之后注释和文件名变成乱码。原因是多版本共存时 CMake 链接到了错误的库以及压缩包使用 UTF-8 编码而 Windows 控制台默认 GBK 解码。解决方法是锁死依赖版本用 vcpkg 或者固定路径引入解压时在 Linux 下执行LANGC.UTF-8 7z x指定编码Windows 下用 7-Zip 右键解压再把解压后的目录拖进 IDE。PyCharm 用户尤其注意别想着直接读 7z先解压再打开才是正路。6. 进阶用注释包反向梳理一套自己的标定流程读注释包的最高效用法不是把它当源码看而是把它当成一份带说明的标定 SDK反向提炼出属于自己的操作流程。我的做法是先把 intrinsic_calib.cc 的 main 函数到 CameraCalibration::run 的调用链画成一张思维导图再沿着调用链把每个相机模型的投影函数、畸变参数和适用镜头整理成一张参数卡片。这张卡片只留关键信息最后长这样模型投影特点适用镜头建议初始值接受 RMSPINHOLE针孔投影FOV 100 度fxfy500cx中心 0.5 pxEQUIDISTANT等距投影 rf*theta鱼眼 120-180 度同上 0.5 pxCATA统一球面模型折反射全景同上 0.8 pxSCARAMUZZA多项式近似强畸变镜头同上 0.8 px有了这张卡片每次拿到新镜头我都按同一套流程走先查镜头标称 FOV 圈定候选模型再用固定标定板采集一组图两个模型各跑一遍标定RMS 更低者胜出。标定通过之后还要把内参写进 VINS-Mono 配置跑一段五分钟的测试序列观察轨迹终点和起点的偏差如果漂移明显回头再验一遍标定而不是直接怀疑后端算法。从那以后我每次拿到新镜头都强制走一遍这套流程先出参数卡片再谈建图再也没有被标定问题拖过后腿。这套注释包最值钱的地方就是让你不用从零去啃那几千行公式推导直接站在注释的肩膀上把链路跑通。希望帮到你。本文还有配套的精品资源点击获取
返回列表