ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

视觉SLAM入门指南:先懂原理,再跑Demo

视觉SLAM入门指南:先懂原理,再跑Demo

常有读者拿着编译报错截图来问我:“SLAM到底怎么学?”我反问他:“你先告诉我,SLAM到底是干嘛的?”对方沉默几秒,回答“是机器人导航用的吧……但具体怎么回事说不清。”

这不是个例。我见过太多人,下载了一堆依赖、看了大量资料,最后要么卡在“环境没装好”,要么卡在“跑通了但不知道在跑什么”。所以这篇视觉SLAM简介,就是写给刚接触这个领域、有点懵、又不想第一周就被数学公式劝退的人。我不打算堆满证明,也不打算列一串文献。只做三件事:把视觉SLAM解决什么问题讲明白,把主流技术路线画清楚,再告诉你一个新手怎么迈出第一步。目标很朴素——你看完之后,能用自己的话向别人解释什么是视觉SLAM,也知道该往哪个方向继续挖。

1. 先搞清楚 SLAM 在解哪道题

1.1 “我在哪”和“周围是什么”其实是一道题

想象你走进一个完全没有信号的陌生地下车库,抬眼全是排列差不多的柱子。你只能靠记忆判断自己大概在哪,同时每走几步就观察墙上的标志物、柱子的颜色,努力在脑海里画一张草图。走几步之后问题就来了:你想画地图,需要知道自己现在在哪;可你想知道自己在哪,又得先有一张地图。两个信息都是未知的,偏偏彼此依赖。SLAM(Simultaneous Localization and Mapping,同时定位与建图)就是专门解这道互相纠缠的题。

视觉SLAM,就是给一个移动的机器人或者设备装上摄像头,让它一边从视频画面里估算自己的位置和朝向,一边构建周围环境的地图。说人话就是:眼睛看着路,脑子里画着图。扫地机器人知道自己在客厅哪个位置、房间边界在哪,靠的是SLAM;无人机飞进没有卫星信号的室内,靠的也是SLAM;手机AR能把虚拟物体稳稳放在桌面上,背后同样是SLAM在算相机相对于桌子的位姿。

1.2 SLAM 输出的两个东西:轨迹和地图

SLAM系统最终输出两样东西:轨迹和地图。轨迹,是机器人每一时刻的位置和朝向,工程里通常用一个旋转矩阵加一个平移向量来表示,合称位姿。地图,在视觉SLAM里最常见的是稀疏的三维路标点,也就是一堆带坐标的特征点。需要特别强调:这两样东西必须一起估计,不能先算完轨迹再去建图。为什么?你第一次定位错了那么一点,就会把路标点放到错误的位置;错误的路标点又会导致下一帧定位错得更多。误差会滚雪球。

所以真正的SLAM算法会把所有位姿、所有路标点塞进同一个大的优化问题里,迭代地同时修正它们。你可以把整个系统想象成一条流水线:视频帧进来,先是前端从图像里提取信息、估计相邻帧之间的运动,然后后端把整段轨迹和地图一起打磨,回环检测负责发现“我绕了一圈又回来了”并修正累积误差,最后建图模块把结果整理成可供上层使用的形式。这套框架,后面的章节我会逐一拆开。

1.3 为什么非要用“视觉”来做这件事

激光雷达测距原理直接,白天夜里表现都稳定,但价格摆在那里,而且点云没有颜色和语义。摄像头恰好相反:便宜、普及、轻便,图像里天然带着颜色、纹理、边缘,甚至语义。你能从图像里看出“那是一扇白色的门”,而激光数据只能告诉你“那个方向有物体”。对上层智能来说,视觉的潜力大得多。

当然,视觉也有明显的短处:光线一变、镜头快速转动、画面全是白墙,算法都可能失灵。所以现在更常见的做法是把视觉和惯性测量单元(IMU)放到一起融合,互相补位。初学者一开始不必急着碰传感器融合,先把纯视觉是怎么工作的理解透,后面再往系统里加东西会顺利很多。

2. 三条路线,两条主流:特征点法、直接法与半直接法

2.1 特征点法:把图像翻译成“特征”再谈匹配

特征点法的核心,是把每一帧图片先翻译成一堆“特征点”。每个特征点包含两部分:关键点的位置,以及一段描述这个局部长什么样的描述子。图像来了之后,算法先找到角点这类显著的位置,然后对周围区域计算描述子。下一帧进来又找一遍,再用描述子去匹配——两个点长得像,就认为是同一个三维点的两次投影。有了足够多的匹配,就能估计两帧之间的位姿变化。

