☰
线面SLAM从零入门:核心原理、参数化与实操指南
2026/10/16 1:19:33 网站建设 项目流程

1. 暑期研习系列:线、面SLAM从零入门(一)

1.1 为什么选在暑期啃SLAM这块硬骨头

每年一到暑期,总有一批同学开始琢磨着给自己“加餐”。有人刷算法题,有人补数学,还有人盯上了SLAM这块公认难啃的骨头。我当年也是这么过来的,第一次看到“同时定位与建图”这几个字的时候,脑子里全是问号——这玩意儿到底在算什么?为什么需要“同时”?线特征和面特征又是怎么冒出来的?

先说清楚这个系列要干什么。线、面SLAM从零入门,核心目标不是让你一上来就去改ORB-SLAM的源码,而是先把“线”和“面”这两种几何基元在SLAM里的角色搞明白。点特征SLAM已经非常成熟了,ORB-SLAM系列、VINS系列都是围绕点特征构建的。但点特征有个天然短板:在弱纹理环境里,比如白墙、走廊、玻璃幕墙,点特征提取不出来,跟踪就容易丢。这时候线和面就派上用场了——墙面的边缘是线,地板和墙壁的交界是线,大块的平面结构是面。这些几何信息在结构化环境里到处都是,而且比点特征更稳定。

这个系列适合谁看?如果你已经学过SLAM十四讲的前几章,知道什么是位姿、什么是李群李代数、跑过一两个开源SLAM demo,那这个系列就是给你准备的。如果你完全零基础,建议先去补一下视觉里程计的基本概念,不然直接看线和面的参数化会有点吃力。但也不用怕,我会尽量把每个数学推导背后的直觉讲清楚,不堆公式。

1.2 线特征和面特征到底解决了什么问题

先聊一个实际场景。假设你拿着相机在一个长走廊里走,两边是白墙,地面是纯色地砖。这种环境下,ORB特征点几乎提不出来,因为图像梯度太弱了。但人眼在这个环境里完全不迷路,为什么?因为你能看到墙和地面的交界线、墙和天花板的交界线、门框的竖直线。这些线在图像里是清晰的,而且随着你移动,它们的透视变化是有规律的。SLAM系统如果能把这些线利用起来,定位精度和鲁棒性都会明显提升。

面特征也是类似的逻辑。室内场景里,地板、墙面、桌面、天花板,这些都是大块的平面。一个平面在三维空间里有四个自由度(法向量三个,距离一个),比一个点特征(三个自由度)信息量更大。而且平面在图像里的投影区域很大,不容易被遮挡或模糊影响。把平面加入SLAM的优化框架,相当于给系统加了“大块头”的约束,位姿估计会更稳。

但线和面也不是没有代价。线的参数化比点复杂得多,一条三维空间中的直线有四个自由度,怎么用最少参数表示它、怎么在优化中更新它,都是坑。面的参数化也有类似问题,而且从图像里提取平面比提取线更难,通常需要RGB-D相机或者激光雷达。所以这个系列的第一篇,重点放在“理解为什么”和“知道怎么表示”上,实操部分会从最简单的线特征提取和参数化开始。

2. 线特征SLAM的核心原理与参数化方案

2.1 三维空间中的直线怎么表示才不别扭

点特征用三个数就能表示,直线不行。三维空间里一条直线有四个自由度:你可以想象一根无限长的棍子,它在空间中的位置和朝向,去掉沿棍子方向的平移(因为无限长,沿自身方向平移不改变直线),剩下就是四个自由度。常见的表示方法有几种,每种都有自己的脾气。

第一种是普吕克坐标,用两个三维向量表示,一个是方向向量d,一个是矩向量m。它们满足约束d·m=0,所以六个参数里只有四个独立。普吕克坐标的好处是直线之间的相交、平行判断很直接,而且对直线做刚体变换就是两个向量分别乘旋转矩阵,形式很统一。缺点是六个参数带约束,在优化里直接更新会破坏约束,需要额外处理。

第二种是正交表示,把普吕克坐标分解成一个旋转矩阵和一个尺度因子,旋转矩阵用四元数或旋转向量表示,这样就是四个参数无约束。优化的时候直接更新这四个参数就行,不会破坏约束。ORB-SLAM里用的就是这种思路,先转成普吕克坐标做投影和误差计算,优化时再转成正交表示更新。

第三种是端点表示,直接用两个三维点表示直线。简单直观,但问题是端点在图像里经常不稳定,一条线可能被遮挡一半,端点就飘了。而且优化的时候两个端点各自更新,容易把直线拉弯。所以端点表示一般只用于初始化,后续优化还是用普吕克或正交表示。

