这篇文章记录的是一条完整的工程路径:先把 LingBot-Map 原项目在 Windows + WSL2 + RTX GPU 上跑起来,用一段真实竖屏视频得到原始空间点云;再把一次性 Viewer 演进为可上传、可排队、可融合、可下载、可在网页自由查看的本机 Workbench。
首屏这段 GIF 来自保留的真实融合 LAZ:从 1,464,610 个最终点中确定性采样后做相机环绕。它证明浏览器可以围绕同一份实际点云浏览;它不是算法随时间增长的动画,也不证明厘米级精度或完整的语义建模。

8.4 秒竖屏 HEVC 样例,经 LingBot 推理和融合后得到的真实点云环绕预览。GIF 只改变相机视角,不改变点的位置或颜色。
先说边界:这台机器解决什么,不解决什么
目标是让一个本机用户把视频变成可检查、可下载的相对尺度点云。浏览器运行在 Windows,Python、GPU 模型和中间数据运行在 Ubuntu 22.04 WSL;浏览器只访问 localhost。首版没有账户、多租户、云存储、移动端、自动房间分割或厘米级测量。
场景坐标以 right-handed-y-up 发布,scaleStatus 为 relative。页面可显示相对高度,但不把单目视频重建包装成测量仪器。反光面、无纹理墙面和细薄结构仍然是需要更多样例验证的边界。
平台:把重计算留在 WSL ext4
最初的部署建议来自模型项目的 Python 3.10 / PyTorch 2.8 组合。后来环境中的 CUDA wheel 从 cu128 升到 cu129,因此文章不把某个 wheel 标签写成永恒配置。下面的图是本文复跑时刚执行的运行时自检:它记录当时真正可用的 WSL、GPU、驱动、Torch、CUDA、FlashInfer 与存储文件系统。

这是一张由实际命令输出制成的终端图。发布版本省略了私有绝对路径,但保留了版本和文件系统证据。
| 组件 | 本次复跑自检 |
|---|---|
| WSL | Ubuntu 22.04.2 LTS,WSL2 内核 6.18.33.2 |
| GPU | NVIDIA GeForce RTX 5070 Ti,驱动 595.79,16,303 MiB |
| Python | 3.10.12 |
| PyTorch / torchvision | 2.8.0+cu129 / 0.23.0+cu129 |
| CUDA 可用性 | True,CUDA 12.9 |
| FlashInfer | 0.6.15.post1 |
| 数据根 | WSL ext4,而不是 /mnt/d |
模型权重、抽出的帧、逐帧 evidence 和 Potree 文件都放在 WSL ext4。视频抽帧与 LOD 打包会创建大量小文件;把它们放到 Windows 挂载盘会让跨文件系统 I/O 成为明显瓶颈。Windows 保留浏览器和前端开发体验即可。
原项目的最短环境检查可以写成下面这样。实际安装前仍应以仓库 README 和当前 GPU 驱动为准。
# 在 WSL 中,而不是 Windows Python 中python --versionnvidia-smipython -c "import torch; print(torch.__version__, torch.cuda.is_available(), torch.version.cuda)"成功信号不是“命令没有报错”,而是 Python 能看到 CUDA,nvidia-smi 能看到这张卡,并且模型虚拟环境位于 Linux 文件系统。Windows 侧曾经使用过 SDPA 路径,因为那里没有可用的 FlashInfer;WSL 侧则可以使用 FlashInfer。为了让本次原项目重放尽量少依赖额外后端,我显式使用了 --use_sdpa。
从原项目开始:视频先变成一组方向正确的帧
LingBot-Map 的 demo.py 能把图像序列或视频推理成深度、相机与世界坐标点,并在 Viser 中打开交互 Viewer。它很适合判断“模型能不能在这台机器上跑”,但它不是作业系统:没有上传协议、持久状态、重试、融合产物或浏览器 LOD。
这次使用的是此前上传过的 8.4 秒、720×1280、HEVC、30 fps 竖屏样例。OpenCV 曾在同一 HEVC 文件上间歇性读出零帧,因此不把它作为唯一输入链路。先用 FFmpeg 在 WSL ext4 中固定抽帧,再旋转 90°,让相机与画面方向一致:
ffmpeg -i input.mp4 -vf "fps=10" -q:v 2 frames/%06d.jpg
python demo.py \ --model_path models/lingbot-map-long.pt \ --image_folder frames \ --rotate_clockwise_90 \ --camera_num_iterations 4 \ --use_sdpa \ --port 8085本次复跑实际得到 84 帧,并在 http://localhost:8085 启动原项目 Viewer。下面是 84 帧、4 次相机优化的原始 Viser 视图截图。它是模型直接输出的点云观察面,因此会比融合结果更松散;这正是后续需要融合层的原因。
复跑日志中还出现了一条被内部捕获的 DINO 预训练路径警告;这不是把它忽略掉的理由。此处把 8085 监听成功和浏览器中实际渲染出的 Viser 点云作为通过依据,同时把该警告留作原项目环境待清理项,而不是把它包装成“零警告部署”。