为什么初学者首选特征点法?因为它把每个环节拆得非常干净:特征提取是一个模块,匹配是一个模块,位姿求解又是一个模块,任何一步出问题都容易定位。而且描述子统计的是局部区域的梯度方向信息,对光照变化有一定忍耐力。短板也很明显:它只能得到稀疏地图,在白墙、光滑地面这类纹理很少的场景特别吃力,同时每帧都要提取和匹配特征,计算量不小。

2.2 直接法:不找特征,直接用像素亮度说话

直接法的出发点和特征点法完全不同。它不找特征,直接拿像素本身的亮度做比较。基本假设是:同一个空间点在不同帧里看起来亮度应该一样。那么当相机位姿变化时,把当前帧像素的灰度值和参考帧对应位置的灰度值一对比,差值就是误差,优化这个误差就能反过来推出位姿和深度。相当于把“找出对应点”这个步骤彻底取消,变成在连续图像里做亮度对齐。

直接法在特征缺失的地方确实能活,也能生成半稠密甚至稠密的地图。但它对光度变化极其敏感:同一个天花板,白天和晚上亮度不一样,直接拿灰度比对就容易出错。它对初始值的要求也很高,相机一旦快速转动就容易跟丢。这个流派的理论更绕,把图像坐标、位姿、深度全部揉进同一个能量函数里,新手一上来就啃它,很容易被打击到。

2.3 半直接法、流派对比与新手选型建议

半直接法更像工程派的选择:关键帧之间用特征匹配建立可靠的几何约束,普通帧之间用直接法的思路做增量跟踪,两边配合,既省计算量又保留视觉信息。听起来很理想,但工程细节非常多——什么情况切到特征匹配、什么情况继续用直接法,全是经验活。我不建议初学者把第一个代码项目做成半直接法,但了解一下这个流派的存在非常有必要。

流派核心思路优势劣势新手上手难度
特征点法提取特征 → 匹配 → 估计位姿鲁棒、模块清晰、可解释性强弱纹理场景失效、地图稀疏最低
直接法最小化像素亮度误差弱纹理可用、可做稠密光照敏感、依赖初始值较高
半直接法关键帧特征匹配 + 帧间直接法速度和鲁棒性折中实现复杂、调参多高

我的建议很简单:入门阶段只学特征点法。不是因为它最好,而是因为它最容易让你理解整条链路。等你能把每个环节讲清楚,再去看直接法和半直接法,两相对比,你会立刻理解它们各自的取舍。学习顺序对新手来说,比“哪种算法更先进”重要得多。

3. 入门路上绕不开的六块拼图

3.1 相机模型与畸变校正:图像坐标是怎么来的

很多教程讲相机模型时噼里啪啦丢出一堆坐标变换公式,新手看三行就关了。我用大白话讲一遍:现实里的一个三维点,经相机镜头中心投影,落在图像平面上,变成像素坐标。这个投影关系由相机的内参矩阵描述,里面放着焦距和光心位置。而相机本身在世界坐标系里也有一个位置和朝向,这叫外参。内外参拼在一起,三维点在世界坐标、相机坐标、图像坐标、像素坐标之间的转换就齐了。

畸变是镜头带来的额外麻烦。尤其是广角镜头,画面边缘的直线会弯成曲线,这叫径向畸变。如果不管它,你以为的像素坐标和真实投影位置对不上,后面估计位姿和三角化都会跟着偏。所以正式跑SLAM之前,一般要用棋盘格标定板拍一组不同角度的照片,解出内参和畸变系数,再在代码里做去畸变。别小看这一步,很多人第一次跑出来的地图七扭八歪,根子根本不是算法,而是没给相机做标定。

3.2 特征提取与匹配:让“同一个点”被认出来

特征提取与匹配是整个系统的信号源。可以这样理解:特征点是图像里一个特别的位置,描述子是这块区域附近的“DNA”。不同帧寻找同一个点,就是比较DNA的相似程度。实用上,匹配结果里总有一批是错的——光照变化、视角差异、重复纹理都会造成误匹配。常见的过滤手段是先算最近邻,再用比例检测把“第二近的邻居和最近的距离太接近”的匹配剔除掉,因为这往往说明附近有很多相似区域,匹配本身就不可靠。

这里有一条很重要的调试经验:别只盯着匹配数量,一定要把匹配结果可视化。把相邻两帧图像并排显示,用线段连接匹配点,错配一眼就能看出来——线段交叉乱飞的就是问题所在。如果这一步的误匹配清理不干净,后面再好的优化算法也救不回来,这就是所谓的垃圾进、垃圾出。我调试时打开的第一个窗口永远是匹配可视化。

3.3 位姿求解与三角化:从 2D 回到 3D

