Lift-Splat-Shoot解析:多相机BEV感知的隐式逆投影与端到端路径规划

📅 发布时间:2026/10/19 2:35:53
Lift-Splat-Shoot解析:多相机BEV感知的隐式逆投影与端到端路径规划
我第一次读这篇论文时最直接的反应是原来多相机感知还能这么搞。六路相机拍回来的图像不再是各自做2D检测再硬凑到俯视图上而是把每一个像素沿着相机射线“举到”三维空间再“拍扁”到地面栅格里——这正是标题里的 Lift 和 Splat。而 Shoot 则是最后用一条路径穿过这张 BEV 地图把规划的目标直接接回感知网络形成端到端的训练闭环。标题其实已经把整个架构说完了。这篇工作提出了一种“隐式逆投影”的思路。与其先估一个精确深度、再往3D空间反投影不如让网络给每个像素在所有离散深度上同时留下特征把“深度估计”这件事融进端到端学习里。这篇论文发布之后很快成了BEV感知绕不开的baseline直到今天很多后续工作的对比表格里仍然能看到 Lift-Splat 的影子。如果你做自动驾驶多传感器融合、视觉感知、或者纯视觉BEV建图这篇论文值得反复读。下面我从原理、实现到复现心得一层层拆开聊。1. 拉开的序幕多相机融合为什么这么难1.1 几何逆投影的两条老路各自卡在哪几年前业界做多相机鸟瞰感知最常规的做法是“图像坐标到地面坐标”的几何投影。第一条路是 IPM逆透视映射假设地面是一个平面用单应性矩阵把像素铺到俯视图上。这个办法在平整的停车场里看着还行一旦遇到坡道、路拱、桥面地面不再是平面像素就会开始“撕裂”。车身上的立体障碍物更是麻烦——一辆和你并排的车在IPM里会被拉成一长条影子后续检测根本没法用。第二条路是先做单目深度估计拿到每个像素的深度然后反投影成3D点云最后落到BEV栅格里。这条路的瓶颈在深度估计本身。单目深度天生是病态问题同一块路面纹理远处和近处在深度上相差几十倍网络稍微犹豫一下远处的车辆就可能错位几米。而且这种“深度估计→反投影”是两条独立管线前一步的误差会直接传导到后一步很难端到端修正。LSS的转变在于它不做“先猜一个精确深度再硬投影”而是让每个像素在所有离散深度槽上都保留一份特征由网络自己决定每个深度上“放多少置信度”。这就是标题里 implicitly unprojecting隐式逆投影的含义。它放弃了“我一定要在某一个深度上做对”这条思路改成“我把特征沿光线铺开后续的拍扁操作会替我选出有用部分”。1.2 统一坐标系所有相机先放到一个“自车参考系”里多相机融合本质上要回答一个问题不同相机里的同一个像素到底分别对应自车周围的哪个位置答案依赖内外参。LSS的做法是把所有相机放在以自车为中心的局部坐标系里每个相机的图像特征先沿着各自的光线抬到3D再用外参统一变换到自车坐标系最后落到同一个BEV栅格上。这个选择很朴素但它解决了工程里的一个关键痛点不需要手动拼接多路相机的视野也不需要判断哪些像素重叠。网络只需要对“自车坐标系下的BEV栅格”负责所有相机都往这个栅格里填特征重叠区域自然累积空隙区域自然空缺。你回头看很多后续BEV感知工作它们虽然换了更花哨的注意力机制、更复杂的深度网络但这个“所有相机映射到统一BEV坐标”的大前提一直没变。2. Lift把图像像素沿着光线铺进三维空间2.1 前向网络的两个头上下文与深度分布Lift阶段的前向过程很简单。输入是若干张图像先过一个图像编码器常见的是 EfficientNet 这类主干得到分辨率降低后的特征图尺寸是 H×W×C。然后这个特征图会被送进两个分支上下文分支context head输出 C 维语义特征表示“这个像素是什么”深度分支depth head输出 D 维向量经过 softmax 之后表示“这个像素在不同深度上的概率分布”。这两个输出做外积每个像素得到 D×C 的张量。用代码视角看这就是把高度为 H、宽度为 W 的特征图扩展成 D×H×W×C 的特征体。每一份语义特征都被复制到了 D 个深度槽位上每个槽位再用对应的概率加权。这个“复制→加权”的操作用爱因斯坦求和就能写清楚但直觉上更好理解网络把每个像素“不确定地”放进了三维空间中沿光线的一串位置而不是强行选一个点。2.2 为什么是“软深度”而不是回归一个深度值我见过很多第一次接触LSS的人会问为什么不直接让网络回归深度值答案在训练和推理的不同阶段都有体现。训练阶段深度真值往往来自激光雷达投影稀疏且带噪声。如果让网络回归一个精确深度标签里的噪声会被放大而把深度做成 D 类的分类问题天然对噪声更宽容网络只需把质量放到正确的槽位附近就行。推理阶段相机视角里存在大量“其实分不清远近”的像素比如雨雾、暗光、透明挡风玻璃后面的物体。硬回归会把所有不确定性压缩到一个点上而软分布允许网络同时在两个可能的深度上都留下响应把最终裁决交给后续的聚合过程。这有点像你在黑房间里摸开关与其赌它一定在某个位置不如把整个墙面都摸一遍虽然浪费一些动作但不会漏掉。LSS的 Lift 就是在“摸墙”。2.3 相机标定如何进入计算图很多人在读论文时忽略了一个工程细节外积完成后特征体里的每个点还需要从相机坐标系转换到自车坐标系。这个过程依赖内参 K 和相机外参 R、t。LSS在代码实现里通常会提前算好一个映射矩阵对每个相机、每个像素、每个深度槽位它的自车坐标系下的3D坐标是多少。前向时直接按这个坐标往BEV里填。这套做法的好处是图像编码器和深度网络只需要被优化坐标变换本身是一个固定的索引映射。但这也意味着标定质量直接决定BEV几何是否正确。我的实测经验是外参哪怕只有1到2度的偏差几十米外的物体在BEV图上就会错位1米以上。所以用LSS做工程第一项要检查的永远不是网络结构而是相机标定。3. Splat把视锥点云拍扁到地面栅格3.1 从三维点到 BEV pillarLift 阶段结束之后我们得到一堆散布在三维空间中的特征点。这些点还不能直接进入二维卷积网络因为BEV感知需要的是一张规整的俯视图。于是就有了 Splat。具体操作是把自车周围三维空间划分成固定大小的柱子pillar论文里一般使用 0.5m 分辨率的栅格自车周围 100m×100m 的范围对应 200×200 的BEV网格。每个三维点根据它的 x、y 坐标整除到对应柱子里然后把同一个柱子里所有点的特征求和。这个操作叫 sum pooling。为什么用 sum 而不是 mean关键在重叠区域。多个相机看同一个地方时那片区域的响应会出现多次如果用 mean相机数量越多响应反而被稀释用 sum多相机共识区域会自然累积出更高的响应强度更多视角看到同一个物体时特征会变得更“响亮”。这也解释了为什么LSS在相机少的时候会变弱——它不是靠几何一致性来消歧的而是靠多个视图的信息叠加来获得置信度。3.2 工程实现排序前缀和搞定 sum pooling真正在 GPU 上实现 Splat 时一个直观的做法是用 scatter_add对每个 pillar 的索引做原子累加。但原子操作在多线程争用时会拖慢速度而且显存访问不连续。我在复现时发现LSS 开源实现里的排序法更聪明。思路是把所有三维点按 pillar 索引排序然后对排序后的特征做 cumsum前缀和再在 pillar 索引变化的位置做差分一次性拿到每个 pillar 的特征总和。整个过程像这样# 伪代码排序前缀和实现 pillar sum pooling # points: (N, 3)features: (N, C) # 1. 根据 x, y 坐标量化到 pillar 索引 idx quantize(points) # (N,) # 2. 按 pillar 索引排序 order argsort(idx) sorted_idx idx[order] sorted_feats features[order] # 3. 前缀和 cumsum_feats cumsum(sorted_feats, dim0) # 4. 找到每个 pillar 的最后一个点用差分还原每个 pillar 的总和 boundary where(diff(sorted_idx) ! 0) pillar_sums diff(cumsum_feats[boundary])实现方式特点适用场景scatter_add实现简单但原子操作多显存访问不连续小规模点云、原型验证排序前缀和连续内存访问GPU利用率高几乎无锁竞争大规模相机点云、批量训练自定义CUDA核fast-BEV-pool速度最快能进一步省显存工程部署、实时推理我自己在跑 6 相机、41 个深度槽位时朴素 scatter_add 的前向时间能占整个 pipeline 的相当大比例换成排序前缀和之后体感速度明显提升。后来一些仓库把这一步写成了 fused CUDA kernel效果更好但原理还是同一套。3.3 重叠区域、遮挡和置信度问题sum pooling 固然巧妙但它也有明显的毛病当两个相机的视角差异过大时同一个物体在不同图像里的特征可能差异很大直接相加会把语义搞混。光照变化、角度变化都会影响特征的一致性。LSS在论文里没有做额外的置信度加权直接把特征相加。后续工作们看中了这个短板BEVDepth 在 Splat 之前引入深度校正用显式深度监督把深度分布校准得更准BEVFormer 则用可变形注意力在 BEV query 和图像特征之间做跨视图交互替代了简单的求和。如果你在工程里发现重叠区域的特征“糊”在一起可以尝试在相加前给每个相机的特征加一个可学习的置信度权重往往能缓解问题。4. Shoot从 BEV 表示到可微路径规划4.1 把 BEV 语义图变成一张“能开车的成本图”Shoot 是 LSS 里最容易被跳过的一节因为很多人只关心 BEV 感知结果而不关心规划闭环。但 Shoot 恰恰是这篇论文最有启发的部分它把 BEV 语义分割的输出接进了一个可微的路径规划器。做法很简单。BEV 语义概率图里每个格子都对应“这里有车”“这里有行人”“这里是道路”等类别的概率。给定一组类别对应的成本权重我们把这些概率加权求和得到一张成本图。行人和车辆格子的成本高道路格子的成本低。网络最后输出的这张成本图是平滑的意味着我们可以对它的空间坐标求梯度。4.2 轨迹栅格化与反向传播训练时我们手里有真值轨迹来自数据录制时的自车行驶路径。把轨迹“画”到成本图上——也就是对轨迹覆盖到的格子进行采样并求和——就得到这条轨迹的总成本。我们希望真值轨迹的成本尽量低理想情况下网络应该学会把障碍物放到真值轨迹以外的地方。由于轨迹只是索引整个链路是可微的成本矩阵从感知网络来轨迹采样不切断梯度所以梯度可以一路传回 BEV 特征再传回图像编码器。这就是 Shoot 名字的由来——你可以想象成在网络里“朝地图射出一条射线”。我复现时发现一个实际操作上的坑这个损失的梯度作用范围非常窄只有轨迹覆盖到的格子会影响更新。如果训练轨迹太短、覆盖格子太少感知网络的监督信号会很稀疏。解决办法一是把轨迹做得足够长二是随机采样多条候选轨迹把它们的成本一起纳入损失让网络学会“即使走不同路线也不能撞上障碍物”。4.3 端到端到底有没有必要论文里有一个非常有意思的分析当成本函数给得合理时分开训练感知网络和规划器效果和端到端差不多但当成本函数故意给得很差时端到端训练能明显缓解错误。也就是说Shoot 的真正价值不是替代下游规划器而是给感知表示施加了一个“驾驶偏置”——网络会倾向于把障碍物放到路径之外让 BEV 特征不再只是客观的“有什么东西”而是“该往哪开”的潜在答案。这也是后来很多工作讨论端到端自动驾驶时反复引用的论据感知和规划之间的接口不该只是一堆检测框。LSS把成本图作为接口等于让感知网络提前考虑了下游决策的需要。5. 实验设定与NMN评估这篇论文如何证明自己5.1 数据与配置清单LSS使用的数据集是公开的自动驾驶多模态数据集包含 6 个相机、激光雷达、毫米波雷达和高精地图。论文里几个关键配置配置项常见取值说明相机数量6前视、后视、侧视等形成 360 度覆盖深度槽位41 个范围 4m45m每隔 1m 一个槽位BEV范围100m × 100m以自车为中心BEV分辨率0.5m/格对应 200×200 栅格图像编码器EfficientNet 系列输出下采样特征图BEV编码器ResNet 或 EfficientNet处理 BEV 特征监督信号2D/3D语义分割、可选深度监督图像和BEV两个层面这里有个容易忽略的设计深度槽位从 4m 开始而不是从 0m 开始。近处的盲区通常由保险杠附近的其他传感器负责视觉这一路没必要在自车正下方浪费计算量而 45m 之外的深度槽位如果继续加密显存开销会爆炸。这个取舍在后来的 BEV 感知工作中被广泛沿用。5.2 为什么不用 IoUNMN 的设计哲学做BEV语义分割评估时最直接的想法是拿预测图与真值图算 IoU。但 LSS 作者提出了一个值得思考的问题在自动驾驶场景里所有像素点的误差真的同等重要吗NMN 是论文配套数据集上定义的新评测指标它的核心思想是重新标定错误的权重。车辆前方几米内“该看到的障碍没看到”远比远处“多预测了一个幽灵目标”更致命。NMN 会把“我们本应知道却不知道”的错误和“我们预测多了但真值里没有”的错误分开计分而不是像 IoU 那样把它们都当作对称的 FP/FN 处理。换句话说IoU 是一个通用视觉指标NMN 则是一个“驾驶安全”导向的指标。论文里几组对比实验显示光有 BEV 分割损失时模型已经能学到一部分结构加上图像监督IS和深度监督DS之后NMN 的提升非常明显。尤其深度监督等于直接把雷达点云投影到图像上生成稀疏的深度标签用二值交叉熵约束深度分布。这个操作让 Lift 阶段生成的视锥点云从一开始就落在正确的位置附近而不必靠后续的 BEV 网络慢慢摸索。5.3 图像监督和深度监督各自贡献了什么我在阅读和复现时对这两个监督的感受是图像监督解决的是“语义”问题它让图像编码器输出的 context 更有辨识度知道这是车、那是路深度监督解决的是“位置”问题它让 depth 分支输出的密度分布更锐利、更准确。两者一个管“是什么”一个管“在哪”加起来正好对应 Lift 阶段外积的两个因子。如果你在工程中只能加一个我的建议是先加深度监督。因为 BEV 几何的准确性对下游规划影响更直接而且雷达投影生成深度标签的成本很低几乎不需要额外标注。6. 复现LSS时踩过的坑以及保留的改进空间6.1 第一坑Lift 阶段的张量爆炸Lift 阶段生成的特征体非常大。以 6 个相机、41 个深度槽位、特征图尺寸 H/8×W/8、特征通道 C 为例一次前向的张量是 6×41×(H/8)×(W/8)×C。如果你直接把通道数设成 256Batch Size 设成 8显存会瞬间见底。我的建议是分步走先把特征图降到 1/16 分辨率把 BEV 通道压到 64 或 128Batch Size 从 2 开始跑通流程再逐步放大。后来社区里出现的 fast-BEV-pool 等自定义算子能把 Splat 阶段提速一个数量级但 Lift 阶段巨大的中间张量仍然是显存瓶颈这一步只能靠分辨率和通道数来平衡。6.2 第二坑数据增强必须保持“整车”同步LSS 的数据增强极其讲究。多相机图像会一起做随机缩放、旋转、翻转、裁剪和颜色抖动同时相机外参也必须同步变换BEV 真值也要做同样的平移、旋转和缩放。很多早期复现者在这里翻过车——只对图像做增强不对相机外参和 BEV 真值做同步变换结果训练出来的 BEV 图在自车附近出现各种扭曲。这个坑的根因是相机位姿变了图像内容变了但外参还停留在原地网络就会学到“图像特征和 BEV 位置之间的错误映射”。解决方式并不复杂但需要写数据管线时格外小心保证图像、外参、BEV 真值三者使用同一个几何变换矩阵。提示如果发现 BEV 分割图在自车附近出现扭曲第一件事不是调网络结构而是检查增强代码里的几何变换是否同步。6.3 第三坑坐标系约定LSS 默认的坐标系是“前 x、左 y、上 z”的自车右手系。但很多相机标定工具使用的是 OpenCV 那种“右 x、下 y、前 z”的相机坐标系。如果你从某个现成标定流程拿到的外参直接塞进 LSSBEV 图很容易出现整体翻转或错位。排查方法很土但很有效跑一次前向把 BEV 特征可视化看自车周围的形状是否合理。如果前方车辆特征跑到了后方或者左右侧的马路沿翻转了直接检查内外参变换里的坐标轴是否对齐。这种问题可能花掉你大半天但一旦理顺后面就很顺。6.4 这套思想到现在还能用吗LSS 之后出现了大量改进BEVDepth 用深度监督和显式深度校准把 Lift 阶段的深度分布做得更准BEVFormer 用可变形注意力替换了简单的 splat 求和让 BEV 查询点直接去图像里取特征Fast-BEV 则在工程速度上做了大规模优化。但很有趣的是几乎所有方案在最底层仍然没有离开“把图像特征沿光线映射到 BEV 栅格”这个内核。这也是我把这篇论文列为多相机感知“必读第一篇”的原因。它不一定在每个指标上都是最强的但它把一个原本要靠手工几何、先验假设和复杂 calibration 才能解决的问题变成了一个干净、可微、可扩展的学习管线。我自己的经验是第一次复现 LSS 时跑通之后并没有马上看到“惊艳”的 BEV 图但把数据增强和外参修好之后效果立刻上一个台阶。它给我的启发是在感知融合这件事上几何约束和网络学习不是替代关系而是乘法关系。如果现在让我给一个新项目选型我仍然会先拿这套 Lift-Splat 管线做底再根据场景往上加深度监督、图像监督以及后续的工程优化而不是一上来就上大模型。调这些细节的过程比读十篇综述都管用。