实操心得:如果你刚开始写线特征SLAM的代码,建议先用普吕克坐标做投影和误差计算,因为公式最直接。等到要写优化部分了,再转成正交表示。不要一上来就两种混着用,容易把自己绕晕。

2.2 线特征的重投影误差怎么算

线特征的重投影误差和点特征不一样。点特征是把三维点投影到图像上,和观测到的像素点算距离。线特征是把三维直线投影到图像上,得到一条二维直线,然后算观测到的线段端点到这条二维直线的距离。注意,这里用的是端点到直线的距离,不是直线到直线的距离,因为观测到的线段端点是有噪声的,而投影得到的二维直线是模型预测的。

具体来说,假设三维直线L投影到图像上得到二维直线l,观测到的线段两个端点是p1和p2。那么误差就是p1到l的距离加上p2到l的距离。这个误差对位姿和直线参数的雅可比矩阵是可以推导的,但推导过程比较长,这里不展开。关键是理解这个误差的物理意义:它衡量的是“模型预测的直线”和“实际观测到的线段”之间的偏差。

有一个细节需要注意:线特征的重投影误差对直线的方向不敏感,只对直线的位置敏感。也就是说,如果预测的直线和观测的线段平行但偏移了,误差会很大;如果预测的直线和观测的线段相交但角度差了一点,误差反而可能不大。这会导致优化时直线方向更新得比较慢。解决办法是同时考虑端点到直线的距离和直线之间的角度差,或者用更复杂的误差模型。

2.3 线特征提取的实操要点

线特征提取常用的算法是LSD和EDLines。LSD速度快,但容易把一条长线打断成很多短线;EDLines对长线更友好,但参数需要调。实际用的时候,我一般会先用LSD提取,然后做一个线段合并,把方向相近、端点靠近的线段合并成一条。合并的阈值需要根据图像分辨率调,太大容易把两条平行线合并成一条,太小又合并不了。

提取出来的线段还需要做筛选。太短的线段(比如小于20像素)直接扔掉,因为噪声大;太长的线段也要小心,可能是图像边缘或者光照突变造成的假线。筛选之后,还要做描述子的计算,用于后续的线特征匹配。线描述子常用的有LBD,它考虑了线段周围区域的梯度分布,比单纯的线段方向更鲁棒。

注意:线特征提取对图像质量很敏感。如果图像有运动模糊或者曝光过度,提取出来的线段会又短又碎。所以实际跑SLAM的时候,相机的曝光时间和运动速度要控制好,不然线特征反而会拖累系统。

3. 面特征SLAM的核心原理与参数化方案

3.1 平面参数化的四种方案对比

平面的参数化比直线更麻烦。一个平面在三维空间里有三个自由度:法向量两个(因为法向量是单位向量,只有两个独立方向),距离一个。但直接用三个参数表示平面会有奇异点,比如法向量接近竖直的时候,参数化会不稳定。所以常见的做法是用四个参数表示,再加一个约束。

第一种是Hesse形式,用单位法向量n和距离d表示,满足n·p+d=0。四个参数,一个约束。简单直观,但优化时约束不好处理。

第二种是球坐标,用法向量的两个角度和距离表示,三个参数无约束。但角度在极点附近有奇异,而且角度更新是非线性的,雅可比不好推。

第三种是四元数,把法向量用四元数表示,再加距离,五个参数两个约束。冗余度高,但更新平滑。

第四种是切空间表示,在平面的切空间里用两个参数表示法向量的扰动,距离单独一个参数。三个参数无约束,而且没有奇异点。这是目前最常用的方案,ORB-SLAM的平面版本和很多RGB-D SLAM都用这种。

实操心得:如果你用的是RGB-D相机,平面提取可以用深度图做区域生长,把深度相近、法向量相近的像素聚成一个平面。区域生长的阈值需要根据深度噪声调,深度噪声大的时候阈值要放宽,不然平面会碎成很多小块。

3.2 平面特征的重投影误差与优化

平面的重投影误差和线类似,也是把三维平面投影到图像上,然后算观测到的平面区域和预测平面区域之间的差异。但平面的投影不是一条线或一个点,而是一个区域,所以误差的定义更复杂。常见的方法有两种:一种是算平面参数在图像上的投影误差,比如用平面的法向量和距离构造一个误差项;另一种是算平面上的点到预测平面的距离,然后求和。

第一种方法计算量小,但物理意义不够直观;第二种方法物理意义明确,但需要知道平面上哪些点属于这个平面,通常用深度图或者分割结果。实际用的时候,我一般会用第一种方法做粗优化,然后用第二种方法做精优化。

平面的优化还有一个特殊问题:平面之间的约束。比如地板和墙面是垂直的,桌面和地面是平行的。这些结构约束如果加入优化,可以显著提升精度。但约束的权重需要调,太强了会过拟合,太弱了没效果。我一般会先用结构约束做一次初始化,然后在优化中逐渐降低权重。