原始 Viewer 验证的是模型输出可被交互查看。右侧视频帧控制与左侧点云来自同一次本机复跑。
两段真实输入的原始推理记录
早期直接推理已经跑过两段 HEVC 视频;本文又重放了竖屏样例的完整 Viewer 链路。下表把模型纯推理记录和 Workbench 端到端融合结果分开,避免把两个不同的计时口径混在一起。
| 样例 | 输入与抽帧 | 直接模型记录 | 峰值显存 | Workbench 融合结果 |
|---|---|---|---|---|
| 竖屏 | 8.4 秒,720×1280,HEVC;FFmpeg 84 帧,顺时针旋转 90° | 约 12.1 秒 | 约 13.37 GB | 12,792,528 observations → 1,464,610 点 |
| 横屏 | 24.3 秒,1280×720,HEVC;10 fps 为 243 帧,无旋转 | 4 次优化约 33.9 秒 | 约 13.66 GB | 37,006,956 observations → 1,009,550 点 |
这些数字来自真实 GPU 运行记录。它们不等于“上传到网页直到可交互”的端到端时间;首个可交互小于 3 秒、默认视图 30 FPS、浏览器内存小于 512 MB 仍是待测验收目标。
为什么原始 Viewer 还不够:从一次推理到可用工作台
原始 Viser Viewer 解决了模型可视化,但用户还需要处理视频上传、输入异常、GPU 排队、长任务进度、断线恢复、场景下载和重复浏览。于是 Workbench 将一次命令拆成有状态的本地 job:FastAPI 提供接口,SQLite 保存状态,SSE 推送阶段与日志,单个常驻 GPU worker 按 FIFO 消费任务。
状态为 uploading、queued、preflight、extracting、reconstructing、fusing、packaging、ready、failed 和 cancelled。取消不会被伪装成失败,只有失败和取消的 job 可以重试。错误返回固定的 code、stage、retryable、userMessage 和 diagnosticId;CUDA 栈和本地路径只留在诊断日志。
启动入口固定使用 WSL 项目虚拟环境,避免 Windows 临时 uv 解释器带来的 0x800700e8:
.\scripts\workbench.ps1 start.\scripts\workbench.ps1 status.\scripts\workbench.ps1 stopstop 只停止 Workbench,不执行 wsl --shutdown。此前出错命令的 Python 位于 uv 的临时构建缓存,临时目录清理后 Windows 无法再创建该进程;这个问题与 CUDA 或模型权重无关。
逐帧点为什么要融合:把重复观测收敛成权威点云
每一帧推理都会产生世界坐标点、RGB、置信度和相机信息。简单拼接会保留重复观测和深度噪声。Workbench 先按 Morton 顺序分块,再按最细体素累计 sumWeight、weightedXYZ、weightedRGB、observationCount、positionVariance、firstFrame 与 lastFrame:
xyz = Σ(weight_i × xyz_i) / Σ(weight_i)rgb = Σ(weight_i × rgb_i) / Σ(weight_i)最细融合层是唯一的权威结果;父级 LOD 从它派生。这样 LAZ 下载、Potree 浏览和统计数字不会各自做一次融合。竖屏样例的 observation 数下降 88.55%,横屏样例下降 97.27%,但这不是随机删点,而是将同一个细小体素中的多次观测收敛为一个带权重的代表点。


原始预览最多约 200 万个确定性采样点,用于快速判断输入;权威融合点导出为压缩 LAZ,并由 PotreeConverter 2 打成网页 LOD。可选的 Open3D TSDF 表面单独导出为 GLB,失败只给警告,不阻塞已经成功的融合点云。

两个真实显示问题:白色点云与空间方向
白色不是没有颜色
横屏融合结果第一次在网页里几乎全白,但几何正确。检查 LAZ 后确认 RGB 字段还在,问题落在 Potree 材质的输入与输出颜色编码没有对齐。ViewerAdapter 在加载点云时令 outputColorEncoding 与 inputColorEncoding 一致,恢复了真实 RGB。

这个例子提醒我:截图中的“颜色丢失”不能直接归因于模型。应该先分层检查模型 RGB、导出字段、浏览器材质和显示器色彩链路。
方向不对时,不改写原始坐标
点云有时会像侧放在场景网格上。只旋转一个点云对象会让轨迹、GLB、边界和 fit view 不同步,因此修复放在显示层:以内容边界中心为 pivot,应用 T(center) × R(x,y,z) × T(-center)。点云、轨迹和可选 GLB 共用内容根节点,世界网格保持不动。


右侧面板提供 X/Y/Z 的 -180° 到 180° 滑杆、每轴 +90° 和重置。这是显示偏好,当前只保留在页面会话,不会改写原始场景或假装已经持久化为标定结果。
最终产物与当前验证边界
发布 manifest 为 point-scene/v1,其中声明坐标系、相对尺度、轨迹和三个 variant:
| Variant | 用途 | 主格式 |
|---|---|---|
| raw-preview | 输入与逐帧推理核对 | 确定性采样点云 |
| fused-points | 浏览与下载的权威点云 | Potree LOD + 压缩 LAZ |
| clean-surface | 可选连续表面观察 | 独立 GLB |
已验证的事实包括:两段真实 HEVC 视频在 RTX GPU 上的模型运行、84 帧竖屏样例的原始 Viser Viewer 复跑、真实 LAZ/Potree/GLB 资产、网页三种 variant 切换、颜色修复、方向控制,以及前端的上传、SSE 恢复、取消重试、viewer dispose、键盘和 reduced-motion 自动化覆盖。
尚未形成正式基准的项目也明确保留:ready 后首个可交互时间、默认 30 FPS、浏览器内存和不同墙角/薄结构的 P95 几何误差。下一轮会用固定输入、同一浏览器和相同点预算记录这些数据,而不是以“能打开页面”代替性能结论。
这条链路现在有两层证据:原项目 Viewer 说明模型能从同一段视频生成可浏览点云;Workbench 的 LAZ、Potree、任务状态和融合统计说明这份结果能够被稳定地保存、重开、切换与下载。前者验证模型,后者把模型变成了可以实际使用的本机工具。
COMMUNITY DISCUSSION
评论
使用 GitHub 账号登录后参与讨论,评论会同步到 GitHub Discussions。