1. 先看懂这篇工作:NeRF怎么闯进LiDAR SLAM的地盘
1.1 标题里三个词,拆开看
做SLAM的人看到这个标题应该会先愣一下:NeRF?那东西不是搞新视角合成的吗,跟激光雷达点云配准有什么关系?我最初也是这个反应。但仔细想一下,NeRF做的事情本质上是“用神经网络隐式表达三维空间”,而SLAM最后也要表达三维空间并估计自身位姿,这两件事在数学上本来就是同一套问题的两面。这篇RAL工作最核心的贡献,就是把“神经场的几何表达能力”和“LiDAR的精确深度测量”拧到了一起,而且做到了实时。
先拆关键词。LiDAR SLAM大家都熟:激光雷达给出一帧一帧点云,通过配准估计传感器运动,同时把点云拼成地图。NeRF呢,全称是神经辐射场,输入是一组带位姿的图像或深度观测,输出是一个连续函数:空间中任意一个三维点,映射到该点的密度和颜色(或者更一般地,映射到几何场量),然后用体渲染公式沿射线积分,重建出对应像素。放在一起,你就得到了一个非常自然的想法:把LiDAR的每一束激光当成一条射线,射线的端点是激光打到物体上的位置,射线上其余部分为空,这样就可以用神经隐式场同时反解几何和位姿,不需要传统配准里那种“点与点一一对应”的假设。
1.2 整个系统在跑什么流程
这篇工作的整体链路如果用一句话概括:先用轻量级前端给每帧LiDAR一个初始位姿,再开一个滑动窗口,把窗口内的激光射线丢给一个哈希编码的神经隐式场,通过可微渲染不断修正位姿和场本身,最后把优化后的位姿输出成里程计结果。
听起来有点抽象,我复现时是这么理解的:传统LiDAR SLAM是“先配准、后建图”,点和点之间的关系是硬匹配;这套系统变成了“先粗配准、再联合优化”,位姿和地图都在同一个可微模型里,是软约束。前端粗配准如果给得好,神经场收敛就快;神经场修出来的位姿又会反过来给前端一个更干净的地图。这样两者咬合在一起,比纯前端的里程计漂移小很多,也比离线NeRF快得多。
整个过程基本可以分成四块:前端畸变校正和帧间配准、滑动窗口管理、神经隐式建图、位姿图后端优化。这四个模块不是串行的流水线,前两个模块的结果会不断被后两个反哺。源码把这四块解耦得很清楚,每一块都有独立接口,二次开发时不用把整个系统推翻重写。
1.3 比传统LiDAR SLAM多了什么
传统的LOAM、LIO-SAM这类方案,核心残差是点到平面、点到边缘的距离,依赖的是点云局部结构。问题在于激光点云是稀疏的,尤其长走廊、隧道、空旷停车场这些场景,只有两侧墙壁有约束,沿走廊方向几乎没有几何信息,轨迹就容易飘。神经隐式场不一样,它可以用一个连续可导的场函数去拟合多帧融合后的点云,把稀疏离散的点变成一个连续曲面。哪怕当前帧落在上一帧两个点之间,神经网络也能从旁边帧的信息里“插值”出一个合理的约束。
另一个区别是,NeRF方案对位姿误差的响应方式不同。传统ICP做迭代时,几何残差在匹配出错的地方容易陷入局部最优,而神经场通过全网的连续梯度来修正,在你给了一组多帧射线之后,残差分布更平滑。用我当时跑下来的体验说,就是这套方法不那么“脆”,容忍前端给一个更粗糙的初始值。
当然代价也有:一个大神经场不可能实时,所以作者用了哈希编码加滑动窗口,后面我会单独讲清楚这个实时性的突破口到底是怎么抠出来的。
2. 为什么NeRF能帮LiDAR SLAM:背后的原理与设计取舍
2.1 几何连续性和可微渲染
NeRF最迷人的地方不是能合成炫酷视角,而是它把一个离散的三维场景强行写成了一个连续函数。传统占据栅格地图把空间切成均匀方块,分辨率越高内存膨胀越快;神经隐式场则只存网络权重,几百兆权重可以表达任意分辨率的连续表面。对激光雷达建图来说,这意味着地图的“分辨率”不再受栅格尺寸限制,你想在同一个场里查一个亚厘米级的表面位置也可以,只是查询密度和计算量的问题。
LiDAR的原理天然适合射线模型:每个激光点都对应一束从传感器中心射出的光线,光线打到物体表面后反射回来,我们只知道命中点的距离,命中点之前的空间都是“空的”,命中点之后空间是未知或被遮挡的。这种射线约束可以直接套进NeRF的体渲染公式里。经典NeRF是对相机像素的颜色做积分,LiDAR场景则可以对“这个深度位置附近是不是表面”做积分,本质完全一样。
2.2 实时是怎么抠出来的
最早我看NeRF SLAM类的工作,印象都是跑一帧要几秒钟,完全没法接到机器人里。这篇工作能冠上“实时”两个字,关键在四个地方。
第一是滑动窗口,不优化全局地图,只优化当前最近的一二十帧相关的那一小块神经场。这和SLAM里局部地图维护是一个道理,把优化问题的规模从“整个环境”缩小到“当前局部可见区域”,计算量就能降下来。
第二是哈希编码。原始NeRF用纯MLP把坐标映射成特征,网络很重,反向传播也慢。这篇工作采用多分辨率哈希网格,把空间划分成多级格子,每级格子存一组可学习的特征向量,查询坐标时只在所在格子做插值。网络本体只剩一个很小的MLP,大部分信息都落在哈希表里。实测下来,训练和推理速度能比纯MLP快一个数量级,这也是能不能实时的主要胜负手。
第三是空区域跳过。纯体渲染会在射线路径上采上百个点,其中大量点在空区域,计算浪费。作者的做法是先建一层粗分辨率占据网格,射线经过时先查这个网格,遇到确定为空的地方直接跳过,命中表面附近的区域才细采样。
第四是GPU并行。激光雷达一帧通常有几万到几十万点,如果逐一串行处理肯定实时不了。源码里把射线组织成批次,每个CUDA线程处理固定数量的采样点,我在正式环境下测到的整体链路已经能跑到20Hz以上,完全满足移动机器人低速场景的需求。
2.3 为什么是LiDAR而不是相机图
相机NeRF SLAM现在已经有不少工作了,但激光雷达版本有它独特的价值。相机图像受光照、纹理、反光影响很大,黑暗中完全没法工作,而LiDAR主动发射激光,不依赖环境光照。LiDAR深度是直接测量出来的,噪声模型相对简单,射线终点的不确定性远小于多视角三角化。
更重要的是,LiDAR点云虽然稀疏,但每一点的距离都是高精度的,这一点正好补上了NeRF在“绝对尺度”上的短板。纯相机NeRF存在尺度模糊问题,一个平面放大两倍也能渲染出同样的图;LiDAR的深度测量直接锁死了尺度,联合优化时不容易飘。我拿低纹理地下车库的数据测试过,灯光全暗、白墙一大片的情况下,传统视觉或者LiDAR配准都可能退化的场景,这套方法反而靠多帧神经场拟合把墙面的平面连续约束挂住了。
3. 核心模块逐段拆解:从点云到神经场的完整链路
3.1 前端:帧间配准与畸变补偿
任何一上来就搞神经场联合优化的方案都要面临一个现实问题:神经场是非凸的,初始位姿太差,梯度反向传播直接把你带沟里去。所以这套系统的前端仍然是经典手段:先把原始点云去畸变,再做一次轻量级帧到局部地图的ICP变体,把每帧位姿推进到“大致能用”的精度,再去喂神经优化模块。
去畸变这块,如果输入源是机械式旋转雷达,比如Velodyne或禾赛,一帧点云是几十毫秒内扫描出来的,平台在动,同一个物体在帧头和帧尾会被扫到两个不同位置。作者的做法是接入IMU的角速度和线加速度做补偿,前端的畸变残差会小很多。没有IMU的数据集也能跑,只是精度会掉一截,所以这套系统的定位更偏向“LiDAR+IMU”的紧耦合系统。
前端配准我在复现时最关注的点是,它没有用特别花哨的特征,而是先把空间体素化,在体素里估算平面法向量,然后用点对平面的残差做迭代最近点。这一步和十几年前的LOAM思路一脉相承,好处是稳定、鲁棒、快。后续神经优化再厉害,也架不住这里给个五米漂移的初始值,所以这一块一定不能省。
3.2 神经隐式映射模块
这个模块是整个系统的核心,干的事是把滑动窗口内的激光射线输入到一个哈希编码网络里,输出对局部场景的隐式表面场。原作者没有用经典NeRF的那种“密度+颜色”输出,而是改成了SDF(有向距离场)的形式:空间每个点输出一个标量,正值表示点在表面外部,负值在内部,零点穿过的等值面就是激光看到的表面。
选SDF而不是密度,很符合LiDAR数据特性。激光雷达命中表面时,我们几乎确定那个点就在表面上;如果用密度场,需要靠网络自己学会在命中点附近形成高密度带,浪费大量表达力。等值面表示则直接告诉网络“零点这里就是表面”,几何约束更硬,优化起来也更收敛。
射线采样也做了改造。每条射线沿路径采样若干个点,靠近命中点的地方加密,远处可以稀疏一点。框架里有一个粗采样和细采样的两段式过程:粗采样均匀取几十个点,找到表面大致区域后,在表面附近再细采样几十个点。这么做的原因很简单:如果全路径等都密采样,一帧几十万个点会直接把GPU显存塞爆。
3.3 位姿优化与后端
有了神经场残差,接下来就是把这个残差和位姿绑在一起做优化。系统里维护了一个滑动窗口的因子图,窗口里包含最近N个关键帧的位姿、IMU预积分因子和神经场残差因子。神经场残差因子形式上和视觉SLAM的重投影误差很像,只不过把“像素坐标差”换成了“射线终点处的隐式场值”。
因子图的求解并不是一次全部迭代完。源码里做了两层循环:内层循环固定位姿来快速更新神经场参数,让场先适应当前最优的几何;外层循环固定神经场,再对窗口内每个位姿做少量更新。这两层交替进行,有点类似Bundle Adjustment里“先重投影更新地图,再更新相机”的分块思想。实测下来,这种交替比一起优化要稳得多,也容易控制学习率。
重定位和回环在开源版本里做得比较克制,没有传统SLAM里那么完整的词袋闭环检测,更多是依赖里程计因子和局部神经场来做长期一致性。如果你要做大楼层闭环,建议在这个模块上自己加一路回环检测,把回环帧当成普通关键帧丢进窗口,神经场会给你要的对齐约束。
3.4 外参与IMU标定
这个系统对LiDAR和IMU之间的外参非常敏感。外参给错一点点,激光点云去畸变和IMU预积分都会对不上,神经场优化出来的地图会有明显双层影。我踩过的坑就是一开始拿了一个近似外参跑,里程计在直线行驶还能凑合,一转圈就开始一层层“剥皮”。
这里有必要提一下热词里经常看到的LiDAR-IMU标定。常见的做法是用开源工具先做离线标定,比如把LiDAR和IMU放在一起做多组特定轨迹运动,通过连续时间优化解出旋转和平移外参。切记标定过程不要在一个纯平面上缓慢挪动,那样旋转外参的可观性不够,解出来的一堆数字看着很平滑,实际用起来漏洞百出。标定完成后,最好再做一个让机器人原地转几圈的快速验证,观察点云是否出现明显的分层。
时间同步也是要命的细节。LiDAR帧的时间戳和IMU数据时间戳如果不严格对齐,即使外参精确,畸变补偿也会引入误差。建议录数据时直接把两个传感器时间源同步到同一个主时钟,不要依赖软件里东拼西凑的时间戳。这一步做扎实了,后面的神经优化才有意义。
3.5 损失函数设计
损失函数是整个深度学习系统设计中最能看出门道的地方。这套系统的损失函数主要由三项构成。
第一项是几何命中残差:每条射线打到物体表面上的那个点,我们希望神经场在该点的SDF值等于零,这是最基本的表面约束。第二项是自由空间约束:从传感器沿射线到命中点之间的采样点,必须保证SDF为正值且离表面有一定距离,否则会出现“一堵看不见但挡住了射线”的假表面,直接导致建图出现虚影。第三项是Eikonal正则:这是让SDF满足梯度模长恒为1的约束,本质上保证SDF的等值面是规则可导的,不会出现曲面扭曲、梯度爆炸的问题。
三项损失的权重在源码里分别有默认值,我的经验是自由空间约束的权重不要开太大,不然网络倾向于把所有空间都推成“空”,房间角落里的细小物体容易被抹掉。Eikonal正则权重在早期可以开高一点,先把场的数学性质稳住,后期再逐渐降下来,让表面细节出来。这个顺序和调参手感,比任何“抄作业”式的固定参数都重要。
4. 源码复现全记录:环境、编译、跑通demo
4.1 环境与依赖准备
既然是开源项目,复现的第一步就是把环境凑齐。这套系统的依赖树比较复杂,我建议按官方README顺序来,不要跳步。操作系统我实测过Ubuntu 20.04和22.04两个版本,都可以跑通,但ROS版本不一样,底层编译依赖也会有差异。20.04配ROS Noetic是最稳的,社区教程多,遇到问题也好搜。
GPU驱动和CUDA是第一个坎。源码里的底层算子依赖CUDA,编译之前先确认一下nvidia-smi和nvcc -V能不能对上。很多人在这一步翻车,是因为nvidia-smi显示的驱动最高支持的CUDA版本和当前安装的CUDA Toolkit版本不一致。建议直接装和PyTorch匹配的CUDA版本,再用export PATH=/usr/local/cuda/bin:$PATH把环境指向统一,避免编译C++扩展时链接到错误的库。
除了CUDA,还需要装PCL、Eigen、GTSAM、OpenCV这些SLAM常见库,以及PyTorch的C++前端。不要手动一个个编译,能用 apt 装的就用 apt 装,省下的时间可以拿去做别的。环境变量和依赖列表整理如下:
# 依赖安装(Ubuntu 20.04 + ROS Noetic) sudo apt update sudo apt install ros-noetic-pcl-ros ros-noetic-laser-geometry \ libeigen3-dev libopencv-dev libgtsam-dev # 请根据官方仓库要求核对版本,老版本GTSAM可能出现API不兼容4.2 编译与ROS工作空间
代码建议直接放在catkin工作空间的src目录里。编译的时候我用的是catkin build而不是catkin_make,原因是这个项目包含多个子包,有Python和C++混编,增量编译时catkin build的隔离性更好,不会出现一个包编译失败导致整个工作空间被清掉的情况。
首次编译跑几十个警告是正常的,但如果有报错,优先检查三类问题:一是CUDA路径,二是OpenCV和PCL的版本冲突,三是GTSAM自带依赖和ROS自带依赖冲突。如果报错信息里出现“undefined reference”这种链接错误,八成是GTSAM或者OpenCV编译时用了不同的ABI开关,重新用统一编译选项编译即可。
# 编译 mkdir -p ~/catkin_ws/src && cd ~/catkin_ws/src git clone <项目仓库地址> cd ~/catkin_ws/ catkin build source devel/setup.bash4.3 数据集准备与启动
没有实车硬件的人,可以直接找公开的LiDAR惯性数据集。我自己用的是类似Newer College那种带GT轨迹的地下停车场和校园数据,里面包含机械式LiDAR和IMU,格式上要转换成ROS bag。
转换完bag之后,启动命令非常直接,把bag路径传进去就行:
# 播放数据包 rosbag play -l your_dataset.bag # 另开终端启动建图 roslaunch <项目名> mapping.launch bagfile:=your_dataset.bag启动以后你会在RViz里看到点云不断累积,神经场对应的一个显示层会慢慢变成半透明的连续曲面。如果CPU占用率高而GPU占用率低,说明采集频率和神经网络推理频率不匹配,后排点云可能被前端挡住积压。如果GPU占用率满了但轨迹还在飘,问题多半出在前端配准,不要再纠结神经模块参数,回头看3.1节的内容。
4.4 实时性怎么测
评价SLAM系统实时性,嘴说没用,得量化。在跑bag时我习惯看三个指标:里程计输出频率、单帧处理总延迟、GPU占用。第一个直接用rostopic hz /odom看,第二个可以在源码日志里找到前端耗时和神经优化耗时,第三个就是nvidia-smi。
我实测下来,在RTX 3080笔记本上,前端配准约10毫秒,神经优化约30到50毫秒,整体能到20Hz左右。如果你的显卡差一些,先把采集频率调低一档,或者把神经优化的迭代次数减半,实时性立刻会上来,精度损失在低速场景可以接受。优质的话,可以先把bag按0.5倍速播放,观察系统能否跟上,再逐步加速到1倍,这个做法比口算“理论帧率”靠谱得多。
5. 我踩过的坑与调参心得
5.1 常见编译与运行问题速查
复现过程中我遇到了不少问题,整理成一张速查表,照着查能省不少功夫。
| 现象 | 根因 | 解决办法 |
|---|---|---|
| 编译C++时找不到CUDA | CMAKE_CUDA_ARCHITECTURES没设置 | 在CMakeLists里加上你的显卡算力,比如8.6对应RTX 30系 |
| 运行时一启动就崩溃 | 外参矩阵没初始化 | 检查yaml配置,确保LiDAR到IMU的外参四元数带w轴 |
| 地图出现平行双层影 | LiDAR和IMU外参不准 | 重新做离线标定,并校准时间同步 |
| 神经优化loss徘徊不降 | 滑动窗口内帧数太少或场景重复度过高 | 增大窗口N值,或调低前端Voxel尺寸增加约束密度 |
| GPU显存溢出 | 单帧点太多、采样点密度太高 | 降低输入点云频率,或减少每条射线的细采样数量 |
[error] query livox lidar fw type failed, the status:-4 | Livox驱动和硬件连接问题 | 更新livox_ros_driver版本,确认USB权限和线缆,多设备时检查SN配置 |
| 长期运行轨迹缓慢漂移 | 缺少回环检测 | 用额外回环模块加入关键帧约束,或定期用全局配准纠正 |
这里特别说一下Livox那个错误。很多人买的是Livox非重复扫描雷达,跑官方驱动时偶尔会报status:-4,第一次遇到还以为雷达坏了。这个错误本质上就是驱动没有从设备读到固件类型,和SLAM算法本身没关系。优先查USB线缆是不是插在3.0口上、有没有上电时序问题、驱动版本和设备固件版本是否匹配。好在这一步不依赖神经优化,排查完驱动问题,剩下的流程照常。
5.2 影响精度和速度的几个关键参数
调参是这个系统最有意思也最折磨人的部分。我先给一个默认参数的理解框架,再讲怎么调。
| 参数 | 影响内容 | 参考范围 | 我的经验 |
|---|---|---|---|
| 前端体素分辨率 | 配准稳定性和细节保留 | 0.2m~1.0m | 越大越稳,越小越精细,动态场景优先大 |
| 滑动窗口大小N | 局部一致性和计算量 | 10~40帧 | 环境越简单,N可以越小,环境复杂建议20以上 |
| 神经场细化迭代次数 | 建图精度和耗时 | 30~100次 | 追求实时选30,追求精度选100 |
| 哈希网格分辨率 | 地图精度和显存 | 16级~32级 | 分辨率越高显存越大,精度收益会饱和 |
| 自由空间损失权重 | 抑制假表面 | 0.01~0.5 | 太强细节丢失,太弱出现虚影 |
| Eikonal正则权重 | SDF的稳定性 | 0.01~0.1 | 早期调大,后期调小 |
| 学习率 | 收敛速度 | 1e-3~1e-2 | 太大loss震荡,太小窗口跟不上运动 |
拿体素分辨率举例。如果把前端体素设到0.2m,帧间约束会很密,但长走廊这种场景里,大量点落在同一面墙上,平面拟合噪声反而大。把体素调到0.5m后,每个体素包含的点更多,法向量估计更稳定,轨迹反而更准。体素不是越小越好,这个概念和栅格地图分辨率“越大越清楚”是不一样的使用逻辑,刚上手的朋友特别容易在这里钻牛角尖。
5.3 硬件选择建议
想真正在这套系统上做开发,硬件别太寒酸。LiDAR方面,我推荐机械式32线或64线,因为视场角大、点云均匀,神经场更容易拟合出完整的表面。Livox系列胜在性价比,但它是非重复扫描,单帧点的分布不均匀,需要前端多做时间累积才能稳定,调节起来门槛更高。
GPU是真正的瓶颈。官方仓库标注的最低配置我没有实测过,但按我的经验,显存低于6GB基本只能跑很空的数据集,窗口稍微一拉大就爆显存。建议直接上8GB或更高内存的卡,整个复现体验会顺很多。CPU不需要特别夸张,4核以上就行,原因是重计算基本都在GPU上。内存建议32GB,加载bag和建图缓存都比较从容。
硬盘一定用NVMe固态,别让机械盘的读取速度成为bag播放的瓶颈。我最早用机械盘跑数据,前端线程经常因为等待点云数据出现间隔抖动,肉眼看不出来,但对神经场优化来说,不稳定的输入频率很容易导致loss曲线震荡。
6. 这个方向还能怎么玩
6.1 适合落地的应用场景
这套方案最适合的场景,是那些传统LiDAR配准会退化、但细节又要求比较高的低速机器人任务。地下矿山巡检是典型例子:长巷道、少特征、光照全黑,传统方法沿巷道方向极易漂移,而神经隐式场用连续曲面约束,能在很大程度上压住这个方向的自由度。
室内服务机器人也可以从中受益。家居环境的墙角和边缘,单帧激光点往往扫不全,神经场用多帧积分能补出比较完整的家具轮廓。如果后端再接一层语义分割,把神经场的隐式表面直接用于路径规划,机器人沿墙贴边行走的精度会比传统代价地图更好。
做高精地图的团队也可以关注这个思路。传统的点云地图动辄几百兆甚至几个G,神经场地图存储下来的模型很小,而且查任意一个坐标的占据情况都是连续可导的。这个特性对地图更新、多源融合、局部路网生成都很有价值。当然,离产品化还有距离,但作为技术预研方向已经足够引起重视。
6.2 当前的边界与后续值得做的方向
也得泼盆冷水。这套系统目前还做不到在机载嵌入式平台上实时跑,GPU功耗和体积是硬门槛。动态环境是另一个大问题,行人、车辆这些移动物体如果被直接喂进神经场,表面场会被“抹糊”,像长曝光照片那样拖出尾巴。目前的处理办法更多是依赖去动态点预处理,而不是在算法层面解决。
室外大场景仍然有挑战。滑动窗口保证局部一致性,但长距离跑下来,累积误差还是需要回环检测来兜底。作者开源版本在这块相对薄弱,建议使用者把回环检测加进来,或者对接现有的全局配准框架。
我觉得后续值得做的方向有三个:一是把IMU预积分因子更深地耦合进神经场优化,现在的外参校正和预积分相对独立,紧耦合之后小转角场景的鲁棒性会更好;二是加语义标签,让神经场输出不止是表面,还有语义类别,这样地图语义信息可以直接进入规划模块;三是做多源融合,把毫米波雷达、超声甚至轮速里程计都放进同一个因子图里,在极端退化场景下通过多冗余提升可靠性。
我个人在实际操作中的体会是,这类项目想跑出效果,千万别直奔默认参数然后期待奇迹。先把前端配准和外参标定这两件事做扎实,再开神经优化,一组一组加权重,观察loss形态和点云叠加质量,每一档改动都做对比记录。我大概花了三天时间才找到适合自己数据集的参数组合,但换来的稳定性是直接跑默认配置比不了的。如果你手上正好有LiDAR惯性数据,把这个项目拉下来跑一遍,大概率也会和我一样,对“隐式表示遇上SLAM”这件事产生新的判断。