项目地址 :github.com/robbyant/lingbot-map
论文:Geometric Context Transformer for Streaming 3D Reconstruction
复现环境:Ubuntu 24.04 + RTX 4090 24GB + PyTorch 2.6.0
1. 项目概述
LingBot-Map 是一个基于 Vision Transformer 的流式 3D 重建系统。输入单目 RGB 图片序列(或视频),逐帧输出相机位姿(6-DoF)、深度图和 3D 点云,无需传统的 SfM 初始化或全局 BA 优化。
核心特点:
-
流式推理(Streaming Inference):逐帧处理 + KV Cache 注意力,在 RTX 4090 上可达 ~20 FPS(518×378 分辨率)
-
长序列支持:通过滑动窗口 KV Cache + Keyframe Interval 策略,支持超 10,000 帧
-
端到端:从 RGB 直接到 3D 点云,无后处理 BA
-
SOTA 重建质量:在 Tanks and Temples、ScanNet 等基准上优于现有流式和离线方法
与 SLAM 的区别:LingBot-Map 是离线 3D 重建系统,输入是已录制的视频文件/图片序列,不是实时摄像头流。它追求的是重建质量而非实时性,"~20 FPS"指的是处理速度而非实时吞吐。
2. 环境搭建
2.1 硬件要求
LingBot-Map 的显存需求主要由三部分组成:
显存 = 模型权重 + ViT 特征 + KV Cache + 中间激活 + 输出
| 帧数 | 预估 KV Cache | 最低显存建议 |
|---|---|---|
| ≤100 帧 | ~2.5 GB | 8 GB |
| ≤320 帧 | ~5 GB | 12 GB |
| ≤1000 帧 | ~12 GB | 16 GB |
| >3000 帧 | ~20+ GB | 24 GB(推荐 RTX 4090 / A6000) |
关键约束 :必须使用 SM 7.0+(Volta 及更新)架构的 NVIDIA GPU 。Pascal 架构(如 P40、P100)因 SM 6.1 不被 CUDA 12.x 预编译 kernel 支持,运行时会报 GET was unable to find an engine。
2.2 创建 Python 环境
conda create -n lingbot-map python=3.10 -y
conda activate lingbot-map
2.3 安装 PyTorch
根据 CUDA 版本选择。CUDA 12.4 对应:
pip install torch==2.6.0 torchvision==0.21.0 --index-url https://download.pytorch.org/whl/cu124
验证 GPU 可用:
python -c "import torch; print(torch.cuda.is_available()); print(torch.cuda.get_device_name(0))"
2.4 安装依赖
# 基础依赖
pip install einops safetensors opencv-python tqdm scipy
# 3D 可视化(viser 提供浏览器端 3D 点云查看)
pip install viser trimesh matplotlib requests
# 模型下载工具(国内用 ModelScope 更快)
pip install huggingface_hub modelscope
# LingBot-Map 本体(editable 模式)
git clone https://github.com/robbyant/lingbot-map.git ~/lingbot-map
pip install -e ~/lingbot-map
2.5 下载模型权重
模型文件 4.4GB。HuggingFace 直连国内可能被墙,使用 ModelScope 国内源:
python -c "
from modelscope import snapshot_download
snapshot_download('Robbyant/lingbot-map', cache_dir='models', allow_patterns='lingbot-map*.pt')
"
2.6 下载天空分割模型(室外场景)
skyseg.onnx(168MB)用于过滤点云中的天空噪点:
python -c "
import os
os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
from huggingface_hub import hf_hub_download
hf_hub_download('JianyuanWang/skyseg', 'skyseg.onnx', local_dir='.')
"
室内场景可跳过此步,运行时去掉 --mask_sky 参数。
2.7 FlashInfer 与 SDPA
LingBot-Map 支持两种注意力后端:
| 后端 | 性能 | 安装难度 | 适用场景 |
|---|---|---|---|
| FlashInfer | 高(论文 ~20 FPS) | 需从源码编译 CUDA kernel | Linux + 完整 CUDA 工具链 |
| SDPA | 略低 | 零安装(PyTorch 内置) | Windows / 无 FlashInfer |
如果未安装 FlashInfer,运行时加 --use_sdpa 即可降级为 PyTorch 内置的 F.scaled_dot_product_attention。
3. 核心架构
3.1 总体数据流
输入: RGB 图片序列 [S, 3, H, W]
│
├── 1. Patch Embedding (DINOv2 ViT-L/14)
│ 图片 → ImageNet 标准化 → Conv → Patch Tokens [S, N_patches, 1024]
│
├── 2. Special Tokens
│ [Camera Token] [Register×4] [Scale Token] + Patch Tokens
│ → [S, 6 + N_patches, 1024]
│
├── 3. Alternating Attention Blocks (24 层)
│ ├── Frame Attention: 每帧独立 self-attention(空间特征)
│ └── Global Attention: 跨帧 causal attention + KV Cache(时序一致性)
│ → Multi-scale token outputs
│
├── 4. Prediction Heads
│ ├── Camera Head: camera token → 4 次迭代 → pose [S, 9]
│ ├── DPT Head (Depth): tokens + image → depth [S, H, W, 1]
│ └── DPT Head (Points): tokens + image → world_points [S, H, W, 3]
│
└── 5. Depth Unprojection
depth + pose → Camera Coords → World Coords → 3D Point Cloud
3.2 Backbone:DINOv2 ViT-L/14
| 参数 | 值 |
|---|---|
| image_size | 518 |
| patch_size | 14 |
| embed_dim | 1024 |
| depth | 24 |
| num_heads | 16 |
| num_register_tokens | 4 |
每帧 518×294 图片 → 37×21 = 777 个 patch tokens,每个 1024 维。虽然代码支持加载 DINOv2 预训练权重,但发布 checkpoint 中已包含全部权重,pretrained_path 为空不影响使用。
3.3 Special Tokens 体系
每帧在 patch tokens 前添加 6 个可学习的特殊 token:
Token 序列: [Camera] [Register×4] [Scale] [Patch_0] ... [Patch_776]
0 1-4 5 6 782
| Token | 数量 | 作用 |
|---|---|---|
| Camera Token | 1 | 聚合帧级信息,输入 Camera Head 预测位姿 |
| Register Tokens | 4 | 继承自 DINOv2,存储全局图像信息 |
| Scale Token | 1 | 核心创新:承载前几帧的双向尺度信息,解决单目尺度不确定性 |
Scale Token 的工作机制:
前 num_scale_frames(默认 8)帧共享一个 scale token,在这些帧之间做双向注意力(非因果),让模型在尺度估计阶段汇聚全局尺度/深度信息。后续流式帧使用另一个 scale token,仅做因果注意力。这个设计是 LingBot-Map 解决单目尺度歧义性的关键。
3.4 Alternating Attention(交替注意力)
这是整个模型最核心的设计。24 层 Transformer 按 aa_block_size=1 组织成 Frame Attention 和 Global Attention 交替执行:
Layer 0: Frame Attention ← 每帧独立 spatial self-attention
Layer 1: Global Attention ← 跨帧 temporal causal attention + KV Cache
Layer 2: Frame Attention
Layer 3: Global Attention
...
Layer 22: Frame Attention
Layer 23: Global Attention
Frame Attention(ViT Block):
-
每帧内部标准 self-attention,所有帧并行
-
2D RoPE 位置编码
-
LayerScale + DropPath
Global Attention(SDPA / FlashInfer Block):
-
因果注意力(Causal Attention):每帧只能看到当前帧及过去帧,不能看到未来
-
跨帧交互,让 camera token 访问其他帧的信息
-
KV Cache 存储过去帧的 Key/Value,避免重复计算
-
3D RoPE 位置编码(t, y, x)
3.5 KV Cache 机制
这是 streaming 推理的核心,避免对历史帧的重复编码。
存储结构(SDPA 模式):
kv_cache = {
"k_0": tensor[B, H, S_cached, P, D], # 第 0 层 global block 的 Key
"v_0": tensor[B, H, S_cached, P, D], # 第 0 层 global block 的 Value
"k_1": ..., "v_1": ..., # 后续层
# 被淘汰帧的 special tokens 保留在 _special cache
"k_0_special": tensor[B, H, S_evicted, N_special, D],
}
滑动窗口淘汰:
当缓存帧数超过 scale_frames + sliding_window 时触发:
cache: [S0...S7 | ...被淘汰的中间帧... | W1...W64]
← scale_frames=8 → ← sliding_window=64 →
-
保留前 scale_frames 帧(尺度参考帧,不可丢弃)
-
保留最后 sliding_window 帧(最近帧)
-
被淘汰中间帧的 special tokens(camera/scale/register token)转入
_specialcache,patch tokens 丢弃
Keyframe Interval:
is_keyframe = (keyframe_interval <= 1) or \
((i - scale_frames) % keyframe_interval == 0)
-
keyframe_interval=1:每帧都是 keyframe,cache 全存(默认,≤320 帧时自动启用) -
keyframe_interval=4:每 4 帧存 1 个,cache 缩减 75% -
非 keyframe 帧仍产生完整预测(pose + depth + points),只是 K/V 不持久化
3.6 Camera Head:迭代位姿优化
Camera Head 从 Camera Token 预测 9 维位姿编码 absT_quaR_FoV:
| 维度 | 含义 |
|---|---|
| 0:3 | 绝对平移 T (x, y, z) |
| 3:7 | 四元数旋转 R (w, x, y, z) |
| 7:9 | 视场角 (FOV_h, FOV_w) |
采用 4 次迭代优化 (--camera_num_iterations 可调):
for iter in range(num_iterations):
# 1. 用当前位姿估计生成 AdaLN 调制参数
shift, scale, gate = poseLN_modulation(embed_pose(current_pose))
# 2. Adaptive LayerNorm: 用位姿信息调制 token 特征
tokens = gate * (adaln_norm(tokens) * (1+scale) + shift)
# 3. 4 层 Transformer trunk
tokens = trunk(tokens)
# 4. MLP 预测位姿残差并累加
current_pose = current_pose + delta_pose
AdaLN(Adaptive Layer Normalization) 是 Camera Head 的核心机制:将当前预测的位姿编码投影为 LN 的 shift/scale 参数,让位姿估计指导 token 的特征调制,使下一次迭代能更准确地预测残差。
3.7 DPT Head:密集预测
DPT Head(出自 Vision Transformers for Dense Prediction, Ranftl et al. 2021)从多尺度 token 重建密集空间输出:
Layer 0 tokens → Conv1×1 → 256C → Resize×4 ┐
Layer 1 tokens → Conv1×1 → 512C → Resize×2 ├→ Concat → Fusion → Output
Layer 2 tokens → Conv1×1 → 1024C │
Layer 3 tokens → Conv1×1 → 1024C ┘
两个 DPT Head 实例共享结构,输出不同:
| Head | 输出维度 | 激活函数 |
|---|---|---|
| Depth Head | 2 (depth + confidence) | exp / expp1 |
| Point Head | 4 (world_xyz + confidence) | inv_log / expp1 |
3.8 两阶段推理流程
Phase 1:尺度初始化(Scale Frames)
前 8 帧一起处理,通过 scale token 做双向注意力,汇聚全局尺度信息。8 帧的 K/V 全部存入 cache。
Phase 2:流式逐帧处理
for i in range(8, S):
is_keyframe = ((i-8) % keyframe_interval == 0)
if not is_keyframe:
skip_append_to_cache()
output = forward(frame_i) # 因果注意力 + KV Cache
每帧的 camera token 通过 causal attention 看到:所有 8 个 scale 帧(双向)+ 过去所有 keyframe(因果),实现全局轨迹一致性约束。
3.9 关键技术总结
| 技术 | 出处 | 作用 |
|---|---|---|
| DINOv2 ViT Backbone | Oquab et al. 2024 | 自监督视觉特征提取 |
| RoPE (2D + 3D) | Su et al. 2024 | 空间 + 时空位置编码 |
| KV Cache + Sliding Window | LLM 推理 / StreamingLLM | 避免重复计算历史帧 |
| FlashInfer Paged Cache | Ye et al. 2024 | 类 vLLM 分页 KV Cache,减少碎片 |
| Scale Token | LingBot-Map 原创 | 解决单目尺度歧义性 |
| Camera Token + AdaLN | LingBot-Map 原创 | 迭代相机位姿优化 |
| DPT Head | Ranftl et al. 2021 | ViT → 密集预测 |
4. 运行
4.1 跑 courthouse 示例
cd ~/lingbot-map && python demo.py \
--model_path /path/to/lingbot-map.pt \
--image_folder example/courthouse \
--mask_sky \
--use_sdpa
286 帧,RTX 4090 上推理约 39 秒(~7.2 FPS),峰值显存 13 GB。

