Paper · 2022-05-27 · cs.CV
BEVFusion: A Simple and Robust LiDAR-Camera Fusion Framework
BEVFusion gives the camera its own stream: image features are lifted into a bird's-eye-view grid with predicted depth and fused with a LiDAR BEV feature. The camera stream never needs LiDAR points, so the detector still predicts when LiDAR is dropped or cropped. Author-reported: 69.2 mAP and 71.8 NDS on nuScenes test.
Object detectionImage fusionBipartite matching for set-prediction lossesBird's-eye-view (BEV) representationlidar-camera-fusion3d-object-detectionbird-eye-viewautonomous-drivingrobustnesssensor-failure
arXivPDFCodeMarkdown16 min read
AI-compiled by claude-haiku-5-5 on 2026-10-18, not yet reviewed by a human. Every claim cites its source; check it when a detail matters.
In plain words
Self-driving cars find other objects by combining a camera, which sees colour and texture, with a LiDAR, which measures distances with laser light. Most earlier systems used the LiDAR points to decide where to look in the camera image. If the LiDAR misses points, for example on objects that reflect little light, or has a limited field of view, those systems have nothing to look with and stop producing useful output.
BEVFusion keeps the two sensors apart until late. The camera side takes each picture and guesses how far away each pixel is, then places the picture's content onto a top-down map of the road. The LiDAR side builds its own top-down map from its points. The two maps are stacked, mixed with a small learned layer, and sent to a standard detector. Because the camera map does not depend on LiDAR points, the system can still produce a result when the LiDAR is weak, and it does better still when both sensors are working.
TL;DR
- Two independent streams build features in the same bird's-eye-view (BEV) grid: a camera stream using Lift-Splat-Shoot-style depth lifting, and a LiDAR stream reused from PointPillars, CenterPoint or TransFusion-L §3 §3.1 §3.2.
- Fusion is one 3x3 convolution over the concatenated maps, followed by a channel gate §3.3 eq. 1 eq. 2.
- On nuScenes validation with TransFusion-L as the LiDAR stream, fusion gives 67.9 mAP against 64.9 for LiDAR alone tab. 1.
- Under simulated LiDAR loss (field-of-view limits or dropped object points), the abstract reports margins of 15.7 to 28.9 mAP points over the earlier methods, measured under those training settings; the tables give the per-setting numbers against LiDAR-only rows §4.4.1 tab. 3 tab. 4.
- Author-reported. The code is official, and several training choices differ from the paper (see Paper vs code).
Problem
Current LiDAR-camera fusion methods pick image features by projecting LiDAR points, or proposals made from them, into the image. Without a usable LiDAR point cloud these methods cannot produce a meaningful prediction §1 §2 §4.4.1. The paper lists causes such as low reflectance from object surfaces, an internal data-transfer glitch, and a LiDAR field of view below 360 degrees because of hardware limits §1. The target is 3D object detection on nuScenes, with six cameras, one LiDAR point cloud per frame and 10 classes §4.1.
Method
BEVFusion has two streams that map raw inputs to BEV features of the same spatial size, one fusion module, and a standard detection head §3 fig. 2. The camera stream has three parts: an image encoder, a view projector that lifts image features into 3D ego coordinates with a predicted depth distribution, and a BEV encoder §3.1 fig. 2. The paper uses Dual-Swin-Tiny as the image backbone, a standard FPN, and a small adaptive module (ADP) that resamples and concatenates the multi-scale FPN outputs; the ADP architecture is in app. A and fig. 5 §3.1.
- Image encoder. Multi-view images go through the backbone and FPN. The ADP refines each upsampled level before concatenation §3.1 app. A.
- View projection (LSS-style lift). A convolution predicts a depth distribution over bins and a set of context features per pixel. Their product gives a frustum of features, which is projected into the ego frame using the camera extrinsics and summed into a voxel grid §3.1.
- Spatial-to-channel. Height is folded into the channel axis instead of pooled or compressed with strided 3D convolutions, which keeps full resolution §3.1 cam_stream_lss.py.
- BEV encoder. Four 3x3 conv layers reduce the folded channels to the camera BEV width §3.1.
- LiDAR stream. Any LiDAR BEV network can be used. The paper tests PointPillars, CenterPoint and TransFusion-L, which voxelise points and apply sparse 3D convolution to reach a BEV map §3.2.
- Fusion. The two BEV maps are concatenated on channels. A 3x3 conv reduces the channels to the LiDAR width, and a channel gate from global average pooling, a linear map and a sigmoid rescales them §3.3 eq. 1 eq. 2.
- Head. The fused map goes to one of three detection heads: anchor-based (PointPillars), anchor-free (CenterPoint) or transformer-based (TransFusion-L) §3.4.
At inference the camera stream takes images and the camera-to-LiDAR matrices only; the LiDAR points enter through the LiDAR stream and the fusion module §3 §3.1. In the PointPillars training config, projected LiDAR depth also supervises the camera depth during training (see Paper vs code).
# BEVFusion forward pass, TransFusion-L variant [§3] [eq. 1] [eq. 2]
# Input: images (B, N, 3, 448, 800), N = 6; points: list of (P_b, 5)
# lidar2img: (B, N, 4, 4), matrices for the original 1600x900 frame
# Output: per-object class scores and 3D boxes; no NMS at test time
def forward(images, points, lidar2img):
# Image backbone (Dual-Swin-T), FPN and ADP [§3.1] [app. A]
image_feats = fpn_adp(dual_swin_t(images)) # (B*N, 256, 112, 200)
# Depth: softmax over 41 bins, 4 m to 44 m in 1 m steps [§3.1]
# [code: mmdet3d/models/detectors/cam_stream_lss.py#L130-L141]
# [code: mmdet3d/models/detectors/bevf_faster_rcnn.py#L31]
depth_probs = softmax(depth_conv(image_feats), dim=depth_bins)
# Context: 64 channels, each weighted by the depth probabilities [§3.1]
# [code: mmdet3d/models/detectors/cam_stream_lss.py#L140]
context = context_conv(image_feats) # (B*N, 64, 112, 200)
frustum = depth_probs[:, None] * context[:, :, None]
# Splat: move each cell to ego coordinates and sum into a grid [§3.1]
# Grid: 0.6 m cells, x, y in [-54, 54] m, z in [-5, 3] m
# [code: mmdet3d/models/detectors/cam_stream_lss.py#L229-L258]
voxels = splat_sum(frustum, lidar2img) # (B, 64, 13, 180, 180)
# Spatial-to-channel: fold the 13 height slices into channels [§3.1]
# [code: mmdet3d/models/detectors/cam_stream_lss.py#L318-L322]
bev = reshape(voxels, (B, 64 * 13, 180, 180))
# Four 3x3 conv layers, 832 -> 512 -> 512 -> 256 channels [§3.1]
# [code: mmdet3d/models/detectors/cam_stream_lss.py#L194-L207]
cam_bev = bev_encoder(bev) # (B, 256, 180, 180)
# LiDAR stream: voxels 0.075 x 0.075 x 0.2 m [§3.2]
# [code: configs/bevfusion/bevf_tf_4x8_6e_nusc.py#L62-L79]
lidar_bev = lidar_stream(points) # (B, 512, 180, 180)
# Fusion f_static: 3x3 conv on the channel concatenation [§3.3] [eq. 1]
# The code also applies BatchNorm and ReLU here
# [code: mmdet3d/models/detectors/bevf_faster_rcnn.py#L59-L67]
stacked = concat([cam_bev, lidar_bev], dim=channels) # (B, 768, ...)
fused = conv3x3_bn_relu(stacked) # (B, 512, 180, 180)
# Fusion f_adaptive: channel gate from global average pooling [eq. 2]
# [code: mmdet3d/models/detectors/bevf_faster_rcnn.py#L15-L24]
gate = sigmoid(conv1x1(global_avg_pool(fused)))
fused = gate * fused # (B, 512, 180, 180)
# Head, TransFusion-L: 200 queries seeded from a heatmap [§3.4]
# [code: configs/bevfusion/bevf_tf_4x8_6e_nusc.py#L102-L113]
queries = heatmap_queries(fused, num_proposals=200)
# Training matches queries to ground truth with the Hungarian method
# [code: configs/bevfusion/bevf_tf_4x8_6e_nusc.py#L133-L139]
outputs = transfusion_head(queries, fused)
return outputsKey equations, as written in the paper:
\mathbf{F}_\text{fused} = f_\text{adaptive}\big(f_\text{static}\big(\left[\mathbf{F}_\text{Camera}, \mathbf{F}_\text{LiDAR}\right]\big)\big) eq. 1
f_\text{adaptive}(\mathbf{F}) = \sigma\big(\mathbf{W} f_\text{avg}(\mathbf{F})\big) \cdot \mathbf{F} eq. 2
Robustness augmentations, as the code runs them. They are training data transforms, and the object-failure one is also applied in the halfbox test pipeline [code: configs/base/datasets/nusc_halfbox_tf.py#L38] [code: configs/base/datasets/nusc_halfbox_tf.py#L68].
# Object-failure augmentation, as implemented [§4.4.1]
# [code: mmdet3d/datasets/pipelines/transforms_3d.py#L985-L994]
# Paper: each frame 0.5, and each object within it 0.5 [§4.4.1]
def drop_object_points(points, gt_boxes):
# Frame-level coin: applied with probability 0.5 per call
if random() < 0.5:
# Remove every point inside any ground-truth box, with no
# per-object coin
inside = points_in_any_box(points, gt_boxes)
points = points[~inside]
return pointsHyperparameters
Values marked "not stated" are absent from the paper. Values from the code are the official config for that variant.
| Name | Value | Where | Notes |
|---|---|---|---|
| Image input | 448 x 800 (H x W), keep ratio, pad to multiple of 32 | §4.1 [code: configs/base/datasets/nusc_tf.py#L16] [code: configs/base/datasets/nusc_tf.py#L39] | paper and code agree |
| Image backbone | Dual-Swin-Tiny (CBSwinTransformer): embed 96, depths 2-2-6-2, heads 3-6-12-24, window 7, drop path 0.2 | §4.1 bevf_tf_4x8_6e_nusc.py | drop path, window and heads: not stated in paper |
| Neck | FPN, 256 output channels, ADP on | §3.1 app. A bevf_tf_4x8_6e_nusc.py | ADP internals not checked against code |
| Depth bins | 41 bins, 4 m to 44 m, 1 m step | bevf_faster_rcnn.py cam_stream_lss.py | not stated in paper |
| Frustum canvas | 900 x 1600 (original frame), downsample 8, giving 112 x 200 | cam_stream_lss.py | not stated in paper; see Pitfalls |
| Camera BEV grid | 0.6 m cells; x, y in [-54, 54] m; z in [-5, 3] m | bevf_tf_4x8_6e_nusc.py bevf_tf_4x8_6e_nusc.py | not stated in paper |
| Camera BEV channels | 64 context channels x 13 height slices = 832; output 256 | cam_stream_lss.py bevf_tf_4x8_6e_nusc.py | C_Camera = 256 from config (imc) |
| LiDAR voxel size | 0.075 x 0.075 x 0.2 m | §4.1 bevf_tf_4x8_6e_nusc.py | paper says "official settings" |
| LiDAR voxel limits | max 10 points per voxel; max voxels (120000, 160000) | bevf_tf_4x8_6e_nusc.py | not stated in paper |
| LiDAR sparse shape | 41 x 1440 x 1440 | bevf_tf_4x8_6e_nusc.py | not stated in paper |
| LiDAR sweeps | 10 | [code: configs/base/datasets/nusc_tf.py#L26-L31] | not stated in paper |
| Fused channels | LiDAR BEV 512 + camera BEV 256 -> 512 | bevf_tf_4x8_6e_nusc.py bevf_tf_4x8_6e_nusc.py | not stated in paper |
| Fusion conv | 3x3, 768 -> 512, BatchNorm (eps 1e-3, momentum 0.01), ReLU | §3.3 eq. 1 bevf_faster_rcnn.py | eq. 1 shows conv only |
| Channel gate | global average pool, 1x1 conv, sigmoid; no reduction ratio | §3.3 eq. 2 bevf_faster_rcnn.py | |
| Head (TransFusion-L) | 200 proposals, 1 decoder layer, 8 heads, hidden 128, FFN 256, dropout 0.1 | bevf_tf_4x8_6e_nusc.py | not stated in paper |
| Assignment costs | focal class 0.15; BEV L1 box 0.25; 3D IoU 0.25 | bevf_tf_4x8_6e_nusc.py | not stated in paper |
| Loss weights | focal 1.0 (gamma 2, alpha 0.25); L1 box 0.25; Gaussian focal heatmap 1.0 | bevf_tf_4x8_6e_nusc.py | not stated in paper |
| Test-time NMS | none (nms_type None) | §4.1 bevf_tf_4x8_6e_nusc.py | paper: no extra post-processing |
| Optimizer | AdamW, betas (0.9, 0.999), weight decay 0.05, grad clip max-norm 10 | bevf_tf_4x8_6e_nusc.py | optimizer type not stated in paper |
| Fusion learning rate | 1e-3 | app. B.1 bevf_tf_4x8_6e_nusc.py | |
| Fusion batch size | 32 (8 GPUs x 4) for TransFusion-L and CenterPoint | app. B.1 bevf_tf_4x8_6e_nusc.py | PointPillars config uses 2 per GPU; see Paper vs code |
| Fusion schedule, TransFusion-L and CenterPoint | 6 epochs, x0.1 at epochs 4 and 5 | app. B.1 bevf_tf_4x8_6e_nusc.py | §4.1 says 9 epochs; see Paper vs code |
| Fusion schedule, PointPillars | 12 epochs, x0.1 at epochs 8 and 11 | app. B.1 [code: configs/base/schedules/schedule_1x.py#L11-L14] | |
| Freezing in fusion stage | LiDAR stream frozen for CenterPoint and TransFusion-L | app. B.1 train.py bevf_tf_4x8_6e_nusc.py | PointPillars config does not freeze LiDAR |
| Camera stream frozen in fusion | image backbone, neck and lift module frozen | bevf_tf_4x8_6e_nusc.py bevf_faster_rcnn.py | paper does not say; see Paper vs code |
| Camera-only stream schedule | 20 epochs, AdamW, lr 1e-4, batch 4 per GPU | app. B.1 bevf_tf_4x8_20e_nusc_cam.py bevf_tf_4x8_20e_nusc_cam.py | paper says streams trained with "official" LiDAR settings |
| FOV robustness | keep points with azimuth in [-60, 60] deg (pi/3) or [-90, 90] deg (pi/2), for training and test | §4.4.1 app. B.1 [code: configs/base/datasets/nusc_fov60_tf.py#L30] [code: configs/base/datasets/nusc_fov60_tf.py#L55] loading.py | |
| Object-failure augmentation | drop-probability 0.5 per frame; drop all points in GT boxes | §4.4.1 transforms_3d.py | paper: per-object 0.5 too; see Paper vs code |
| Robust fine-tune | learning rate 1e-4, 12 epochs, x0.1 at epochs 8 and 11, batch 32 | app. B.1 halfbox_bevf_tf_2x8_1x_nusc.py halfbox_bevf_tf_2x8_1x_nusc.py | config batch is 2 per GPU |
| BEV-space augmentation (BEVFusion*) | cyclic schedule, base lr 1e-4, 10 epochs, grad clip max-norm 0.1 | §4.3 bevf_tf_4x8_10e_nusc_aug.py | paper does not state epochs for this run |
Results
Author-reported throughout. Setup: nuScenes detection, mAP and NDS §4.1 app. B.1. The generalisation and robustness results are on the validation set according to the text §4.2 §4.4.1; Table 2 also gives test results. Test results use no test-time augmentation or ensemble §4.3. The paper does not say how many runs were averaged; its checklist says results were observed with a fixed seed.
Generalisation over LiDAR streams (nuScenes validation) tab. 1
| Streams | PointPillars mAP | PointPillars NDS | CenterPoint mAP | CenterPoint NDS | TransFusion-L mAP | TransFusion-L NDS |
|---|---|---|---|---|---|---|
| Camera only | 22.9 | 31.1 | 27.1 | 32.1 | 22.7 | 26.1 |
| LiDAR only | 35.1 | 49.8 | 57.1 | 65.4 | 64.9 | 69.9 |
| Camera + LiDAR (BEVFusion) | 53.5 | 60.4 | 64.2 | 68.0 | 67.9 | 71.0 |
Comparison with state of the art tab. 2. Subset of the table: mAP and NDS only. * uses BEV-space augmentation during training; † uses double-flip at test time (as labelled in the source). Per-class APs are in tab. 2.
| Set | Method | Modality | mAP | NDS |
|---|---|---|---|---|
| val | FUTR3D | LC | 64.2 | 68.0 |
| val | BEVFusion (TransFusion-L) | LC | 67.9 | 71.0 |
| val | BEVFusion* | LC | 69.6 | 72.1 |
| test | TransFusion-L | L | 65.5 | 70.2 |
| test | CenterPoint† | L | 60.3 | 67.3 |
| test | PointAugmenting† | LC | 66.8 | 71.0 |
| test | MVP | LC | 66.4 | 70.5 |
| test | FusionPainting | LC | 68.1 | 71.6 |
| test | TransFusion | LC | 68.9 | 71.7 |
| test | BEVFusion (TransFusion-L) | LC | 69.2 | 71.8 |
| test | BEVFusion* | LC | 71.3 | 73.3 |
FOV-limited LiDAR tab. 3. Gains in parentheses are absolute mAP or NDS points over the LiDAR row. "LC" is the TransFusion LiDAR-camera baseline §4.4.1.
| FOV | Metric | PointPillars LiDAR | PointPillars BEVFusion | CenterPoint LiDAR | CenterPoint BEVFusion | TransFusion-L LiDAR | TransFusion-L BEVFusion | TransFusion LC |
|---|---|---|---|---|---|---|---|---|
| ±90 deg | mAP | 12.4 | 36.8 (+24.4) | 23.6 | 45.5 (+21.9) | 27.8 | 46.4 (+18.6) | 31.1 |
| ±90 deg | NDS | 37.1 | 45.8 (+8.7) | 48.0 | 54.9 (+6.9) | 50.5 | 55.8 (+5.3) | 49.2 |
| ±60 deg | mAP | 8.4 | 33.5 (+25.1) | 15.9 | 40.9 (+25.0) | 19.0 | 41.5 (+22.5) | 21.0 |
| ±60 deg | NDS | 34.3 | 42.1 (+7.8) | 43.5 | 49.9 (+6.4) | 45.3 | 50.8 (+5.5) | 41.2 |
Object-point dropping tab. 4. Parenthesised gains in the augmented rows are measured against the LiDAR-only row without augmentation, not against an augmented LiDAR baseline (checked: for example 41.6 - 12.7 = 28.9).
| Training | Metric | PointPillars LiDAR | PointPillars BEVFusion | CenterPoint LiDAR | CenterPoint BEVFusion | TransFusion-L LiDAR | TransFusion-L BEVFusion | TransFusion LC |
|---|---|---|---|---|---|---|---|---|
| no augmentation | mAP | 12.7 | 34.3 (+21.6) | 31.3 | 40.2 (+8.9) | 34.6 | 40.8 (+6.2) | 38.1 |
| no augmentation | NDS | 36.6 | 49.1 (+12.5) | 50.7 | 54.3 (+3.6) | 53.6 | 56.0 (+2.4) | 55.4 |
| with augmentation | mAP | - | 41.6 (+28.9) | - | 54.0 (+22.7) | - | 50.3 (+15.7) | 37.2 |
| with augmentation | NDS | - | 51.9 (+15.3) | - | 61.6 (+10.9) | - | 57.6 (+4.0) | 51.1 |
Camera failures (nuScenes validation) tab. 5. "Missing F" removes the front camera; "Preserve F" keeps only the front camera; "Stuck" freezes 50 % of the camera frames §4.4.2.
| Approach | Clean mAP | Clean NDS | Missing F mAP | Missing F NDS | Preserve F mAP | Preserve F NDS | Stuck mAP | Stuck NDS |
|---|---|---|---|---|---|---|---|---|
| DETR3D (camera only) | 34.9 | 43.4 | 25.8 | 39.2 | 3.3 | 20.5 | 17.3 | 32.3 |
| PointAugmenting | 46.9 | 55.6 | 42.4 | 53.0 | 31.6 | 46.5 | 42.1 | 52.8 |
| MVX-Net | 61.0 | 66.1 | 47.8 | 59.4 | 17.5 | 41.7 | 48.3 | 58.8 |
| TransFusion | 66.9 | 70.9 | 65.3 | 70.1 | 64.4 | 69.3 | 65.9 | 70.2 |
| BEVFusion | 67.9 | 71.0 | 65.9 | 70.7 | 65.1 | 69.9 | 66.2 | 70.3 |
Camera-stream ablation (PointPillars head, validation) tab. 6 §4.5.
| BEV encoder | ADP | Large backbone (Dual-Swin-T) | mAP | NDS |
|---|---|---|---|---|
| - | - | - | 13.9 | 24.5 |
| yes | - | - | 17.9 | 27.0 |
| yes | yes | - | 18.0 | 27.1 |
| yes | yes | yes | 22.9 | 31.1 |
Fusion-module ablation (validation) tab. 7 §4.5. CSF is channel and spatial fusion (the 3x3 conv); AFS is the adaptive channel gate.
| Fusion | PointPillars mAP | PointPillars NDS | CenterPoint mAP | CenterPoint NDS | TransFusion-L mAP | TransFusion-L NDS |
|---|---|---|---|---|---|---|
| LiDAR only | 35.1 | 49.8 | 57.1 | 65.4 | 64.9 | 69.9 |
| CSF | 51.6 | 57.4 | 63.0 | 67.4 | 67.3 | 70.5 |
| CSF + AFS | 53.5 | 60.4 | 64.2 | 68.0 | 67.9 | 71.0 |
Appendix results (author-reported; figure and table numbers follow source order).
- Fusion module under malfunction, TransFusion-L tab. 8: clean, object failure, and the camera failures. CSF only: clean 67.3 mAP; object failure 50.1 mAP; missing F 65.4; preserve F 63.5; stuck 65.6. CSF + AFS: clean 67.9; object failure 50.3; missing F 65.9; preserve F 65.1; stuck 66.2. LiDAR-only TransFusion-L: clean 64.9; object failure 34.6.
- Both malfunctions, BEVFusion vs TransFusion tab. 9: BEVFusion mAP is 67.9 clean, 47.8 object failure plus missing F, 39.3 plus preserve F, 43.6 plus stuck. TransFusion LC is 66.9 clean, 34.2, 33.6 and 33.9.
- Lighting tab. 10: mAP (metric not labelled in the table) by day / night: CenterPoint (L) 62.8 / 35.4; TransFusion-L (L) 64.8 / 36.2; TransFusion (LC) 67.0 / 41.8; BEVFusion (LC) 68.0 / 42.4.
- Distance from ego vehicle tab. 11 tab. 12: BEVFusion with TransFusion-L, mAP for <15 m / 15-30 m / >30 m: 77.3 / 69.4 / 49.2, against 76.3 / 66.1 / 43.2 for LiDAR only. In tab. 12 the authors' TransFusion re-implementation gets 77.6 / 68.3 / 47.7 and their reported TransFusion gets 75.5 / 66.9 / 43.7.
- Latency and memory tab. 13: batch size 1, Intel Xeon Gold 6126 CPU and one V100 GPU. Memory in MB / time in ms. LiDAR only: PointPillars 7190 / 189.38; CenterPoint 12468 / 199.87; TransFusion-L 12536 / 263.61. Camera only: 7948 / 1264.55; 7956 / 1278.94; 7950 / 1264.45. Both: 7968 / 1513.65; 18078 / 1464.21; 18086 / 1529.66. The text says the camera-to-BEV projector alone costs more than 957 ms app. E and can be sped up by running streams concurrently.
Paper vs code
Code: github.com/ADLab-AutoDrive/BEVFusion
at fb4384ca05cae05be548dd96a0f940da70263cdb (cloned with --depth 1).
The paper's abstract links this repository, so it is the authors' own code. The repository is a fork of MMDetection3D (Apache-2.0,
LICENSE). The BEVFusion code lives in mmdet3d/models/detectors/,
and configs/bevfusion/ holds the per-variant configs.
Consistent with the paper: nuScenes input size 448 x 800, Dual-Swin-T backbone, TransFusion-L fusion learning rate 1e-3 with the 6-epoch and 4/5 schedule app. B.1 bevf_tf_4x8_6e_nusc.py, 12-epoch schedule for PointPillars [code: configs/base/schedules/schedule_1x.py#L11-L14], LiDAR frozen for CenterPoint and TransFusion-L but not for PointPillars train.py, FOV limits of 60 and 90 degrees [code: configs/base/datasets/nusc_fov60_tf.py#L30], and no NMS at test time bevf_tf_4x8_6e_nusc.py.
Discrepancies found:
- Depth supervision is not in the paper. The code adds a KL loss between predicted depth and depth projected from LiDAR, with weight 1.0 bevf_transfusion.py. The code comments say this is "not mentioned in paper" bevf_faster_rcnn.py. Only the PointPillars data pipeline loads projected depth [code: configs/base/datasets/nusc_pp.py#L32]. The TransFusion and CenterPoint data configs do not [code: configs/base/datasets/nusc_tf.py#L19-L42]. The PointPillars fusion config also freezes the camera stream bevf_pp_2x8_1x_nusc.py, so the effect of this loss on that model is not clear from the fusion config alone.
- Camera stream frozen in fusion training. The paper describes the
camera stream as trained in stage one and inherited in stage two. It says
nothing about freezing it. The configs set
freeze_img=Truebevf_tf_4x8_6e_nusc.py bevf_cp_4x8_6e_nusc.py bevf_pp_2x8_1x_nusc.py, and the freeze also covers the lift module bevf_faster_rcnn.py. - Object-failure augmentation is per frame in code, per object in paper. The paper says each frame has a 0.5 chance and each object a further 0.5 chance §4.4.1. The code makes one 0.5 draw per call and removes points in every ground-truth box transforms_3d.py.
- Object-failure config also limits the field of view in training. The halfbox training pipeline restricts LiDAR points to ±60 degrees [code: configs/base/datasets/nusc_halfbox_tf.py#L30]. The paper does not describe this for the object-failure experiment.
- Object-failure evaluation is random. The halfbox test pipeline applies
the same drop [code: configs/base/datasets/nusc_halfbox_tf.py#L68]. The
drop calls
np.random.rand()with no seed set in that function transforms_3d.py. The paper's checklist says results were observed with a fixed seed, which does not obviously cover this draw. - Batch size. The paper states 32 on 8 V100 GPUs app. B.1. The TransFusion and CenterPoint configs give 4 per GPU, which is 32 on 8 GPUs bevf_tf_4x8_6e_nusc.py. The PointPillars config gives 2 per GPU bevf_pp_2x8_1x_nusc.py, and its name ("2x8") suggests 16 in total. The GPU count is not in that file. The robust fine-tune configs also give 2 per GPU halfbox_bevf_tf_2x8_1x_nusc.py.
- Fusion schedule length. §4.1 says the fusion stage runs "another 9 epochs" §4.1. Appendix B.1 gives 12 epochs for PointPillars and 6 for CenterPoint and TransFusion-L, and the TransFusion config matches the 6 bevf_tf_4x8_6e_nusc.py. The paper is internally inconsistent here.
- Fusion conv. Eq. 1 shows a convolution only. The code uses convolution, BatchNorm and ReLU bevf_faster_rcnn.py.
- Undocumented choices in code. Depth bins, BEV grid, voxel limits, sweep count, optimizer type and gradient clipping are set in the configs and not in the paper (see Hyperparameters). The paper gives the optimizer learning rate and batch size only.
- BEV-space augmentation has a different source. The BEVFusion* rows use BEV-space augmentation, which the paper's footnote credits to another repository (mit-han-lab/bevfusion). The code README gives the same credit [README.md]. This card does not use that repository's code.
No code was found that conflicts with the paper on input size, backbone, learning rate, FOV limits or the fusion equations.
Pitfalls
- Frustum canvas differs from the input size. Images are resized to
448 x 800 [code: configs/base/datasets/nusc_tf.py#L16], but the frustum is
built on the original 900 x 1600 frame cam_stream_lss.py,
and
lidar2imgis not rescaled by the resize transform. Downsampling by 8 gives 112 x 200, which matches the stride-4 features of the 448 x 800 input app. A. Changingimg_scalewithout also changingfinal_dimanddownsamplebreaks this match. - Depth is a soft distribution over 41 bins from 4 m to 44 m. The bin range is a code default, not a paper value, and the paper does not say what happens beyond it cam_stream_lss.py.
- The spatial-to-channel reshape swaps the BEV axes. The camera BEV is permuted to swap its two horizontal axes cam_stream_lss.py. The fusion code only interpolates on a shape mismatch, so it does not check the axis order against the LiDAR BEV bevf_faster_rcnn.py.
- Robustness results are for removed or cropped points, not for an empty point cloud. The code path for a frame with zero LiDAR points was not checked, so do not assume the model handles total LiDAR loss without a test.
- "%" in the paper means percentage points. For example, the 18.4% gain for PointPillars is 53.5 - 35.1 mAP tab. 1 §1.
- The table gains in tab. 4 are against the non-augmented LiDAR baseline. The augmented LiDAR-only rows are not reported.
- Vanilla fusion can get worse after augmentation. TransFusion LC drops from 38.1 to 37.2 mAP with object-failure augmentation tab. 4; the paper hypothesises that missing foreground points give wrong supervision §4.4.1.
- Camera-only stream training details are in code, not the paper. The config uses 20 epochs, AdamW and lr 1e-4 bevf_tf_4x8_20e_nusc_cam.py bevf_tf_4x8_20e_nusc_cam.py. The paper only says "official" LiDAR settings app. B.1.
- The camera-stream BEV grid (0.6 m) is coarser than the voxel grid (0.075 m) but matches the LiDAR BEV resolution bevf_tf_4x8_6e_nusc.py bevf_tf_4x8_6e_nusc.py. Re-tuning one stream without the other changes the BEV size.
- Table and figure numbers follow caption order in the source. LaTeX floats may appear in a different order in the PDF, so check the PDF before quoting a number.
- Parts of the paper were added in review. Appendix A to F and several
sentences in the main text are wrapped in a
\rebuttalmacro in the source [raw source: latex/appendix.tex].
Lineage
- Builds on: Lift-Splat-Shoot for the depth-based view projection §3.1. Dual-Swin-T (CBNetV2) for the image backbone §3.1. Spatial-to-channel from a prior work the paper cites §3.1. PointPillars, CenterPoint and TransFusion-L as LiDAR streams and heads §3.2 §3.4. Squeeze-and- Excitation for the channel gate §3.3. MMDetection3D for the code base §4.1.
- Compared against: FUTR3D, PointAugmenting, MVX-Net, PointPainting, 3D-CVF, MVP, FusionPainting, TransFusion and DETR3D tab. 2 tab. 5.
Source and license
Adapted from “BEVFusion: A Simple and Robust LiDAR-Camera Fusion Framework” by Tingting Liang, Hongwei Xie, Kaicheng Yu, Zhongyu Xia, Zhiwei Lin, Yongtao Wang, Tao Tang, Bing Wang, Zhi Tang (arXiv:2205.13790), used under CC0 1.0. Changes: condensed and restructured for implementation, with tables, equations and algorithms taken from the paper and its code, each cited to its source. Not affiliated with or endorsed by the authors. Code citations refer to https://github.com/ADLab-AutoDrive/BEVFusion at commit fb4384ca05ca (Apache-2.0). Any quoted code is shown under that license.