3.3 面特征提取的实操要点

面特征提取通常依赖深度信息。RGB-D相机可以直接从深度图提取平面,激光雷达可以从点云提取平面。常用的算法有RANSAC、区域生长、层次聚类等。RANSAC适合提取主导平面,比如地板和墙面;区域生长适合提取连续的大平面;层次聚类适合提取多个小平面。

提取出来的平面需要做合并和筛选。法向量相近、距离相近的平面可以合并;面积太小的平面直接扔掉;法向量不稳定的平面也要小心,可能是噪声造成的。合并的阈值需要根据场景调,室内场景一般法向量夹角小于10度、距离差小于5厘米就可以合并。

注意:平面提取对深度图的噪声很敏感。如果深度图有空洞或者噪声大,提取出来的平面会又碎又飘。所以实际用的时候,深度图要先做滤波,比如双边滤波或者中值滤波,把噪声压下去再提取平面。

4. 线面SLAM的完整实操流程与代码框架

4.1 从零搭建一个线面SLAM的代码框架

假设你已经有一个基本的视觉里程计框架,能跑点特征SLAM。现在要加入线和面,代码结构需要改几个地方。首先是特征提取模块,要增加线特征提取和面特征提取,并且把提取结果和点特征一起打包成帧。其次是地图模块,要增加线和面的地图点,并且维护它们的参数化和更新。最后是优化模块,要把线和面的重投影误差加入优化目标函数。

具体来说,帧的数据结构里要加两个字段:一个是线特征列表,每个线特征包含两个端点、LBD描述子、普吕克坐标;另一个是面特征列表,每个面特征包含法向量、距离、内点索引。地图的数据结构里也要加两个列表:线地图点和面地图点,每个地图点包含参数、观测列表、更新历史。

优化的时候,误差项要分三部分:点特征重投影误差、线特征重投影误差、面特征重投影误差。三部分的权重需要调,一般点特征权重最高,线特征次之,面特征最低。因为点特征最准,线特征次之,面特征最容易受噪声影响。

# 伪代码示例:线面SLAM的优化目标函数 def optimize(frame, map): error = 0 # 点特征重投影误差 for point in frame.points: error += point_weight * reprojection_error(point, map) # 线特征重投影误差 for line in frame.lines: error += line_weight * line_reprojection_error(line, map) # 面特征重投影误差 for plane in frame.planes: error += plane_weight * plane_reprojection_error(plane, map) return error

4.2 参数调优的实战经验

线面SLAM的参数比点SLAM多得多,调起来很头疼。我总结了几条经验。第一,线特征的权重不要设太高,因为线特征提取本身有噪声,权重太高会把位姿带偏。一般线特征权重是点特征的0.5到0.8倍。第二,面特征的权重更低,0.2到0.5倍就够了,因为面特征提取依赖深度,深度噪声大的时候面特征很不准。第三,优化迭代次数不要太多,线面SLAM的优化容易震荡,迭代10到20次就够了,多了反而会过拟合。

还有一个坑是线特征的端点更新。如果优化的时候直接更新端点,线特征容易变短或者变长,甚至变成一条曲线。解决办法是只更新直线的参数(普吕克坐标或正交表示),端点只用于计算误差,不参与更新。这样线特征会保持直线形状,不会变形。

实操心得:调参的时候先用小数据集跑,比如TUM的RGB-D数据集里的几个序列。跑通了再上大数据集。不要一上来就用KITTI或者EuRoC,那些数据集场景复杂,线面特征提取容易出问题,调参会很痛苦。

4.3 线面SLAM的评估方法

评估线面SLAM和评估点SLAM类似,主要看绝对轨迹误差和相对位姿误差。但线面SLAM还要看线和面的重建质量。线的重建质量可以用线的长度误差和方向误差衡量;面的重建质量可以用面的法向量误差和距离误差衡量。这些指标在点SLAM里没有,需要自己写代码算。

还有一个评估维度是鲁棒性。线面SLAM的优势在弱纹理环境,所以评估的时候要专门跑弱纹理序列,比如白墙走廊、纯色地板。如果线面SLAM在这些序列上比点SLAM好,说明线和面确实起作用了。如果差不多,说明线和面的权重太低或者提取质量太差,需要调。

5. 常见问题与排查技巧实录

5.1 线特征提取不稳定怎么办

线特征提取不稳定的表现是:同一场景下,不同帧提取出来的线段数量差异很大,或者线段的端点飘忽不定。原因通常有三个:图像质量差、提取参数不合适、场景本身线特征少。解决办法:先检查图像质量,如果有运动模糊或者曝光问题,先解决图像问题;然后调提取参数,比如LSD的阈值、最小线段长度;最后如果场景本身线特征少,那就不要强求,降低线特征的权重或者干脆不用。

