CubeSLAM学习:单目物体级SLAM的思路、核心模块与复现避坑实录
想搞清楚相机在场景里到底在哪,单目SLAM是最经济的手段,但纯点云、纯特征的那套玩法,在弱纹理、动态物体、遮挡多的环境里总是显得吃力。我最早接触CubeSLAM这个方案时,第一反应是“终于有人把物体的三维框拉进SLAM里用了”,它不再满足于只知道点在哪,而是直接回答“这有个茶几,茶几在哪,姿态怎么摆”。这篇文章就把我学习和复现这类物体级SLAM方案时的完整思路写出来,从方案选型、三维物体检测与位姿估计的细节,到联合优化、工程坑点,一次讲透。适合正在学SLAM的学生、刚接触物体感知的工程师,以及想把语义信息融进定位系统的从业者。
1. 内容整体设计与思路拆解
1.1 为什么单目SLAM需要“物体级”信息
传统单目SLAM把场景抽象成一堆稀疏特征点,视觉里程计靠光流追踪这些点,后端靠三角化得到路标。这个方法在室内纹理丰富的地方确实稳,可一旦面对白墙、玻璃幕墙、大面积重复纹理,特征点要么检测不出来,要么追踪后误匹配成一片。就算勉强跑起来,单目系统固有的尺度漂移问题也会在后端优化的长时间运行中逐渐放大。
物体级信息的价值在于,物体本身是天然的、稳定的、语义明确的地标。一个茶几、一台冰箱、一辆停在路边的车,它们有固定的物理尺寸和几何形状,不会像特征点那样容易被光照和视角“带偏”。CubeSLAM这类方案的核心思想就是:把检测到的三维边界框当作SLAM的约束,把物体位姿(位置与朝向)和物体尺寸直接放进优化问题,让相机轨迹、三维点和物体框在同一张因子图里联合求解。
单目相机没有深度传感器,要凭空估计物体的三维尺寸和位姿,本质上是一个病态问题。这也是为什么很多方案依赖深度学习回归或CAD模型匹配,而CubeSLAM给了一个很有意思的回答:不用深度网络回归三维参数,也不用CAD模型库,纯粹从几何关系里把三维框提出来。它借助一个核心假设——物体对应的三维框是长方体,通过二维检测框与三维长方体的投影关系反推相机与物体的相对位姿。这个思路在室内外很多物体上都是成立的,抽屉冰箱、桌椅沙发,基本都能用一个紧致的长方体包络。
1.2 方案选型:为什么选用3D边界框而不是2D检测框
学习这个方案时,我第一个想明白的问题是:已经有了成熟的2D物体检测,为什么还要费劲去构造3D边界框?
答案在于约束强度。2D检测框只能告诉优化器“某块图像区域属于某类物体”,对应到SLAM后端的约束非常弱,相机在光心连线的任意位置、任意姿态下,投影都可能落进同一个框里,歧义太大。而一个3D边界框在图像上的投影,包含了物体的具体朝向、在空间中的大致距离和尺寸信息。当同一个物体从多个不同视角被观测到,且多个三维框中心、尺寸之间相互一致时,每一帧的相机位姿就都被锁定得非常死。
另一个重要原因是数据关联的稳定性。纯特征点方案里,一个特征点就是一个小像素块,换个视角后相似点很多,特别容易匹配错。而三维边界框是物体级别的粗粒度特征,同一物体在连续帧间变化不会突然“跳变”,只要检测框不丢,关联就非常稳。以我自己的测试体验来说,物体级关联对快速运动、局部遮挡的容忍度明显高于纯点特征。
这个选型也有代价:检测器的质量直接决定系统的上限。如果目标检测经常漏检、框不准,物体级约束就会变成噪声。因此,实际落地时通常要搭配一个帧率稳定、类别泛化好的检测器,并且需要在系统里做检测结果质量的判断与过滤。
1.3 整体架构:相机、特征点、物体位姿与物体尺寸的联合优化
把CubeSLAM的完整链路拆开看,它其实是三个维度同时推进:
- 视觉前端:特征提取、匹配、双目几何(单目则是多视图几何),得到相机位姿初值和稀疏三维点;
- 物体级感知:借助目标检测器得2D框,通过几何约束恢复每个物体对应的3D边界框,包括大小与朝向的估计;
- 后端联合优化:以因子图(或滑动窗口)的方式,把相机位姿、三维点、物体位姿、物体尺寸全部设为优化变量,用投影误差作为约束一块儿做非线性最小二乘。
这个链路最巧妙的地方在于,物体尺寸作为一个全局常量被长期约束。跑过单目SLAM的朋友都知道,尺度漂移是老大难问题,纯单目长时间跑下来,地图会慢慢“缩水”或“膨胀”。可一旦在优化里加入“这个冰箱的高度是1.8米”的硬约束,整个地图的尺度就被钉住了。这也是物体级SLAM相比纯特征方法最本质的优势。
在实际复现时,官方论文里的流程图看起来很顺滑,但每一步都有实现细节的坑。下一节我就从最核心的“三维物体检测与位姿估计”讲起,因为整个方案的成败,多半看这一步做得稳不稳。
2. 核心模块拆解:三维物体检测与位姿估计
2.1 从2D检测框到3D边界框:核心几何关系推导
三维边界框本质上是一个有9个自由度(3个维度上的尺寸+3个方向的旋转+3个方向的位移)的长方体。如果已知相机内参,并且假设物体在地面平面附近,那么自由度数会进一步下降。CubeSLAM使用一个很直观的几何约束方式:在图像上画一个矩形框,假设这个框恰好贴合目标物体的2D包围盒,那么在相机坐标系下,这个2D框的四条边就是三维长方体的四条边在图像上的投影。
这个概念理解起来像什么呢?打个比方:你隔着窗户看到房间里一个纸箱,纸箱的六个外表面在窗户(图像平面)上的投影,会形成一组多边形。如果你知道箱子其实是长方体,还知道窗户的焦距,就可以通过反投影把所有可能的“纸箱在空间里的位置和朝向”列举出来,再和图像里检测框的边界对比,找出一组最贴合的那一个。
具体做法上,通常先在图像上找到2D检测框的四个角点,然后由四个角点构建两条消失方向的线索(如果物体朝向与相机光轴不平行,边缘延长线会交于消影点),再结合相机的内参矩阵K和物体类别对应的典型物理尺寸范围,列出关于物体旋转矩阵R_o和位移向量t_o的方程。解出来的初步3D框位姿往往有多个候选,需要使用一些先验过滤掉不合理的结果,例如物体应当位于地面平面之上、物体的投影应当几乎与检测框重合等。
2.2 单应变换与角点提纯:处理物体在图像上不同朝向的情况
在实际推导中,我们经常要面对物体相对相机“歪着放”的情况。物体旋转导致三个主轴在图像平面上的投影方向各不相同,此时2D检测框角点并不一定对应三维框角点的直接投影。如果硬套平行投影模型,求解出来的物体位姿误差会很大。
为了解决这个问题,CubeSLAM采用了一个两阶段策略:先用粗略的旋转估计生成一个单应矩阵,将目标物体区域“矫正”成正对相机视角的平面;在矫正后的平面上重新提取更加精确的角点;再把角点反投影回原图,利用这些提纯后的角点重新优化物体位姿与尺寸。这一步相当于把物体位姿估计问题拆成了“旋转矫正—精匹配—位姿更新”三个小步骤,每一步的数值稳定性都更好。
我在复现时的一个体会是,这类基于几何的位姿估计对输入很敏感。检测框稍微偏几个像素,反投影出来的三维位置就可能差几十厘米。所以我会把检测框先做一次sub-pixel级别的边缘对齐,或者使用分割掩膜替代矩形框来提供边界约束。分割掩膜虽然获取成本更高,但提供的约束是像素级的,位姿估计稳定性肉眼可见地提升。
2.3 物体尺寸估计与尺度模糊问题
一个单目相机看到一个大箱子在远处,和一个小箱子在近处,两者成像尺寸可能是相同的,这就是尺度模糊。单目三维物体检测普遍要面对这个问题,CubeSLAM也不例外。它的处理思路很务实:先利用目标类别先验猜测一组初始尺寸,再将尺寸作为优化变量放进后端,由多视角下观测的一致性来同时确定物体尺寸和相机轨迹。
具体到实现,初始化阶段一般会给每个类别一个先验的三维尺寸范围。比如车长3到5米,宽1.5到2米,高1.4到1.8米;家具类物体则根据2D框的宽高比做约束。后端优化时,物体的尺寸和位姿会连同相机位姿一起被修正。这样做的效果是:短时出现尺度模糊时,先验可以兜底;多个视角观测累积后,几何约束自然会把准确的尺寸筛选出来。
这个设计也解释了为什么CubeSLAM的定位精度在物体检测稳定的场景里比纯特征SLAM高一大截。因为每一个物体框实际上是一个包含了几十个特征点约束量的“高信息密度地标”,它同时约束了相机位姿的六个自由度里的平移部分(靠框中心反投影距离)、旋转部分(靠三维框朝向投影),还顺手锚定了地图全局尺度。
2.4 坐标系定义与投影公式:代码实现前必须先搞清的四套坐标
学习这类算法时最容易出的问题不是数学推导,而是坐标系搞混。我把整个系统里涉及的坐标系列在这里,建议实现代码前先画一遍:
- 世界坐标系W:后端的全局参考系,通常与第一帧相机位姿对齐。
- 相机坐标系C:以相机光心为原点,Z轴朝前,X朝右,Y朝下(常规视觉坐标系)。
- 物体坐标系O:以物体三维框中心为原点,三个轴分别对应物体的长宽高方向。
- 图像像素坐标系I:以图像左上角为原点,单位是像素。
一个三维框上的第j个角点,从物体坐标系变换到相机坐标系,再投影到图像坐标系,完整的链式变换是: p_I_j = K * [R_cw | t_cw] * [R_oc | t_oc] * P_o_j
这里的P_o_j是物体坐标系下的三维角点坐标,R_oc、t_oc是物体位姿,R_cw、t_cw是相机位姿。后端优化时,要对这个链式变换求关于相机位姿、物体位姿、物体尺寸的雅可比矩阵。
我当时实现时卡了很久,最后把四套坐标的变换矩阵全部打印出来,跟真值对比,才发现物体坐标系的轴方向定义跟论文里默认的约定差了90度。这类问题真不是看论文能看出来的,必须动手调试。
3. 实操过程与核心环节实现
3.1 最小可运行系统的搭建:检测器、跟踪与预处理的协同
学习CubeSLAM,我建议不要一上来就啃完整工程,而是先搭一个最小闭环:给定一段单目视频,能稳定输出相机轨迹和若干个物体的三维框。这个闭环需要三个核心模块协同工作:
- 目标检测器输出2D框和类别,我推荐使用实时性好一些的检测器做在线处理,精度较高的检测器做离线处理,二者效果差别很大;
- 视觉里程计模块提供初始相机位姿和一个初始稀疏点云,这是后续物体位姿估计和联合优化的基础;
- 物体级数据关联模块负责判断两帧里的检测框是否属于同一个物体,这个模块看似不复杂,实际是工程里最容易出错的地方。
数据关联我通常用两步法。第一步基于几何:把上一帧的物体3D框中心投影到当前帧,计算其与当前帧所有检测框的IoU,IoU大于阈值的候选框进入第二步;第二步基于表观:对候选框对应的图像区域提取CNN特征,与上一帧的特征做余弦相似度匹配。两步都通过才认为是同一个物体。只用几何关联的话,两个相邻物体交错时会关联错;只用表观关联的话,光照变化和遮挡会让特征失效。两条腿走路才稳。
3.2 后端因子图的构建:相机位姿、三维点、物体框如何协同优化
联合优化我建议采用因子图的形式来组织残差,这样新增一种约束非常方便。整个图的变量节点分为三类:
- 相机位姿节点:对应关键帧的位姿T_wc;
- 三维点节点:稀疏特征点经过三角化得到的三维坐标;
- 物体节点:包含物体位姿T_wo与物体尺寸s_o(三维向量)。
因子(残差)也分为三类:
- 视觉重投影残差:特征点P_w从世界坐标系投影到相机坐标系再投影到图像坐标系,与特征点观测位置u_ij的差。这部分与普通SLAM后端一致;
- 物体投影残差:物体三维框的8个角点按3.4的公式投影到图像平面,与2D检测框的边或角点观测做差。注意这里有两个细节:一是投影时只保留朝向相机那一侧的可见角点和边,不可见的不参与残差计算;二是2D检测框并不一定与三维框投影完美重合,在车辆侧偏角较大时,检测框会包含很多背景,此时应把残差的权重调低;
- 物体尺寸与类别先验残差:为了防止尺寸优化发散,加上一个类别先验的弱约束,让物体尺寸保持在合理范围内。
用因子图表示之后,整个问题就是一个标准的非线性最小二乘问题,可以用高斯牛顿或LM算法求解。但后端优化不是单纯调用一个求解器就完事的,有几点必须注意:
- 信息矩阵的设置。视觉重投影残差的协方差通常设为1到2个像素;物体投影残差的协方差需要根据检测框的置信度动态调整,检测置信度低时适当放大协方差,避免错误检测把全局优化带偏。
- 滑窗或关键帧的选择。物体级约束是稠密的,如果关键帧开得太多,优化规模会爆炸。我一般设置关键帧间隔为5到10帧,每新增一帧关键帧就剔除最旧的关键帧,并保留老帧与当前帧之间共视物体的双向约束。
- 求解顺序。建议先固定物体位姿,只优化相机位姿和三维点,迭代几轮后再放开物体位姿和尺寸一起优化。同时放开全部变量在初期容易陷入局部极小值,尤其是物体位姿最初的估计可能离真值很远。
3.3 物体级数据关联的细节处理:跨帧ID维护与误检过滤
物体级数据关联是整个CubeSLAM工程里最容易出暗坑的地方。我在一次数据集中调试时,两个并排停放的车物体ID乱跳,后端的物体框一会儿变成前车的尺寸,一会儿变成后车的尺寸,最后相机轨迹直接飘了。
排查下来,根因是我只在相邻两帧之间做关联,一旦检测器连续两帧漏检,ID就断了,后续再出现同一个物体时,就被当成新物体建了新的三维框。这个问题的解决方案是维护一个“物体轨迹”结构,记录每个物体的历史检测框、3D位姿估计、特征模板和连续未命中次数。当检测器检测到新物体时,优先与现有物体轨迹匹配,而不是只与上一帧的检测框匹配。连续未命中超过阈值的物体轨迹标记为“丢失”,不再参与关联。
还有一个经验:检测框的类别置信度不能直接当作关联匹配的置信度。两个不同类别但视觉上相似的物体(例如“椅子”和“小型沙发”)在特征空间可能非常接近,此时应该使用物体轨迹的三维位姿信息做硬过滤——位置相差太远就不允许关联。
误检过滤方面,我采用“双帧确认+连续跟踪”机制:单个帧孤立出现一次的高置信度检测框,不立即建立物体轨迹;只有连续两帧关联成功后才正式创建轨迹。这个规则能消掉绝大多数单帧误检。
3.4 因子图的求解配置:增量更新与滑窗调整的实践经验
联合优化的规模如果控制不好,运行时间会从每秒几十毫秒退化到秒级,这在实时SLAM里是不可接受的。我实践下来最有效的两个手段是:
第一个是采用增量式求解策略,只对新增关键帧产生的残差和受影响状态量做局部更新。这需要求解器支持增量分解,或者自己实现一个滑窗求解器。如果不做增量,每次新帧进来都对全部历史做全量优化,运行时间会随帧数线性增长,最终必然卡顿。
第二个是使用滑窗限制优化变量数量。我的做法是保留最近10到20个关键帧内的相机位姿,加上与这些帧有共视关系的物体节点和三维点节点,老的关键帧及其对应点用边缘化的方式转成先验约束,而不是直接丢进优化。这样既保证了优化的局部一致性,又限制了计算规模。
对于边缘化,有一点要提醒大家:被边缘化的变量通过舒尔补留下的信息矩阵必须保持对称正定,否则求解容易数值异常。我在实现时曾经图省事直接丢弃被边缘化的变量,结果地图出现严重的不一致,相机轨迹在边缘化点附近弯折。后来老老实实做了舒尔补,问题立刻消失。
4. 常见问题与排查技巧实录
4.1 检测器质量对系统影响有多大
CubeSLAM是把“检测→位姿估计→联合优化”整条链路串联起来的系统。如果检测器本身对目标类别的回归不稳定,三维位姿估计就会在几个不同姿态之间来回跳动,后端优化虽然能平滑一部分噪声,但很难完全纠正系统性偏差。
实测了不同检测器后的对比结果非常明显。我测试过轻量检测器和精度较高的检测器,在相同室内序列下,前者的平均轨迹误差比后者高出接近一倍。而这一倍的差距,几乎全部来自物体3D框初始位姿质量的下降。所以如果你在复现或者部署CubeSLAM,不要舍不得在检测环节投入算力。检测器可以换轻量版本跑在线,但至少要保证每帧的检测框质量足够稳定。
另外,实际场景中检测物体的类别往往与训练集分布不同。比如训练集里“卡车”的标注值是带车厢的整体包络,实际场景里可能只检测到车头。这种情况下,别指望后端优化能自己纠正过来。机器人或车辆视角下拍摄的图片,与常规数据集视角差异很大,最好在部署前用目标场景数据做一点微调或者后处理规则修正。
4.2 首帧初始化:为什么我总在这里翻车
单目系统的初始化是一个经典难点,CubeSLAM只是把这个难点从“特征点三角化”扩展到了“物体位姿初始化”。我第一次跑整个系统时,相机启动后毫无运动,画面对着一个空旷角落,系统就一直初始化失败,界面上没有轨迹,也没有物体框。后来复盘,原因很简单:首帧缺乏足够的视差,物体3D框的深度无法被确定。
正确的初始化姿势包括几个条件缺一不可:
- 启动时相机要有足够的平移运动,让场景中的特征点和物体框在连续多帧间形成有效视差;
- 画面里至少要有一个检测置信度较高的物体,不要对着白墙或无纹理区域;
- 物体的2D检测框边缘至少要有三条以上清晰的轮廓线能对应到三维框的投影边,如果物体被大范围遮挡,初始化出来的三维框会失真。
我在室内跑的时候,会专门把相机对准一个墙角,墙角附近有桌子、椅子、显示器这类明显物体,然后做一个缓慢的横向平移约半米再回正。大概需要两三秒,系统就能稳定初始化。
还有个小技巧是给初始帧的物体尺寸设置更紧的先验约束。初始化阶段最大的不确定性是深度,放宽尺寸先验会让深度不确定进一步放大。反过来,用类别先验把尺寸缩紧,相当于给了深度一个计算锚点,初始化会快很多。
4.3 退化场景下三维框估计不稳定
CubeSLAM一个老生常谈的问题是:物体正面正对相机,或从一个固定视角持续观察一个物体时,物体的深度和位姿都会退化。退化场景的具体表现为:物体3D框在某一帧突然翻转、前后跳动,或者尺寸突然变大变小。这在几何上完全解释得通——物体旋转矩阵中对应相机光轴的那个方向无法从图像中被观察出来。
应对退化有两类有效手段:
第一类是主动视角变化。这是SLAM里最简单粗暴也最有效的办法。手动控制相机绕物体做弧线运动,哪怕是幅度很小的弧线,也能极大缓解退化问题。机器人平台如果具备自主探索能力,可以加入一个“信息增益”规划项,主动让平台运动到物体侧面获取新的视角约束。
第二类是在后端加入物体运动模型或平面约束。如果物体位于地面且不会移动,可以增加一个“物体必须附着于地面平面”的强约束,把物体位置自由度从三维压缩到两维。这个约束一加上,退化问题几乎消掉一大半。不过要注意地面平面的外参标定要准,否则地面平面本身的误差会传导到物体位姿上。
4.4 快速代码排查:从现象定位到问题源头
复现CubeSLAM时,很多人遇到现象类似“物体框漂移”“相机轨迹抖动”,就急着去调后端参数。我的建议是,先做一次系统性的日志排查,把每一层的输出都记录下来,对比中间量:
- 检测层:打印每帧的2D框、类别、置信度。确认检测器输出不是乱跳;
- 数据关联层:打印每帧所有物体的关联ID。确认同一物体的ID没有频繁切换;
- 三维框估计层:打印每个物体轨迹的3D中心、尺寸、旋转。确认三维框没有在相邻帧之间突变;
- 后端优化层:打印每个残差的平均范数和最大范数。确认优化是否收敛,残差是否有异常峰值。
这套排查流程帮我定位过至少三种问题:检测置信度在低光照下剧烈波动导致物体轨迹频繁重建;相机位姿初值因视觉里程计漂移而误差过大导致物体投影残差误导优化;物体坐标系轴定义错误导致框内所有点偏移量恒定,轨迹整体偏移但内部形状不变。
如果走到这一步还没定位到问题,我通常会写一个模拟数据生成器:手工生成一条已知的相机轨迹,在轨迹上渲染一个虚拟3D盒子,然后把渲染结果喂给后端的每个模块。用已知真值去验证每一层的输出,这一招在算法调试中屡试不爽。
5. 物体级SLAM的扩展思考与实操建议
5.1 超越CubeSLAM:从物体级感知到语义地图的搭建
学完CubeSLAM的框架,完全可以把它扩展成一套“语义地图构建系统”。具体做法是:在联合优化的基础上,为每个三维边界框附上类别标签,然后把这些带标签的三维框投影到全局地图中,形成一张稀疏的语义物体地图。这张地图对机器人导航的意义非常大,它可以直接回答“咖啡机在哪里”“最近的椅子在哪”这类问题,而不是只给出一堆无标签的点云。
更进一步,物体框还可以作为动态物体处理的入口。传统SLAM把动态物体当作异常值剔除,但有了物体级感知后,可以反过来追踪动态物体的运动轨迹。比如一个移动的推车,它有一个3D框,每一帧的框中心位置、朝向都在变,这个变化量可以被估计出来作为物体的运动速度。如果多台相机或多个机器人共享地图,这些物体框还能作为跨平台的语义特征,帮助实现不同机器人的地图对齐。
5.2 工程落地的取舍:哪些场景适合用CubeSLAM
不是所有场景都适合物体级SLAM。我的建议是:
- 室内服务机器人、仓储物流场景,环境中结构化物体多且类别有限,非常适合;
- 自动驾驶在城区道路上的场景,车、行人、交通标志等物体密集,物体级约束能显著改善尺度漂移,但对动态物体的处理要求高;
- 完全未知的荒野、洞穴、海底等环境,没有稳定可检测的物体类别,CubeSLAM的优势就无法发挥,不如用传统特征SLAM加IMU融合。
另外要注意,CubeSLAM依赖稳定的检测器输出,如果你部署的平台算力极有限、连检测器都跑不动,那就不要强行上物体级SLAM。可以先跑轻量特征SLAM,物体检测放到云端或后处理,再在离线阶段做联合优化。
5.3 给学习者的最后几个建议
从我的学习经历里提炼几条最值得分享的建议,希望后来的学习者少走弯路:
- 不要先啃代码,先把三维几何推导手推一遍。投影公式、雅可比矩阵、坐标变换,这些数学基础不打好,看代码只会越看越晕;
- 一定用模拟数据验证每个模块。真实数据的问题太多,很难判断是哪个模块出错,模拟数据可以提供真值,帮助快速定位;
- 多视角真实数据做端到端验证。模拟数据通过后,用一段自己录的室内视频跑一遍,观察物体框是否贴合物体,相机轨迹是否平滑,这一步能发现很多数学推导之外的问题;
- 不要忽略工程细节。数据关联、漏检恢复、滑窗边缘化、协方差设置,这些“不起眼”的细节决定了系统能否真正稳定工作。
这套方案的学习曲线不算陡,但跨度很大,从几何到优化到工程实现都要碰。把每一层的坑都踩平之后,你会建立起一个完整的物体级感知与状态估计的系统观,这是纯做特征点SLAM很难获得的视野。如果手头有室内机器人或车载平台,强烈建议拿真实场景跑一段,看到物体框稳稳钉在桌椅上、相机轨迹不再漂移的那一刻,所有掉过的头发都值了。