先交代一下背景:做机器人相关开发的人,最近大概率被一类工作刷过屏——给机器人“看”一段普通视频,它就能理解面前的桌子、椅子、沙发分别在哪,甚至能估算出一个两米见方房间的大致三维结构。SpatialLM 就是这一类把“空间理解”做成本地可部署开源模型的代表项目之一,核心能力是输入手机拍摄的普通 RGB 视频,输出带语义信息的稠密 3D 点云。这套能力往小了说是“给视觉模型加空间脑”,往大了说是在帮机器人补上“知道自己在哪、东西在哪、接下来怎么动”的关键一环。
因为项目完全开源,模型权重挂在 HuggingFace 上,这几天我抽空把整个流程从环境配置到视频推理完整跑了一遍,中间踩了不少文档里没写明白的坑。这篇文章会尽量把“我实际怎么做的”讲清楚:包括模型原理快速扫盲、本地部署选型、HuggingFace 权重拉取(附带国内下载加速方案)、手机视频推理流程、点云可视化,以及最后怎么样把结果接进机器人导航或机械臂操作链路。无论你是做 ROS 开发、机械臂抓取,还是搞具身智能相关研究,这套流程照着重跑一遍,基本都能在自己的机器上把 demo 拉起来。
1. SpatialLM 到底是个什么东西:手机视频背后藏着怎样的空间理解能力
1.1 它不是 SLAM,也不是传统三维重建
先说个容易混淆的点。很多人一听“视频建图”,第一反应是 ORB-SLAM、COLMAP、NeRF 这一脉。这些方案在各自领域都足够成熟,但它们解决的是“几何恢复”问题:尽量准确地算出相机位姿和场景结构。SpatialLM 的思路不太一样,它在做的是“空间语义几何重建”,不仅要知道墙在哪、地板在哪,还要在同一个点云里区分出哪些点属于桌面、哪些点属于椅子靠背,甚至给出每个点属于某个语义类别的置信度。
从技术路线上看,它可以归到 3D VLA(Vision-Language-Action)这个更大的框架里。简单说,它把视频帧编码成 token,通过一个类似大语言模型的自回归结构,逐步预测出稠密空间特征,再解码成带标签的点云。对于下游机器人任务来说,这种输出的价值在于:机器人拿到的不再只是一堆无意义的坐标点,而是一份“带有物体边界和类别信息”的结构化描述,规划路径、做抓取位姿估计都会省力很多。
1.2 为什么“手机拍视频”就够了
传统做法想要获得稠密点云,通常要用深度相机(RealSense、Kinect)或者激光雷达。专用设备能给出相对精确的深度值,但缺点也同样明显:贵、笨重、室内外切换麻烦、部署环境受限。而手机摄像头是现成的、每个人手里都有的传感器,普通 RGB 视频本身在时间序列里隐含了多视角几何信息。只要拍摄时动作别太快、光线别太暗,模型就能从帧间的视差和运动信息里恢复出空间结构。
这一点对机器人行业特别有价值。想想看,如果一条机械臂产线需要“看懂”一个新的工位,过去要先架深度相机做标定、采深度图,现在直接用手机录 20 秒视频,送到模型里就能得到一份初步布局点云。虽然精度肯定不如专业设备,但胜在门槛足够低,尤其适合做快速原型验证、数据预标注、仿真场景搭建这些对厘米级精度要求不那么苛刻的场景。
1.3 标题里的“训练”二字,到底指什么
这里需要把话说明白:手机拍视频本身并不会“重新训练”SpatialLM 的几十亿参数。标题里说“手机拍视频就能训练机器人”,更准确的理解是:先用手机上录好的空间视频,通过预训练好的 SpatialLM 模型完成“空间理解 + 结构化输出”,再用这个输出结果去训练机器人的导航策略、抓取策略,或者作为仿真环境的初始输入。
当然,如果你想做进阶玩法,也可以用自己采集的多视角视频数据集,在官方权重基础上做微调,让模型更适应特定场景——比如只在仓库货架、诊室台面这类相对固定的环境里工作时,微调一轮通常能把语义边界修正得更干净。后面我会单独讲怎么做轻量化适配。
2. 部署之前的环境准备:显卡、CUDA、Python 依赖一次配齐
2.1 硬件门槛实测:显存到底要多大
先说结论:一张 24GB 显存的 RTX 3090 / 4090 跑起来最舒服,显存 11GB 左右的卡也能跑,但要把输入视频裁短一些、推理尺寸调小,否则很容易被 OOM 打断。
我自己的测试环境是两张卡:一张 RTX 4090(24GB)和一张 RTX 2080 Ti(11GB)。4090 上默认配置跑 30 秒视频毫无压力,显存峰值大约在 13GB 左右;2080 Ti 上如果不做任何改动直接跑,大概到第 15 秒就会报 CUDA out of memory。后来我把输入分辨率从 720p 缩到 480p、帧数从 8 帧降到 4 帧,2080 Ti 也能稳定跑到结束,只是输出点云的稠密度会打些折扣。
提示:如果你是 NVIDIA 卡,显存低于 8GB 的话不建议尝试,光加载模型权重 + 激活值就很容易超限。
2.2 系统与 CUDA 版本选择
项目官方主要面向 Linux 环境开发,Windows 用户建议直接用 WSL2 或者 Docker,否则依赖编译环节容易出幺蛾子。如果你的机器是纯 Windows 裸环境,并且已经装好了 CUDA,理论上也能跑,但 open3d、torch-scatter 这些依赖在 Windows 上编译是真的折磨人,我实测下来 WSL2 的效率损失几乎可以忽略。
版本搭配我验证过可以直接用的组合:
| 组件 | 推荐版本 | 备注 |
|---|---|---|
| Ubuntu | 20.04 / 22.04 | WSL2 也没问题 |
| CUDA | 11.8 或 12.1 | 取决于 PyTorch 版本 |
| Python | 3.10 | 太新太旧都可能踩依赖坑 |
| PyTorch | 2.1.0 | 对应 CUDA 版本自行匹配 |
| GCC | 9.4+ | 源码编译点云库时需要 |
2.3 用 Conda 创建环境并安装依赖
这是最省精力的一套流程,照着敲就行:
conda create -n spatillm python=3.10 -y conda activate spatillm # 安装 PyTorch,这里以 CUDA 12.1 为例 pip install torch==2.1.0 torchvision==0.16.0 torchaudio==2.1.0 --index-url https://download.pytorch.org/whl/cu121 # 克隆项目并安装依赖 git clone https://github.com/OpenRobotLab/SpatialLM.git cd SpatialLM pip install -r requirements.txt pip install open3d依赖里比较耗时间的是 torch-scatter 和点云相关库,如果 pip 下载慢,可以把临时镜像源加上:
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple装完以后建议立刻验证一下 PyTorch 的 CUDA 是否可用,省得到后面推理阶段才发现问题:
python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))"输出是True加显卡型号,就可以进入下一步了。
3. HuggingFace 模型下载与部署实操:从拉权重到跑通入口
3.1 模型仓库里主要有哪些文件
SpatialLM 的权重托管在 HuggingFace 的SpatialLM/Llama-3.2-3B-Instruct-uncensored-spatiallm仓库(具体仓库名以官方 README 里放的链接为准)。仓库里通常包含这几个部分:
config.json:模型结构参数,加载时需要读取;model-*.safetensors或pytorch_model.bin:权重文件,体积一般在 6GB 以上;tokenizer*:分词器相关文件;preprocessor_config.json:输入视频预处理参数;README.md:官方的调用说明。
下载的时候别去浏览器里点“下载”按钮,体验太差。HuggingFace 提供了huggingface_hub这个命令行工具,几行就能搞定。
3.2 用 huggingface-cli 拉取权重
先安装并登录:
pip install -U huggingface_hub huggingface-cli login登录的时候会要求填 Access Token,到 HuggingFace 账号 Settings → Access Tokens 里生成一个读权限 token 就行。填完之后用下面的命令把整个仓库拉下来:
huggingface-cli download SpatialLM/Llama-3.2-3B-Instruct-uncensored-spatiallm --local-dir ./models/spatiallm如果网络状况不好,下载到一半经常断,建议加个环境变量启用断点续传:
export HF_HUB_ENABLE_HF_TRANSFER=1 huggingface-cli download SpatialLM/Llama-3.2-3B-Instruct-uncensored-spatiallm --local-dir ./models/spatiallm这个hf_transfer是官方出的高速下载插件,实测对大文件的下载速度提升非常明显。
3.3 国内下载太慢?用镜像源加速
HuggingFace 官方站点在国内的访问速度确实一言难尽,几十 GB 的权重硬拉不现实。好在 HuggingFace 官方社区给了方案:使用hf-mirror.com镜像站点。这不是什么非正规渠道,而是社区维护的官方镜像之一,直接设置环境变量切换下载源即可:
export HF_ENDPOINT=https://hf-mirror.com设置完以后,huggingface-cli和transformers的下载请求都会自动走镜像域名。下载前先确认一下镜像上是否同步了你要的仓库,因为个别新仓库可能同步有延迟。我的实测结果是:走镜像后,6GB 左右的权重文件大概 10 分钟能下完,速度稳定在每秒 8-10MB,基本不需要额外折腾。
提示:切换镜像源只影响 HuggingFace 相关下载,不会改变 PyTorch、CUDA 等其他库的行为,可以放心用。
3.4 写一个最小可用推理入口
模型下载完成后,先在项目目录下确认目录结构正确:
SpatialLM/ ├── models/ │ └── spatiallm/ │ ├── config.json │ ├── model-00001-of-0000X.safetensors │ └── ... ├── inference.py └── ...接着写一个极简入口文件inference.py,先把“模型能加载成功”验证掉:
import torch from transformers import AutoModelForCausalLM, AutoProcessor model_path = "./models/spatiallm" print("Loading processor...") processor = AutoProcessor.from_pretrained(model_path, trust_remote_code=True) print("Loading model...") model = AutoModelForCausalLM.from_pretrained( model_path, torch_dtype=torch.float16, device_map="auto", trust_remote_code=True, ) print("Model loaded successfully.")这一步如果没报错,说明权重文件完整、依赖齐全,可以进入正式的视频推理环节了。
4. 用手机视频跑完整推理流程:拍摄、抽帧、重建、可视化
4.1 拍摄视频的“慢、稳、亮、全”四字诀
输入视频的质量直接决定输出点云的质量,这一步比想象中重要得多。我前后测了室内客厅、办公室桌面、室外墙角三个场景,经验可以浓缩成四个字:
- 慢:手机移动速度一定要慢,每秒大概平移 5-10 厘米,转角度数不超过 5 度;
- 稳:最好是固定在三脚架上做平移旋转,纯手持拍摄抖动太厉害会导致帧间几何估计漂移;
- 亮:环境光要充足,避免大面积的逆光和镜头反光;
- 全:保证想重建的物体在画面中完整出现,不要刚拍到一半就移出镜头。
视频时长建议控制在 15-30 秒,分辨率 720p 足够,太高反而增加解码压力。注意拍完后不要用短视频平台的“动态模板”或者加滤镜,原汁原味的普通视频最合适。
4.2 跑推理前的关键参数调整
官方推理脚本里比较影响结果的是分段帧数和推理尺寸。我用的参数跑下来效果还不错:
python inference.py \ --video_path ./data/living_room.mp4 \ --output_dir ./output/living_room \ --num_frames 8 \ --height 480 \ --width 640 \ --half参数含义拆开说:
| 参数 | 作用 | 建议值 |
|---|---|---|
video_path | 输入视频路径 | 绝对路径更稳 |
output_dir | 输出目录 | 会自动创建 |
num_frames | 从视频中抽取的帧数 | 8 帧左右均衡速度与质量 |
height/width | 推理尺寸 | 480x640 是速度和质量平衡点 |
half | 半精度推理 | 能省一半显存 |
抽取的帧数不是越多越好。num_frames太大(比如 16 帧以上)会让自回归生成序列变长,显存占用快速上升,而且视频里相邻帧非常相似时,增加的输入并不一定能带来明显的几何精度提升。我自己的测试里 8 帧和 12 帧的差异肉眼几乎看不出,但 8 帧的显存占用少了接近 3GB。
4.3 用 Open3D 查看生成的点云
推理完成后,输出目录里会出现一个.ply文件,这就是重建结果。直接用 Open3D 可视化:
import open3d as o3d pcd = o3d.io.read_point_cloud("./output/living_room/scene.ply") o3d.visualization.draw_geometries([pcd])如果你的点云文件带了语义颜色(通常是每个点一个 RGB 值,比如墙面浅灰、地面深灰、物体高亮),在 Open3D 里可以直接看到颜色区分。如果渲染出来是纯白色或者灰蒙蒙一片,检查一下.ply文件的 header 里有没有property float red/green/blue这几列,没有的话说明语义颜色没写进文件,需要用可视化脚本单独做映射。
我实测出来的效果是:客厅点云能清晰看出沙发靠背边界、茶几顶面和地面交界线,墙壁与地面的夹角也比较干净;但玻璃茶几的台面区域会出现明显空洞,说明透明物体仍是这类方法的薄弱点。
4.4 点云后处理:降噪和降采样
直接生成的原始点云通常带一些漂浮的离群点,如果直接拿去给下游用,很容易让机器人的代价地图里多出一些“幽灵障碍物”。我习惯先做一轮统计滤波,再做一个体素降采样:
import open3d as o3d pcd = o3d.io.read_point_cloud("scene.ply") # 统计滤波去掉孤立噪点 cl, ind = pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0) pcd_filtered = pcd.select_by_index(ind) # 体素降采样到 0.02m 分辨率 pcd_down = pcd_filtered.voxel_down_sample(voxel_size=0.02) o3d.io.write_point_cloud("scene_clean.ply", pcd_down)这步做完,点云会干净很多,而且点数减少后,后续跑路径规划或者导入仿真引擎的压力也会小很多。
5. 重建出来的点云如何真正喂给机器人
5.1 导航场景:转成 2D 代价地图或八叉树地图
拿到点云后,如果你做的是轮式机器人导航,最常见的做法是把它投影成 2D 栅格地图,或者转成 OctoMap 直接用。由于点云里已经分出了地面和障碍物,这一步比传统 SLAM 建图要省心:先把地面点云通过 RANSAC 平面拟合提取出来,然后障碍物点云按高度阈值映射到二维栅格。
这里有一个实用命令组合,用 PCL 工具集直接做平面分割:
pcl_plane_segmentation scene_clean.ply plane.ply obstacles.ply \ --axis z --distance_threshold 0.03把地面点云拽掉之后,剩余点云就能用octomap_server直接发到 ROS 的/maptopic,或者用grid_map库转成 costmap。整个链路相当于把“空间语义感知”下沉到了地图构建之前,后续导航规划器只负责在干净的代价地图上跑路径,不用再花大力气处理动态障碍物分类。
5.2 机械臂操作场景:提取物体位姿
如果你做的是机械臂抓取,SpatialLM 输出里的语义标签就派上大用场了。比如场景里有一个“杯子”的聚类,你可以直接用欧式聚类把杯子点云单独切出来,然后计算它的包围盒质心和主轴方向,作为机械臂抓取位姿的候选输入:
import open3d as o3d import numpy as np labels = pcd_down.cluster_dbscan(eps=0.03, min_points=50) for label in np.unique(labels): if label < 0: continue cluster = pcd_down.select_by_index(np.where(labels == label)[0]) bbox = cluster.get_axis_aligned_bounding_box() center = bbox.get_center() size = bbox.get_extent() print(f"Cluster {label}: center={center}, size={size}")拿到包围盒以后,把中心点和姿态发给机械臂的逆解模块,就能做粗对齐。实测下来,对于桌面上的水瓶、玩具车这一类形状规则物体,粗对齐精度足够让吸盘式末端执行器完成抓取;如果是夹爪抓取复杂曲面物体,还需要再用点云配准算法做一遍细对齐。
5.3 进阶玩法:用点云微调模型适配自家场景
官方模型在常见室内场景上表现已经不错,但如果你需要它在特定环境里更“懂行”——比如医院病房、工业货架、甚至某种特殊摆设的厨房——最直接的办法是收集一批自家场景的视频,做一次轻量级微调。常见的做法是 LoRA 微调,只训练模型参数里一小部分低秩矩阵,单卡 24GB 显存就能跑。
微调的数据格式需要把视频抽帧 + 标注点云配对,这一步工作量大但可控。如果你只是想让模型对某类特定物体更敏感,可以用少量样本做偏好优化,不需要把整个模型重训一遍。需要提醒的是:SpatialLM 模型的幻觉率在罕见物体上不低,比如把盆栽认成桌子、把显示器支架认成椅子腿,这类情况只能通过场景数据微调缓解,不能靠改提示词解决。
6. 常见问题与避坑实录
6.1 问题速查表
我把实际跑下来遇到的高频问题整理成一个表,方便直接对照排查:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 显存不足直接 OOM | 输入帧数太多 / 用了全精度 | 调低num_frames,加--half,缩分辨率到 480x640 |
| 模型下载到一半中断 | 网络不稳定 | 设置HF_ENDPOINT=https://hf-mirror.com走镜像 |
| 加载模型时提示 key 不匹配 | 权重文件损坏或版本不匹配 | 删除本地目录,重新用 huggingface-cli 拉取 |
| 输出点云全是地面、主体物体漏重建 | 相机移动太快 / 物体反光 | 重新拍摄,慢速环绕物体移动,避免玻璃、高反光表面 |
| 视频解码失败或帧率为 0 | ffmpeg 不支持编码格式 | 用 ffmpeg 重新压制:ffmpeg -i input.mp4 -vcodec libx264 -crf 23 output.mp4 |
生成的.ply文件在 Open3D 里打不开 | 文件写入不完整 | 检查输出目录磁盘空间,重新推理 |
| CPU 占用异常高、GPU 利用率却很低 | 视频抽帧阻塞 | 预先用 ffmpeg 抽帧,再单独跑模型重建 |
| 点云里出现大量漂浮噪点 | 拍摄时手抖、运动模糊 | 上三脚架,放慢移动速度;后处理加统计滤波 |
6.2 我说一下最容易翻车的两个细节
第一个是权重文件完整性。我在第二次部署时遇到过一次模型加载后推理结果完全乱掉的情况,查了半天发现是上次下载被中断后残留了一个只有几十 MB 的损坏文件,模型加载时居然没报错,但输出就是垃圾。这个非常坑,排查方式是把模型目录清空重下一遍,别心存侥幸。
第二个是输入视频的编码格式。很多手机录出来的默认视频是 HEVC(H.265),部分 Linux 环境没装对应解码器,抽帧时会抽出一堆黑帧或者直接报错。最稳妥的做法是下载完视频后先用 ffmpeg 统一转成 H.264:
ffmpeg -i phone_video.mov -vcodec libx264 -crf 23 -preset fast input.mp4转换不影响视觉内容,但能避免一堆解码相关的隐性问题。
6.3 怎么判断当前点云质量是否够用
判断重建结果能不能给机器人用,我一般看三个指标:
- 几何完整性:墙面、地面、主要物体是不是都在?有没有大面积破洞?
- 语义正确性:点云标签分布是否符合常识?桌子是不是被标成了墙?
- 空间尺度一致性:场景里的桌子高度、门的宽度,和真实环境的误差在不在可接受范围?
其中第三点最容易出问题。SpatialLM 的单目重建结果天生缺少绝对尺度,也就是说它输出的是“比例正确但不知道具体多少米”的点云。要拿到绝对尺度,要么在拍摄时放一个已知尺寸的标定物体(比如 A4 纸、已知边长的小盒子),要么在后期用一个真实距离做全局缩放校准。这一步不做的话,导航路径规划直接使用点云坐标会出大问题。
我个人在实际操作中的体会是:SpatialLM 这类“从普通视频直接出语义点云”的模型,真正改变的是机器人感知系统里“语义建图”的成本结构。以前做语义地图要把语义分割、深度估计、位姿解算串成一条长链路,任何一个环节精度掉链子,后面全部白搭;现在用一个端到端模型直接输出结构化场景描述,虽然细节上还达不到工业级精度,但作为快速原型、仿真预搭建和数据预标注工具,性价比已经高到值得每个做机器人感知的团队都试一遍。
最后再分享一个小技巧:拍摄时除了慢速环绕主物体,记得在视频开头和结尾各停顿两秒,给足模型“观察”稳定帧的时间。这个细节我对比过,有停顿的版本比全程匀速移动的版本,地面平面拟合质量明显更干净,下游做栅格地图投影时少踩很多坑。后面我打算继续试试点云多视角拼接、以及把输出接进机械臂仿真环境做抓取姿态预筛选,等跑通再来补充。