有了两帧之间正确匹配的2D点,就该回答两个问题:相机这两帧之间相对动了多少?被匹配到的那些空间点分别在什么位置?前一个问题的常用工具是本质矩阵(适用于非平面场景)或单应矩阵(适用于纯旋转或平面场景),解出来之后再分解出旋转和平移。后一个问题靠三角化:同一个空间点被两个不同位置的相机看到,两条视线一交,交点就是三维点坐标。

这里有一个非常经典的坑:单目视觉没有尺度。你看一张照片,没法分辨那是一座远处的山还是一块近处的石头。单目SLAM输出的所有位置和三维点,数值上可以在同比放大和缩小之间随意切换,而重投影误差完全不变。意思是,系统知道你走了10,但10米还是10厘米,单靠自己分不出来。想补上真实尺度,要么加双目,要么加深度相机,要么融合IMU。初学者第一次撞上这个现象往往很懵,但这恰恰是理解视觉SLAM和激光SLAM区别的关键点。

3.4 后端优化:别让误差一路滚雪球

前端给出的每对相邻帧之间的运动估计都带着噪声,如果把两两帧的结果串起来,误差会沿着轨迹一路累计。跑得越久,地图和真实位置的差距就越大。后端优化的任务,就是尝试用整段数据来解决这个问题:同时调整所有帧的位姿和所有路标点的三维坐标,让它们对每一帧的重投影误差总和最小。这个优化过程在视觉SLAM里叫光束法平差,也就是Bundle Adjustment。为什么叫光束?因为那些路标点就像光源,向每一帧发出观测光线,优化就是把所有光线的错位程度压到最低。

要理解BA,可以借用弹簧的比喻:每一次观测就是一根连着路标点和相机位姿的弹簧,弹簧的弹力大小等于重投影误差。你动任何一个节点,会连带拉伸附近所有弹簧。BA就是在成千上万根弹簧之间寻找一个平衡位置。实现这样的优化靠非线性最小二乘,常见思路是高斯牛顿法或列文伯格-马夸尔特法,工程上直接用现成的优化库就好,你不必从零写,但要看得懂它在解什么问题。

3.5 回环检测:如何认出“我来过这里”

如果只有前端和后端,系统就像一个蒙着眼走路的人,越走越不知道自己偏了多远。回环检测解决的是这个问题:当机器人绕了一圈回到曾经到过的地方,算法必须认出来,才能把所有历史轨迹重新对齐,把累积漂移一下抹掉。

做法一般分两步。先做图像检索,把当前图像压成一个全局描述子,去历史帧数据库里找相似的候选;再做几何一致性验证,确认候选和当前图像不是“长得像但实际不同的地方”,比如抽取一小批匹配点计算几何关系,通过验证才真正算数。确认回环之后,会触发一次位姿图优化,把所有帧的位姿重新拉一遍。这一下,之前可能歪出去老远的地图会被拉回规整的形状。初学者对这个模块经常后知后觉,直到某天发现地图首尾怎么都对不上的时候,才明白回环检测的厉害。

3.6 建图:稀疏、稠密和语义地图,各管一件事

不少人误以为SLAM输出的地图应该像游戏引擎里那种漂亮的三维模型,其实不是。最常见的视觉SLAM地图是稀疏路标地图:一堆特征点的三维坐标,它足以支撑系统自己做定位,但没法用来导航避障。想躲开障碍物,你需要稠密深度图或者占据栅格地图;想让机器人理解“这扇门是出口”,还得在之上叠语义标签。地图的类型由下游任务决定,是一个选型问题,不是越复杂越好。

对初学者,现阶段只需要理解一件事:SLAM的地图不是“拍下来的照片”,而是机器人内部对环境的抽象表达。稀疏点云用来定位,稠密点云用来导航,语义地图用来交互,各管各的。等真的要做具体应用,再按任务去决定建哪种地图,而不是一开始就追求照片级的重建效果。

4. 新手动手的第一步:环境和 Demo 怎么跑起来

4.1 硬件选型:摄像头和计算平台不用一步到位

先说结论:一个普通USB摄像头加一台普通笔记本,足够撑起入门期的所有实验。别一开始就买昂贵的深度相机,看起来省事,实际会让你绕过单目三角化这个核心训练。深度相机直接把深度给到每个像素,你确实能很快看到稠密点云,但对“深度是怎么来的”“深度有哪些不确定性”没有体感。就像刚学开车就上自动挡,少了手动挡的机械感训练,后面想排查故障时直觉也会差一截。

硬件上注意三点:帧率别太低,30帧左右起步;镜头别用太夸张的广角,畸变会给入门阶段添乱;光照环境尽量可控。这样一套设备几百块就能解决,足够你跑完整个入门周期。

4.2 软件准备:先解决“图像能不能打开”