5.2 面特征提取碎成很多小块怎么办

面特征提取碎成小块的表现是:一个大平面被提取成很多小平面,每个小平面的法向量和距离都有差异。原因是深度图噪声大或者区域生长阈值太严。解决办法:先对深度图做滤波,把噪声压下去;然后放宽区域生长的阈值,让更多像素聚成一个平面;最后做一个平面合并,把法向量相近、距离相近的小平面合并成一个大平面。

5.3 优化震荡不收敛怎么办

优化震荡不收敛的表现是:误差曲线上下波动,位姿估计跳来跳去。原因通常是权重设置不合理或者误差模型有问题。解决办法:先降低线特征和面特征的权重,让点特征主导优化;然后检查误差模型,确保线和面的重投影误差计算正确;最后减少优化迭代次数,或者用更稳定的优化算法,比如LM算法代替高斯牛顿。

问题可能原因排查方法解决方案
线特征数量波动大图像质量差、提取参数不合适检查图像清晰度、调提取阈值改善图像质量、调参、降低权重
面特征碎成小块深度噪声大、区域生长阈值严检查深度图噪声、调阈值深度滤波、放宽阈值、平面合并
优化震荡不收敛权重不合理、误差模型有误检查权重、检查误差计算降权重、修误差模型、减迭代次数
线特征端点飘端点参与优化更新检查优化变量只更新直线参数,端点不更新
面特征法向量不稳平面内点太少、深度噪声大检查内点数量、深度质量增加内点、深度滤波、降低权重

5.4 线面SLAM在弱纹理环境下的表现

弱纹理环境是线面SLAM的主场。我实测下来,在白墙走廊里,点SLAM跑几十帧就丢了,线面SLAM能跑几百帧。但线面SLAM也不是万能的,如果走廊里连线特征都没有,比如纯白圆弧墙面,那线面SLAM也救不了。所以实际用的时候,要根据场景选择合适的特征组合。结构化环境用线面,非结构化环境用点,混合环境用点线面。

注意:线面SLAM的计算量比点SLAM大,因为线和面的参数化更复杂,优化变量更多。如果跑在嵌入式平台上,需要做计算优化,比如用稀疏矩阵、并行计算、特征筛选等。不要指望线面SLAM能在低端设备上实时跑,除非场景特别简单。

6. 从入门到进阶的学习路径建议

6.1 第一阶段:理解线和面的数学表示

这个阶段的目标是能把线和面的参数化推导一遍,知道每种表示方法的优缺点。推荐先看SLAM十四讲里关于李群李代数的部分,然后找几篇经典的线面SLAM论文看,比如关于普吕克坐标和正交表示的。不要急着写代码,先把数学搞明白。数学不清楚,代码写出来也是错的。

6.2 第二阶段:跑通一个线面SLAM的demo

这个阶段的目标是能跑通一个开源的线面SLAM系统,比如PL-SLAM或者RGB-D SLAM的平面版本。跑通之后,试着改几个参数,看看效果怎么变。然后试着在弱纹理序列上跑,对比点SLAM和线面SLAM的差异。这个阶段最重要的是建立直觉:什么场景下线面有用,什么场景下线面没用。

6.3 第三阶段:自己写一个简单的线面SLAM

这个阶段的目标是从零写一个简单的线面SLAM,不用很复杂,能跑通就行。可以先从线特征开始,加入点特征SLAM的框架里,跑通了再加面特征。写的过程中会遇到很多坑,比如参数化更新、误差计算、优化震荡等,这些都是宝贵的经验。写完自己的线面SLAM,再回头看开源代码,会有更深的理解。

实操心得:写代码的时候一定要做单元测试。比如线特征的重投影误差,可以手动构造一个已知的直线和位姿,算出来的误差应该和理论值一致。单元测试能帮你快速定位bug,不然优化不收敛的时候你都不知道是哪里错了。

6.4 第四阶段:探索线面SLAM的前沿方向

线面SLAM还有很多开放问题,比如动态场景下的线面SLAM、线面SLAM和深度学习的结合、线面SLAM的长期建图等。如果你已经能熟练跑通线面SLAM,可以试着往这些方向探索。比如用深度学习提取线特征和面特征,比传统方法更鲁棒;或者在动态场景里,把动态物体的线和面剔除掉,只保留静态结构。

我个人在实际操作中的体会是,线面SLAM的门槛确实比点SLAM高,但一旦跨过去,你会发现SLAM的世界大了很多。点特征只是SLAM的一个视角,线和面提供了另外两个视角,三个视角结合起来,才能更全面地理解场景。这个暑期研习系列的第一篇就到这里,下一篇会讲线特征提取的具体代码实现和调参细节。

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

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

立即咨询