4.2 跑自己的视频
python demo.py \
--model_path /path/to/lingbot-map.pt \
--video_path /path/to/video.mp4 \
--fps 10 \
--mask_sky \
--use_sdpa
--fps 10 表示从视频中每秒抽 10 帧。视频越长,建议 fps 越低(>1 分钟可设 --fps 5)。
4.3 关键参数速查
| 参数 | 默认值 | 作用 |
|---|---|---|
--image_size |
518 | 预处理宽度 |
--mode |
streaming | 推理模式(streaming / windowed) |
--fps |
10 | 视频抽帧率 |
--mask_sky |
False | 天空分割过滤 |
--use_sdpa |
False | 使用 SDPA 替代 FlashInfer |
--keyframe_interval |
1(≤320帧自动) | Keyframe 间隔 |
--kv_cache_sliding_window |
64 | KV Cache 窗口大小 |
--num_scale_frames |
8 | 尺度初始化帧数 |
--camera_num_iterations |
4 | 位姿迭代次数 |
--offload_to_cpu |
False | 每帧输出移到 CPU |
--conf_threshold |
1.5 | 点云置信度阈值 |
--downsample_factor |
10 | 点云下采样率 |
4.4 显存优化方案
当显存不足时,按优先级尝试:
| 方案 | 预期节省 | 代价 |
|---|---|---|
--offload_to_cpu |
0.5-1 GB | 小幅传输延迟 |
--keyframe_interval 4 |
KV Cache 减 75% | 跳帧略微降低精度 |
--kv_cache_sliding_window 32 |
限制 cache 上限 | 旧帧信息被丢弃 |
--camera_num_iterations 1 |
减 3 次迭代 | 位姿精度小幅下降 |
--mode windowed |
分窗重置 cache | 需调节 window_size/overlap |
推荐的内存友好命令:
python demo.py \
--model_path /path/to/lingbot-map.pt \
--image_folder example/courthouse \
--mask_sky --use_sdpa \
--keyframe_interval 4 \
--kv_cache_sliding_window 32 \
--offload_to_cpu
5. 查看结果
推理完成后自动启动 viser 服务器(默认端口 8080),浏览器打开 http://localhost:8080:
| 操作 | 方式 |
|---|---|
| 旋转 | 鼠标左键拖动 |
| 平移 | 鼠标中键拖动(或 Shift+左键) |
| 缩放 | 鼠标滚轮 |
可视化内容包括:
-
3D 彩色点云(天空点已过滤)
-
相机轨迹连线
-
当前帧点云高亮
-
置信度过滤(低于
conf_threshold的点被剔除)
6. 常见问题
6.1 CUDA out of memory
原因 :KV Cache 随帧数线性增长,keyframe_interval=1 时每帧都存 cache。
解决 :增大 --keyframe_interval,减小 --kv_cache_sliding_window,启用 --offload_to_cpu,或切换到 --mode windowed。
6.2 FlashInfer is not available
原因:FlashInfer 未安装。
解决 :加 --use_sdpa,使用 PyTorch 内置 SDPA 注意力。性能略降但不影响结果。
6.3 GET was unable to find an engine(P40/P100 等老卡)
原因:Pascal 架构(SM 6.1)不被 CUDA 12.x 预编译 kernel 支持。
解决:换用 SM 7.0+ 的 GPU(RTX 20 系及以上,V100 等)。
6.4 HuggingFace 下载卡住
原因:国内网络限制。
解决:
-
方式 A:用 ModelScope 国内源下载(推荐)
-
方式 B:设置 HF 镜像
export HF_ENDPOINT=https://hf-mirror.com -
方式 C:手动下载后指定路径
6.5 pretrained_path 为空的警告
原因:模型初始化时尝试加载 ViT backbone 预训练权重(DINOv2),但路径为空。
影响 :无。全部权重已包含在 lingbot-map.pt checkpoint 中,可忽略此警告。
6.6 推理速度随帧数递减
原因:KV Cache 线性增长 → Self-Attention 计算量 O(n²),加上显存溢出时触发共享内存(Unified Memory),带宽从 ~500 GB/s(GPU 本地)骤降到 ~16 GB/s(PCIe)。
缓解 :使用 --kv_cache_sliding_window 限制 cache 大小,或使用 --keyframe_interval 稀疏化。
7. 代码结构
lingbot_map/
├── models/
│ ├── gct_base.py # GCT 基类
│ ├── gct_stream.py # 流式推理(≤320 帧)
│ ├── gct_stream_window.py # 窗式推理(>3000 帧)
│ └── gct_stream_window_v2.py # 窗式推理 v2
├── aggregator/
│ ├── base.py # Patch Embedding + Token + Block 构建
│ └── stream.py # 因果注意力 + KV Cache + 3D RoPE
├── layers/
│ ├── attention.py # 4 种注意力实现
│ ├── block.py # 4 种 Transformer Block
│ ├── rope.py # 2D/3D RoPE
│ ├── vision_transformer.py # DINOv2 ViT 变体
│ └── flashinfer_cache.py # FlashInfer 分页 KV Cache
├── heads/
│ ├── camera_head.py # 位姿预测(迭代优化 + AdaLN)
│ ├── dpt_head.py # 深度/点云密集预测
│ └── head_act.py # 输出激活函数
├── utils/
│ ├── pose_enc.py # 位姿编解码
│ ├── geometry.py # 深度→世界坐标反投影
│ └── load_fn.py # Checkpoint 加载
└── vis/
├── point_cloud_viewer.py # viser 3D 可视化
├── sky_segmentation.py # 天空分割
└── glb_export.py # GLB 模型导出
参考
-
论文:Geometric Context Transformer for Streaming 3D Reconstruction
-
DINOv2: Oquab et al., "DINOv2: Learning Robust Visual Features without Supervision", 2024
-
DPT: Ranftl et al., "Vision Transformers for Dense Prediction", 2021
-
FlashInfer: Ye et al., "FlashInfer: Kernel Library for LLM Serving", 2024