软件环境是劝退重灾区。我的建议有三条。第一,在隔离环境里工作,别把系统原有的开发环境搞乱,Windows上完全可以用虚拟机方案,跑数据集时性能损失是可以接受的。第二,先把“显示一张图、播放一段视频、保存一帧”这些基本功练熟。很多新手半天搞不定SLAM,是因为视频读不出来,或者读出之后显示不了,这其实和SLAM本身没关系。第三,代码目录和数据目录分开,数据集动辄几个GB,混在工程目录里,版本管理和备份都会很快失控。

这些看似和算法无关的准备工作,决定了你第一天是愉快地跑通Demo,还是在报错信息里耗光热情。

4.3 数据从哪来:公开数据集和自制视频各有各的用途

数据源分两类:公开数据集和自制视频。我强烈建议先用公开数据集练手。公开数据集的好处是结果可复现,它还常常附带真值轨迹,你的算法跑完之后可以直接和真值对比误差,量化自己到底做得怎么样。能在数据集上跑通,你已经算入门了。

练了几天之后,再录自己的视频。录制时注意:环境里纹理多一些、光照恒定一些、别对着白墙一路走,运动别太猛。哪怕只是拿着手机绕自己的房间走一圈,只要有稳定纹理和适度转弯,就是一份很好的测试数据。先数据集、后自制数据,这个顺序能帮你把“算法能力”和“数据质量”这两个变量分开。

4.4 跑通流程的四个阶段:先脱机、再离线、后实时

我推荐一个四个阶段递进的流程。第一阶段:只在公开数据集上运行,打开可视化,观察轨迹曲线和地图点,参数一概不动。第二阶段:把自己的离线视频喂进去,跑完后看回放,重点观察跟踪有没有丢失、地图有没有明显漂移。第三阶段:把输入换成实时视频流,这一步才会直面相机驱动、时间戳对齐、帧率控制这些问题。第四阶段:能稳定跑通之后,才动手改参数、改流程。

为什么必须是这个顺序?因为每个阶段只引入一个新变量。如果你一上来就用自己的实时摄像头测试,一旦失败,你根本分不清是代码问题、相机驱动问题,还是运动太快、光线太差。把变量固定住,每次失败都能精确对应到一个原因,这是我在无数次翻车之后总结出来的最实用的习惯。

5. 给初学者的避坑清单和学习路线

5.1 别想先把数学啃完再动手

视觉SLAM确实要求数学功底,但新手不需要把数学全部啃完再动手。你完全可以先建立流程直觉,知道每个模块的输入输出,知道“这个环节输出不好会影响那个环节”,然后回头再补数学。我见过太多人卡在厚重的数学书面前,看了两个月还在前两章,一行代码没跑过。数学是给想改算法的人准备的深水区,不是给想建立认知的人设的关卡。

5.2 每次只改一个变量,把日志打清楚

调试SLAM和调试普通程序很不一样,它很少“报错即定位”,更多时候是性能问题。所以要养成记录关键数字的习惯。我通常会在每一帧计算后打印:特征点数、匹配对数、剔除误匹配之后的内点数、平均重投影误差、最近一次回环的相似度。只要观察这些数字随时间的走势,问题出在哪个环节基本一目了然。匹配对数骤降,大概率是运动太快或纹理不足;内点比例偏低,大概率是误匹配过滤阈值有问题。靠感觉调参是最浪费时间的方式。

5.3 我推荐的六步入门路线

第一步,能用自己的话说清视觉SLAM流水线:输入视频,输出轨迹和地图,中间有前端、后端、回环、建图。第二步,在一个公开数据集上跑通一个开源框架,边跑边画流程图做笔记。第三步,把框架里的前端代码找出来,逐个函数搞清楚它在做什么,并尝试改一行参数观察效果。第四步,读一两篇入门综述,建立领域全貌,知道还有哪些流派、哪些开放问题。第五步,用自己录的视频测试,观察光照变化、运动速度对结果的影响。第六步,做一个小改动,比如换一种匹配策略,用控制变量法对比改动前后的指标。能走完这六步,你已经不是一个“看过SLAM”的人,而是一个“开始会调SLAM”的人。

最后说点个人体会。带人入门时我常讲一句话:学视觉SLAM,目标不是成为一个公式背诵者。你在项目里遇到“地图又飘了”,能知道应该先查回环还是先查前端,这就是能力。它的学习曲线本来就是“先会跑,后会用,再造轮子”的顺序。如果这篇简介能让你在第一周就跑通一个Demo,并且对着终端里跳动的数字知道它们在说什么,那它就算完成任务了。剩下的路,就是在一个个“为什么地图又飘了”“为什么回环没检测到”的问题里慢慢变强。

RELATED READING

延伸阅读

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