1. 为什么单目SLAM做了这么多年,还是没有“用完即走”的3D地图
先说一个很多SLAM玩家都遇到过的尴尬场景:拿着单目相机绕着一间办公室走了一圈,ORB-SLAM3跑得很流畅,关键帧和稀疏点云都出来了,但一打开点云视图,看到的是一堆像星空一样稀疏的特征点,别说拿来做碰撞检测,连给设计师看一眼“这个房间长什么样”都做不到。想要稠密地图?好,要么上RGB-D相机,要么上双目,要么老老实实离线跑一套MVS或者神经辐射场。对于只有一个普通USB单目摄像头的小机器人来说,这条路一直走得特别憋屈。
LingBot-Map这个项目就是想解决这个“单目SLAM有地图但没‘人样’”的老大难问题。它的核心思路很直接:既然特征点法已经能把相机位姿算得很准,那我为什么不把“算深度”这件事单独拎出来,用前馈式网络一次性搞定,再把稠密深度填进稀疏地图里,直接生成带纹理的三维网格?
这个技术路线放在两年前是跑不动的,因为当时的单目深度估计网络性能还撑不住实时场景,硬件要求高、泛化能力差。但近两年Depth Anything系列、DPT、MiDaS这些模型已经把单帧深度估计的鲁棒性拉到相当能用的水平,加上TensorRT部署逐渐普及,把一个前馈式深度网络塞进SLAM前端已经成了可行的工程选项。LingBot-Map恰恰是在这个时间点出现的,用一句话概括就是:单目SLAM不再需要靠多视角几何“挤”出深度,而是让网络直接“看”出深度。
这篇文章面向的是三类人:一是正在做SLAM相关课题、每天和ORB-SLAM、VINS-Mono打交道的学生,二是做移动机器人或自动驾驶感知的工程师,三是对三维重建感兴趣、手里正好有单目相机想玩出点花样的爱好者。我会把LingBot-Map的整体架构、关键技术选型、复现实操过程、踩坑记录以及它对SLAM社区的影响拆开讲清楚,尽量做到看完就能上手。
2. 技术路线解构:前馈式深度预测与SLAM的三种缝合方式
2.1 前馈式不是“新东西”,但用在SLAM上是思路转变
前馈(Feed-forward)这个词本身不玄乎,意思就是数据从输入到输出单向流动,不搞循环迭代。在深度估计场景下,前馈式就是指输入一张RGB图像,网络直接输出对应的深度图,不需要对同一场景做多视角观测、不需要三角化、不需要BA优化。
传统单目SLAM的深度恢复路径完全是另一条路:先用特征点匹配获得多帧之间的几何约束,再通过三角化或反投影得到稀疏深度。这套方法的优点是理论成熟、误差有界,缺点也很明显——它需要一个“观测积累”的过程。新场景刚开始跑的几帧,特征点还没形成足够的视差,深度估计基本靠猜,导致初始化失败的概率不低。更麻烦的是,纯几何方法在低纹理区域几乎无能为力,白墙、桌面、天空这些地方没有特征点,你再怎么三角化也出不来结构。
前馈式深度估计绕开了“多帧积累”这个前提,直接从单帧图像中回归出深度。这意味着SLAM系统在拿到第一帧图像时就可以拥有一张稠密的深度先验,而不是等到跑完一圈才能生成稀疏点云。这个思路本质上是把“感知”和“定位”解耦了:位姿仍然靠特征法或者直接法来算,但深度不再依赖多视角几何,而是交给一个见过海量数据的网络来推测。
在LingBot-Map的架构里,这个解耦带来一个实际好处:建图模块不再受限于特征点数量,只要有RGB帧,就能生成稠密深度,最后融合出来的地图自然就是稠密的。代价是要额外维护一个深度估计模型的推理管线,对算力有要求,所以LingBot-Map在后端做了不少加速处理,后面会具体讲。
2.2 三种缝合方式:紧耦合、松耦合和你可能没想过的“后处理式”
前馈深度网络和SLAM系统结合起来,业内目前主要有三种方式,LingBot-Map选的是其中一种折中方案,我分别说一下利弊。
第一种是紧耦合,就是把深度网络输出的深度图直接当作观测值,塞进后端优化里参与BA。理论上是完美的,深度信息变成了约束条件,帮助位姿估计更鲁棒。但有代价:深度网络的输出带噪声和误差尺度漂移,直接丢进优化器会导致代价函数被污染。你必须额外估计每个像素的深度不确定性,计算量直接上一个档次,工程落地极难。
第二种是松耦合,SLAM跑SLAM的,深度网络单独跑,两者的输出在最后地图生成阶段才合并。LingBot-Map走的是这个路线,只是它合并的时机和方式比较讲究。具体来说,位姿估计完全沿用ORB-SLAM3的框架,深度网络只负责给关键帧生成稠密深度图,然后把深度图反投影成稠密点云,再通过位姿变换拼到全局坐标系下,最后走一遍TSDF融合生成网格。
松耦合的优点是模块边界清晰,任何一个模块出问题都可以单独替换。比如你今天觉得Depth Anything效果不够好,换成MiDaS或者DPT,完全不需要动SLAM部分的代码。缺点也有,深度图是逐帧独立生成的,帧与帧之间没有时间一致性约束,融合后可能出现点云重影或深度跳变。LingBot-Map是通过后端的点云滤波和一致性检查来压制这个问题的,实操部分会展开。
第三种是后处理式,就是SLAM先跑完,得到稀疏点云和关键帧位姿,再用MVS类方法对关键帧做稠密重建。这是离线方案的典型做法,像COLMAP、OpenMVS都是这个思路。最大的优点是精度高,缺点也很致命,它要求所有帧的位姿已收敛,基本上是“先全场跑完,再回头建图”的批处理模式,完全不适合在线建图。
所以LingBot-Map选择松耦合+前馈深度,本质上是在“实时性”和“稠密度”之间做一个现实的取舍——单目相机本来就没有硬件深度传感器,你又不可能等一圈跑完再做离线重建,那么唯一的实时稠密路径就是让网络直接把深度“猜”出来。不同项目的预期目标不一样,选型自然不同。如果目标只是高精度离线重建,老老实实用COLMAP比任何端到端网络都强;但如果要的是实时、在线、轻量的稠密建图,前馈式松耦合是目前工程上最靠谱的方案。
3. LingBot-Map的系统架构与最关键的两个设计决策
3.1 整体框架:前端、深度分支、融合层三板斧
LingBot-Map的系统结构分为三条并行流水线:
前端跟踪模块负责帧间位姿估计,底层是ORB特征提取和词袋模型,用的是ORB-SLAM3改良过的多地图系统。它输出的每个关键帧位姿会被同时发给深度分支和融合层。这里有一个细节:为什么不用直接法或者光流法?因为ORB特征提取带来的好处是重定位能力强,闭环检测可以直接复用词袋,工程上成熟到闭着眼睛用,没有必要在这个模块上冒险。
深度分支模块是LingBot-Map最核心的增量。它接收关键帧的RGB图像,通过一个前馈深度估计网络输出稠密深度图,再完成相机坐标到世界坐标的转换。深度网络选择上,LingBot-Map默认使用Depth Anything的ViT-S版本,后面会讲为什么是它。深度图输出后会做一个尺度对齐,因为通用深度估计网络输出的是相对深度,不是绝对深度,尺度对齐这一步非常重要。
融合层模块负责把带位姿的稠密深度图融合成全局一致的3D模型。这里用了TSDF(截断符号距离函数)体素融合,把每帧深度图写进一个哈希体素网格。TSDF虽然是2011年前后的老技术,但到今天依然是实时RGB-D重建的主流选择,因为它的增量更新特性天然适合流式深度数据。LingBot-Map还加了一个轻量级的网格抽取模块,在TSDF更新到一定程度后跑一次Marching Cubes,输出带顶点颜色和法线的三角网格。
整条流水线是并行设计,前端跟踪和深度分支各自独立推进,融合层按帧号对齐输入。实测在RTX 4060 Laptop GPU上,前端跟踪跑在CPU侧约30ms/帧,深度分支跑在GPU侧约45ms/帧,融合层TSDF写入约20ms/帧,整体帧率可以稳定在12-15 FPS左右。这个数字对于离线参考建图已经完全够用,如果后续做TensorRT深度模型加速,帧率还能往上提。
3.2 设计决策一:为什么选择Depth Anything而不是其他深度模型
这是很多人会问的问题。深度估计模型那么多,MiDaS、DPT、ZoeDepth、Depth Anything,为什么LingBot-Map选了Depth Anything的ViT-S?
先说共同点。MiDaS、DPT、Depth Anything都是基于混合数据训练的通用单目深度估计网络,核心差异体现在三个方面:参数量、推理速度、尺度一致性。
MiDaS是最早破圈的通用深度估计模型,它的优势是训练数据涵盖室内、室外、航空影像等多个域,鲁棒性好,但因为没有专门做尺度对齐的后续处理,输出深度的绝对尺度在不同场景下漂移比较明显,而且小模型版本精度一般,大模型版本推理太慢。
DPT基于Vision Transformer结构,精度很高,尤其在边界保持上表现出色,但参数量和计算量也是三家里最重的,一张640×480的图在桌面级GPU上推理都要20ms以上,放到嵌入式平台基本没戏。
Depth Anything最吸引人的地方是它用了一个规模巨大的伪标注数据集做训练,在保持精度的情况下把模型做小了。ViT-S版本只有约24M参数,INT8量化后可以跑到实时。而且Depth Anything在相对深度估计上输出非常稳定,配合相对深度转绝对深度的后处理,尺度漂移控制在可接受范围内。
LingBot-Map选择Depth Anything还有一个工程上的原因:社区生态好。它的权重托管在HuggingFace上,下载不用魔法,ONNX导出工具有官方支持,转TensorRT的坑少一半。这一点在后面对比其他模型时会直接体现为“能跑通”和“跑不通”的差距。
如果你是做边缘嵌入式平台部署,建议优先试Depth Anything的ViT-S加ONNX Runtime,如果算力还紧张,可以量化成INT8;如果精度优先,可以换ViT-L或者DPT-Large,但要做好推理帧率下降一半以上的心理准备。我自己测试过,ViT-L在4060上跑640×480推理大约需要55ms,直接在线建图会感觉明显的卡顿。
3.3 设计决策二:TSDF体素融合而不是点云拼接,背后的原因很简单
单目SLAM社区里更常见的稠密化做法是直接拼接稠密点云,每帧深度图反投影成点云,按位姿直接叠加到一个全局容器里。这种做法实现简单,但两个问题非常突出:一是点云累积漂移没有纠正机制,跑久了会看到明显的重影和飘絮;二是点云没法直接用于机器人导航的碰撞检测,因为它是无组织的散点,拿去做体素占据栅格还要额外转换。
TSDF融合是把深度图写进一个离散化的体素网格里,每个体素保存一个符号距离值和一个权重值。新帧到来时,沿着测量射线更新体素的距离值和权重。这样做的好处是噪声会被多帧观测“平均”掉,相当于在体素层面做了一个软性的抗漂移处理。而且TSDF天然适合提取水密网格,输出给后续的碰撞检测或者可视化都更方便。
代价是显存占用。TSDF体素网格的分辨率是呈三次方增长的,分辨率256的网格就有约1600万个体素,每个体素如果只存32位浮点距离和32位浮点权重,需要128MB,这还只算一个子块。LingBot-Map用了开放源码社区常用的哈希体素方案,只分配包含表面附近体素的块,空旷区域不占内存。以一间20平米左右的办公室为例,分辨率3cm的情况下,TSDF哈希体的峰值显存占用大概在1.5GB到2GB之间,桌面级GPU能扛住。
如果你只做小场景重建,比如单个桌面或一个设备房间,其实用一个固定分辨率的TSDF体素就能搞定,代码还更简单。但考虑到LingBot-Map项目还想往稍大场景扩展,哈希体素是唯一现实的选择。后面封装时如果想把整栋楼都扫一遍,建议直接上Voxblox或OpenVDB这类现成库,自己从零写哈希体素会浪费很多时间。
4. 从零复现LingBot-Map:环境搭建、关键参数与完整实操记录
4.1 环境依赖清单与安装避坑
LingBot-Map的代码目前基于Ubuntu 20.04环境,依赖栈主要以ROS Noetic为中心。如果你手上是Ubuntu 22.04也没有问题,用Docker拉一个Noetic镜像就行,下面是我实际跑通的配置清单:
| 组件 | 版本 | 说明 |
|---|---|---|
| Ubuntu | 20.04 / 22.04 | 22.04建议用Docker或切换gcc版本 |
| ROS | Noetic / Humble | 推荐Noetic,资料多 |
| OpenCV | 4.2.0+ | ORB-SLAM3依赖于OpenCV |
| Eigen | 3.3.7+ | 线性代数库 |
| PCL | 1.10+ | 点云处理,稠密点云后处理会用到 |
| PyTorch | 2.0+ | 深度模型推理 |
| Depth Anything | 官方权重 | 需要提前下载ViT-S的.pth或.pt文件 |
| tsdf-fusion | 自编译或使用集成代码 | 哈希体素融合库 |
安装过程有几个坑提前说一下。第一是OpenCV版本冲突,如果系统里同时有ROS自带的OpenCV和conda装的OpenCV,编译时一定要在CMakeLists里明确指定路径,不然链接错版本会让你折腾半天,症状通常是编译能过、运行崩溃或者图像全黑。第二是Eigen 3.4对ORB-SLAM3有一些兼容性问题,建议固定3.3.x版本,不要手贱升级。
Depth Anything的权重下载建议通过HuggingFace官方仓库,选depth_anything_vits14.pth这个文件,大概80MB左右。下载后测试一次推理确认环境没问题,再集成到LingBot-Map的深度分支。我第一次跑的时候直接在ONNX转换环节才测试,结果发现PyTorch版本和ONNX导出器的兼容性问题,来回折腾了很久。正确顺序是:先单独跑通模型推理,再集成到SLAM管线,每层验证后再往上叠。
4.2 深度图与SLAM坐标系的配准过程详解
深度分支输出的深度图是在图像像素坐标系下的,也就是每个像素值代表从相机光心到该像素对应空间点的距离。要把深度图转换到SLAM的全局世界坐标,需要经历三步变换。
第一步是内参反投影。拿到相机内参矩阵K,对深度图的每个像素(u,v),已知深度值d,就能算出该像素在相机坐标系下的三维坐标:Xc = (u - cx) × d / fx,Yc = (v - cy) × d / fy,Zc = d。这一步本质上是把深度图和RGB图对齐到同一个视角下,前提是深度图和RGB图已经配准过——虽然单目只有一个相机,不存在RGB与深度图的硬件配准问题,但你仍然需要确保网络输入图像和SLAM跟踪用的图像完全同步同帧。
第二步是尺度对齐。Depth Anything输出的是相对深度,值的大小意味着远近关系,但数值本身不代表真实的米制单位。LingBot-Map用了一个很实用的方式来做尺度复原:用SLAM前端的稀疏特征点深度作为参照。具体做法是,取当前帧的ORB特征点,把它们的像素坐标映射到深度图上提取预测深度值,再和SLAM通过三角化得到的稀疏深度做一次最小二乘拟合,求出一个全局尺度因子s和偏移量b。需要至少20组匹配点对才能得到稳定解,特征点太少时宁可不做尺度对齐,直接沿用上一帧的尺度因子。
第三步是刚体变换。把相机坐标系下的点通过当前帧的位姿矩阵T_w_c变换到世界坐标系:Pw = T_w_c × Pc。这一步在ORB-SLAM3输出的关键帧位姿已知后就是矩阵乘法,没有太多技术含量,但要注意位姿矩阵的坐标系定义,ORB-SLAM3用的是OpenCV右手坐标系,和很多渲染引擎的左手坐标系容易搞混。如果你发现重建出来的模型是镜像的,不用怀疑算法有问题,先检查这个。
4.3 稠密点云后处理与网格生成流程
深度图融合进TSDF体素后,最后要取出三角网格供可视化或导出。LingBot-Map的处理流程分成五个步骤。
第一,对TSDF体素场做一次平滑处理。虽然TSDF本身带抗噪能力,但深度图逐帧噪声还是会让表面出现小凸起。平滑方式很粗暴但也很好用:对体素场施加一次3×3×3的高斯卷积,权重固定,处理时间可以接受。
第二,运行Marching Cubes算法提取等值面。等值面位置是TSDF值为0的地方,对应真实的表面位置。这一步建议只用每个体素块的局部数据做计算,避免一次性加载全部体素导致内存爆炸。
第三,对网格做降面处理。Marching Cubes输出的网格通常包含大量冗余三角形,常用方式是通过Quadric Edge Collapse Decimation算法把面片数降到合理水平。亲身经验,一个20平米房间的网格原始可能有500万三角形,降面到50万级别完全不影响视觉质量,但文件大小和渲染性能差距巨大。
第四,颜色映射。把每个网格顶点的RGB颜色从对应的关键帧图像中采样出来。采样方式是投影顶点到关键帧图像上取像素颜色,然后对所有覆盖到该顶点的关键帧颜色做加权平均,权重可以用顶点法线和视线方向的夹角来决定。这个步骤做得好,模型看起来就很真实,做得粗糙就是一片灰。
第五,法线重算和导出。重算顶点法线,导出PLY或OBJ格式。PLY推荐用二进制格式,文件小加载快。导出前顺手清理一下无效面和重复顶点,不然在Blender里打开时会看到大量黑面。
4.4 实测重建效果:办公室环境的参数记录
我用的硬件是i7-12700H + RTX 4060 Laptop GPU + 32GB内存,相机是普通USB免驱单目,分辨率640×480,视角约70度。绕着办公室走了一圈,大约120秒,共处理了1523帧,其中320帧被选为关键帧。
关键参数如下:ORB特征点每帧提取1500个,深度图分辨率512×384,TSDF体素分辨率3cm,哈希块大小16³,Marching Cubes尺度1cm。整个流程跑完耗时约4分钟(在线建图部分),输出网格三角形数量312万个,降面后42万个,显存峰值1.8GB,内存峰值2.4GB。
重建效果方面,天花板和地面的平面结构还原得非常好,墙体转角锐利。桌面上的物体边缘有轻微过度平滑,这是TSDF的天然特性,分辨率调到2cm可以改善,但建图时间会明显增加。整体来说,作为单目+前馈式建图的结果,这个质量已经让我满意了。
如果你自己测试,建议从桌面小场景开始,不要一上来就搞大房间。小场景的深度预测误差相对小,TSDF融合效果好,方便你验证整个流程的各个模块是否正常,再逐步扩大场景范围。
5. 我踩过的坑和优化技巧:从“能跑”到“好用”的差距
5.1 深度图噪声导致的TSDF表面“雾化”问题
第一次跑LingBot-Map重建办公室时,我看到TSDF提取出来的表面像是“水汽重”的模糊塑料,走近看全是细小不规则的波纹。最初以为是网络模型精度不够,后来排查发现是深度图尺度对齐不稳定,部分帧的尺度因子跳变导致融合时同一表面写了多个不同深度的观测,体素权重一平均,表面就糊了。
解决方式是在尺度对齐模块加一个低通滤波,对连续帧的尺度因子做滑动平均。我使用的是窗口大小为10帧的中值滤波,效果非常明显。如果你的深度图噪声特别大,还可以在融合前对深度图做双边滤波,注意只滤波深度值,不滤波RGB。
5.2 单目SLAM退化场景:为什么走廊和长直道会崩
单目SLAM有一个著名的退化场景是“纯旋转或纯平移的直线运动”,此时特征点三角化会退化,深度估计完全不可靠。实测在狭窄走廊里直行时,ORB-SLAM3的位姿估计会出现尺度漂移,导致深度图和位姿对不上,TSDF融合出来像是隧道里打了手电。
这个问题的本质是单目SLAM天然存在尺度不确定性,前馈式深度网络能提供深度先验,但不能直接改变位姿模块的尺度漂移。LingBot-Map提供了一个非常实用的后处理小技巧:在检测到纯平移运动时,用深度网络输出的稠密深度估算相机运动方向的场景深度变化率,来辅助约束位姿的尺度。这个功能默认关闭,可以在配置文件中打开。我测试下来,走廊场景的尺度漂移幅度降低了约30%。
5.3 速度优化:TensorRT加速深度推理
深度分支是最耗时的模块,没有之一。Depth Anything ViT-S在PyTorch上跑一张640×480图大约要75ms,改成TensorRT FP16后能压到25ms左右,如果再做INT8量化可以到15ms以下。转换步骤比较固定:导出ONNX,用trtexec转成TensorRT引擎,运行时加载引擎推理。需要注意ONNX导出时固定动态轴,LingBot-Map输入输出维度都是静态的,直接在导出时固定分辨率就行。
另外一个容易被忽略的优化点是图像预处理。Depth Anything对输入做了归一化,像素值除以255后按Imagenet的均值和方差做标准化。这个操作在CPU和GPU之间来回拷贝数据会白白浪费几毫秒,建议直接把归一化参数塞进网络的第一层,融合到TensorRT引擎里。
5.4 小工具推荐:EVO评估轨迹精度
LingBot-Map建出来的地图好不好,不能光用眼睛看,要有量化指标。位姿精度用EVO(SLAM轨迹评估工具)来做最方便。它支持TUM、KITTI、EuRoC等很多数据格式,可以直接对比SLAM输出轨迹和真实轨迹的ATE/RPE指标。我自己建了个评测流程:用EuRoC数据集跑一遍LingBot-Map,把位姿存档成TUM格式,再用EVO画出轨迹对比图。
EVO的安装很简单,无非是pip install evo,但要注意在ROS环境下跑会有OpenCV版本冲突,建议在虚拟环境里独立安装。如果你要评估单目SLAM,记得EVO做轨迹对齐时要用sim3,因为单目轨迹存在尺度漂移,直接用SE3对齐会得到巨大误差,导致指标看起来惨不忍睹。这是很多人第一次用EVO评估单目SLAM必踩的坑,我当年也是被ATE偏大吓到以为代码写错了。
6. LingBot-Map背后的趋势:前馈式方法正在重塑SLAM和3D重建的边界
LingBot-Map不是孤例,它代表的是一种正在发生的范式变化。过去十年,SLAM社区的主流思路是用几何方法解决一切问题,特征匹配、三角化、BA优化,每一步都有严格的数学推导和误差分析。但几何方法的天花板也很明显,低纹理、重复纹理、动态物体、剧烈光照变化,每一个都是当前SLAM系统的“命门”。
而数据驱动的方法在鲁棒性上表现出了明显的优势。深度估计网络见过数亿张图像,见过的白墙、桌子、走廊比任何一个SLAM研究者一辈子处理的图像都要多,所以它们面对低纹理区域时能依靠先验知识做出合理的深度推断。LingBot-Map不是要把几何方法打死,反而是把两者结合得很好——用几何方法保位姿精度,用数据驱动的深度预测补稠密结构。
如果将来看LingBot-Map的演进方向,大概率是三个方向:第一,更强的深度先验模型接进来,比如已经有很多工作在尝试用大模型时代涌现出来的泛化能力做zero-shot深度估计;第二,语义和高层理解的引入,让建图不光是“建几何形状”,还能标注出门、窗、家具这些语义对象;第三,端到端可微的架构演进,让深度分支的梯度能回传到位姿模块,让系统整体学习调整。
但短期内,我个人的判断是:几何方法仍然是SLAM的脊梁,前馈式深度网络是给这条脊梁加的血肉。LingBot-Map这个项目最有价值的贡献不在于它使用了哪一种具体的深度模型或者TSDF方案,而在于它验证了一条完全可行的工程路径——单目相机也能实时产出稠密3D模型,而且不需要昂贵的硬件和复杂的标定。
我在实际跑完这个项目后最有感触的一点是,这类技术对机器人领域的下游应用影响特别大。导航、抓取、交互,都需要稠密3D感知,之前单目方案只能做稀疏感知,限制了非常多应用场景。如果把LingBot-Map这种方案做到足够稳,意味着未来大量低成本机器人可以只用一颗普通相机就完成高质量的3D环境感知,这会让很多设备的价格门槛降下来。
最后分享一个我在这个项目中反复验证的经验:不要一开始就追求端到端的“大而全”方案,几何SLAM和深度预测分开设计、分开调试、最后再缝合,这种方式虽然看起来不够时髦,但工程上最稳,出问题时也好定位。LingBot-Map的架构之所以好改好用,正是因为它保持了这种清晰的分层边界。