你已经学过 BEVFormer:BEV 格子主动去图像里查询。这一篇走的是完全相反的方向——让每个像素自己"猜"深度,把特征推到 3D,再压成 BEV。理解了它,你才算真正看懂 BEV 感知。
你学 BEVFormer 时已经彻底理解了它的做法:BEV 网格上的每个格子拿着自己的 3D 坐标,用相机标定参数反投影到图像上,去采样特征。方向是 BEV → 图像,是"拉取"(pull)。
LSS 比 BEVFormer 早两年,走的是相反方向:从图像出发,让每个像素预测自己有多深,把特征"推"到 3D 空间,再拍扁成 BEV。方向是 图像 → BEV,是"推送"(push)。
它们不是"新旧替代"关系,而是两条并行发展、各有优劣的技术路线,今天都在量产中大量使用。LSS 这一派后来演化出 BEVDet、BEVDepth、BEVFusion;BEVFormer 这一派演化出 BEVFormer v2 等。只懂一派,等于只看了半张地图。
这是我见过命名最诚实的论文——三个词就是三个步骤,读完标题你已经知道它在干什么了。
这一节是整篇论文的灵魂。理解了这里,LSS 就通了。
一张普通照片里,某个像素对应现实中多远的位置?单张 2D 图像在数学上无法确定深度——同一个像素,可能是 5 米外的小物体,也可能是 50 米外的大物体,投影到图像上一模一样。这是几何上的根本歧义,不是算法不够好。
LSS 要把像素特征"推"到 3D 空间,就必须知道推到多远。这就撞上了这个根本困境。
最自然的想法:让网络预测一个深度值,比如"这个像素在 12.3 米处",然后把特征放到那儿。
但这样做很脆弱:网络必须做一个硬性承诺。一旦猜错(比如实际是 20 米),特征就被放到了完全错误的位置,而且错误无法挽回——后续网络不知道这里曾经有过不确定性。
既然无法确定深度,那就不要强迫模型做选择。
让每个像素输出一个深度上的概率分布——"我有 60% 可能在 12 米,25% 在 13 米,10% 在 11 米,剩下的分散在别处"。把不确定性显式地保留下来,一路传给后续网络。
LSS 把连续的深度范围切成离散的桶。在 nuScenes 配置里是从 4 米到 45 米,每米一个桶:
# 深度范围配置 dbound = [4.0, 45.0, 1.0]
depths = [4, 5, 6, ..., 44] # 共 41 个桶(右开区间)
# 网络对每个像素输出两样东西:
c = 上下文特征 # C = 64 维,"这里是什么"
α = 深度分布 # D = 41 维,softmax 后和为 1,"这里有多远"
然后做一个外积——这是 Lift 这一步的核心运算:
# 外积: 41 个深度 × 64 维特征
点云特征 = α ⊗ c # shape: [41, 64]
# 含义: 沿这条射线摆 41 个点,第 d 个点的特征是 α_d × c
# 概率高的位置,特征强;概率低的位置,特征弱(但不是零)
第 8 个点(12米处): 0.60 × c # 主要能量在这
第 9 个点(13米处): 0.25 × c
第 7 个点(11米处): 0.10 × c
其余 38 个点: 很小 × c # 微弱但存在
普通深度估计像用图钉钉一个点——必须选一个位置,钉错了就全错。
LSS 像用喷雾罐沿着射线喷一道渐变——最可能的位置喷得最浓,周围渐淡,远处极淡但也有。不确定性被完整地保留在浓淡分布里,后续网络自己决定怎么用。
# 单个相机(图像经骨干网络下采样 16 倍后)
特征图尺寸: 8 × 22 # 128÷16, 352÷16
每个位置摊开 41 个深度点
→ 单相机点数 = 41 × 8 × 22 = 7,216
# 6 个相机
总点数 = 7216 × 6 = 43,296 个 3D 点
# 每个点带 64 维特征 —— 这就是"隐式反投影"生成的伪点云
注意论文标题的副标题里那个词:Implicitly Unprojecting to 3D(隐式地反投影到 3D)。"隐式"就是指不做硬性深度决策,而是用概率分布软性地铺开。
整条路线的质量完全取决于那个深度分布 α 预测得准不准。而这个分布在原始 LSS 里没有任何直接监督——它只能通过最终的检测/分割损失间接学习。
后来 BEVDepth 那篇论文专门指出:LSS 学到的深度其实相当不准。这个问题怎么解决,第 7 节讲。
上一节讲的"概率分布 + 外积"比较抽象,这个演示让你直接看到。点击图像上任意位置,下面会显示这个像素预测出的 41 个深度桶的概率分布,以及它沿射线摊开后的样子。
① 点车辆——分布会很尖锐,集中在一两个桶上。物体边界清晰、有纹理,模型对深度很有把握。
② 点空白路面——分布会宽而平。没有纹理线索,模型说不准是 15 米还是 25 米,于是把概率摊开。这正是保留不确定性的价值。
③ 点天空——概率全堆在最远端。天空无限远,模型学会了把它推到深度范围的尽头。
看完这个演示,回头再看"外积"就非常具象了:右上那张柱状图的每一根柱子,乘上这个像素的 64 维特征,就变成射线上一个 3D 点。柱子高的地方点亮,柱子矮的地方点暗。41 根柱子 = 射线上 41 个点。
Lift 之后手上有 4 万多个带特征的 3D 点。Splat 要把它们塞进 200×200 的 BEV 网格。
Lift 阶段的点是在相机坐标系的射线上("沿着这个方向 12 米处")。用相机内参和外参,换算成车身坐标系的 (x, y, z)。
这一步的数学和你学 BEVFormer 时的投影完全一样,只是方向相反:BEVFormer 是 3D→2D 找像素,这里是 2D+深度→3D 定位置。
BEV 网格覆盖车周围 -50m 到 +50m,格子边长 0.5m,所以是 200 × 200。用 (x, y) 除以格子大小取整,就知道落在第几行第几列。z 方向不分层——同一个 (x,y) 上不同高度的点,全归到同一个"柱子"(pillar)里。
一个格子里可能落进几十个点(来自不同相机、不同深度桶)。把它们的 64 维特征直接相加,得到这个格子的最终特征。
为什么用求和而不是取最大或平均?因为求和天然满足排列不变性——点的顺序不影响结果,相机的顺序也不影响结果。这个性质后面会讲为什么重要。
得到 200 × 200 × 64 的 BEV 特征图后,用一个普通的 CNN 进一步提特征,最后接检测头或分割头。
STEP 3 的"把落在同一格的点求和"听起来简单,但在 GPU 上高效实现并不容易——每个格子里的点数不一样多,没法直接开一个固定大小的张量。
笨办法是补零到最大点数,但那样浪费大量显存。LSS 用了一个很巧的做法:
# 累积求和技巧 (cumsum trick)
1. 把所有点按「所属格子编号」排序
→ 同一格子的点自然排在一起
2. 对整个序列做累积求和 cumsum
→ 得到前缀和
3. 在每个格子的边界处相减
→ 差值就是该格子内所有点的和
# 全程不需要补零、不需要知道每格有多少点
# 论文里把这一步写成了自定义的可导算子
这是典型的部署工程思维——算法上"求和"一句话的事,落到 GPU 上却是性能瓶颈,需要专门设计数据结构和算子。
你 Project 3 第 3 章要手写 TensorRT plugin、第 5 章要写 CUDA 体素化 kernel,遇到的正是同一类问题。LSS 这个 cumsum trick 是一个很好的范本:先想清楚数学上等价的变形,再让它对硬件友好。面试聊部署优化时,这是个能加分的例子。
因为最后是无序求和,LSS 天然具备几个好性质:
论文标题里那句 "from Arbitrary Camera Rigs"(来自任意相机组合)说的就是这个。对车企来说这个性质非常实用——不同车型摄像头布局不同,一套模型能通用,省下巨大的适配成本。
前两步是感知,这一步是规划。做法直白得有点复古,但思路很值得理解。
LSS 不去回归一条连续轨迹,而是:
像在地图上铺一叠透明胶片,每张画着一条路线。把它们逐一盖到"危险热力图"上,看哪条路线穿过的红色区域最少——选那条走。
这个设计的好处是整个流程端到端可导:规划的好坏可以反向传播回感知,逼着 BEV 特征学出"对规划有用"的表示,而不只是"检测得准"的表示。
"让感知为规划服务、端到端联合优化"——这正是你学过的 UniAD 的核心哲学(Planning-oriented)。LSS 在 2020 年就埋下了这颗种子,只是当时的规划模块还很简单(模板打分)。
今天 Shoot 这一步基本被更强的方案取代了(UniAD 的 Planner、VAD 的矢量化规划、端到端 VLA 直接出动作),但 Lift + Splat 这两步仍在大量量产系统里跑着。
现在你两派都懂了,把它们放在一起对比。这一节是本文对你求职最有价值的部分。
| 维度 | LSS 派(推送) | BEVFormer 派(拉取) |
|---|---|---|
| 投影方向 | 图像 → BEV(forward) | BEV → 图像(backward) |
| 谁主动 | 像素主动把特征推出去 | BEV 格子主动去图像查询 |
| 深度处理 | 显式预测深度分布 | 不预测深度,靠注意力隐式解决 |
| 核心运算 | 外积 + 体素池化(求和) | 可变形注意力(加权采样) |
| 时序融合 | 原版没有,后续版本加 | 原生支持(时序自注意力) |
| 最大优势 | 结构简单、CUDA 友好、对相机配置鲁棒 | 无需深度监督、时序天然、精度上限高 |
| 最大软肋 | 深度预测不准就全盘皆输 | 注意力计算重、部署优化难 |
| 部署难度 | 相对友好(算子固定) | 较难(deformable attention 要写插件) |
| 后续代表作 | BEVDet、BEVDepth、BEVFusion | BEVFormer v2、DETR3D 系 |
"LSS 是从图像往 BEV 推,必须显式预测深度分布;BEVFormer 是从 BEV 往图像拉,用注意力绕开了深度估计。前者简单高效但受制于深度精度,后者精度上限高但部署更重。"
能补一句就更好:"所以 LSS 一派后来的主要工作方向是怎么把深度学准(BEVDepth 加激光雷达深度监督),BEVFormer 一派的主要方向是怎么让注意力更高效。"
很多人以为"BEVFormer 更新所以更好"。不是这样。两派各有战场:
工程选型看约束,不看论文发表年份。这个判断力本身就是资深工程师和新手的区别。
LSS 开了路,但它的深度分布没有直接监督,学得并不好。后续工作基本都围绕这一点展开。
| 工作 | 解决了什么 | 关键改动 |
|---|---|---|
| BEVDet | 把 LSS 做成一个完整的 3D 检测框架 | 工程化:更强骨干 + BEV 数据增强 + 检测头 |
| BEVDepth | 直接指出 LSS 的深度学得很差 | 用激光雷达点云投影到图像,给深度分布加显式监督 |
| BEVStereo | 单目深度歧义无法根除 | 引入多帧之间的时序立体匹配提升深度 |
| BEVFusion | 纯视觉深度终究不如直接测 | 相机 BEV 与激光雷达 BEV 在统一空间融合 |
BEVDepth 团队做了个很犀利的实验:把 LSS 学到的深度换成随机值,检测性能居然没掉太多——这说明原始 LSS 里那个深度分布并没有学到真正准确的几何,模型是靠别的方式勉强工作的。
加上激光雷达深度监督之后,性能大幅提升。这个故事告诉我们:一个模块"存在"不等于它"有效",要有实验去证伪。这种批判性思维是读论文时最该学的东西。
你 Project 3 大纲里 Ch6 是"Camera + LiDAR 多传感器融合"。BEVFusion 就是这一章的学术原型——相机走 LSS 路线变成 BEV 特征,激光雷达走体素化变成 BEV 特征,两者在同一个 BEV 空间拼接。
你之前在 BEVFormer 讲解里学过的"BEV 是传感器无关的统一表示",在这里得到了最直接的印证。
把这三篇串起来看,脉络就非常清楚了:
建立了 Q/K/V 查询-匹配-聚合的范式。一切的地基。
证明了只要能 token 化,Transformer 通吃所有模态。打通了视觉这一侧。
不用 Transformer,用显式深度分布 + 体素池化把多相机融进 BEV。证明了 BEV 这个表示本身的价值。
把前三者合流:用 Transformer 的交叉注意力替代显式深度,用时序自注意力加入历史。你最早学的那一篇,其实是这条线的汇合点。
LSS 回答了"BEV 该长什么样",Transformer + ViT 提供了"更强的工具",BEVFormer 是"用新工具重做 BEV"。
关键词:单目深度存在根本歧义、硬性承诺不可挽回、保留不确定性交给后续网络。
特征 c[64] ⊗ 深度分布 α[41] → 点云[41,64],单相机 41×8×22=7216 点。
推送 vs 拉取、显式深度 vs 注意力隐式。这是面试最高频的 BEV 对比题。
变长分组求和在 GPU 上的高效实现。聊部署优化时的好素材。
下一篇按清单是 UniAD(CVPR 2023 最佳论文)——你 Project 2 学的就是它,读原文能把之前所有零散理解收拢成一个整体。或者如果你想先补占据网络那一块,也可以看 Tesla AI Day 的占据网络部分。