MessyKitchens:接触感知的多目标三维场景重建
一句话总结
从单张图像重建杂乱厨房场景中的多个物体,同时保证物体之间接触合理、无穿透——这是机器人抓取和物理仿真的基础能力。
为什么这个问题重要?
想象一个机器人要从一堆叠放的碗里取出某一个——它不仅需要知道每个碗的形状,还需要知道这些碗是如何接触的,哪些边缘是悬空的,哪些表面有支撑。现有方法在这里会失败,因为它们把”重建”和”物理合理性”当作两个分离的问题。
现有方法的三个核心痛点:
- 孤立重建:NeRF 和 3DGS 擅长整体场景渲染,但无法分解出可以独立操作的物体实例
- 物体穿透:现有的 object-level 方法各自估计位姿,拼在一起后物体互相穿透,在物理引擎里直接爆炸
- 缺乏接触真值:现有数据集(ShapeNet、Objectron)没有精确的接触标注,导致模型无法学习”物体放在桌上是什么样的约束”
MessyKitchens 同时解决了这三个问题:新数据集提供精确接触真值,新方法(Multi-Object Decoder,MOD)实现联合重建与物理约束。
背景知识
3D 物体表示方式对比
| 表示方式 | 优点 | 缺点 | MessyKitchens 适用性 |
|---|---|---|---|
| 点云 | 轻量、易获取 | 无表面连通性,难以做接触判断 | 低 |
| 体素 | 规则、易碰撞检测 | 内存爆炸,分辨率受限 | 低 |
| 隐式 SDF | 连续、支持穿透检测 | 需要网络推理 | 高 |
| 网格 Mesh | 渲染快、物理引擎兼容 | 拓扑固定,难优化 | 高(最终输出) |
MessyKitchens 的方法以 SDF(Signed Distance Field)作为中间表示,最终输出可用于物理引擎的网格。
接触建模的数学基础
设物体 $i$ 的 SDF 为 $\phi_i(\mathbf{x})$(正值在物体外,负值在物体内,零值在表面)。
关键约束:
- 非穿透约束:对任意空间点 $\mathbf{x}$,至多一个物体的 SDF 为负值,即 $\min(\phi_i(\mathbf{x}), \phi_j(\mathbf{x})) \geq 0$
- 接触约束:接触点 $\mathbf{x}^$ 满足 $\phi_i(\mathbf{x}^) = 0$ 且 $\phi_j(\mathbf{x}^*) = 0$(两个物体表面同时经过该点)
- 法线一致性:接触点处两物体法线方向满足 $\nabla\phi_i(\mathbf{x}^) \cdot \nabla\phi_j(\mathbf{x}^) \approx -1$(面对面接触)
核心方法
直觉解释
SAM 3D 的思路是:给定单张图,先分割出物体掩码,再用形状先验重建单个物体。问题在于,多个物体独立重建后拼在一起会互相穿透。
MOD(Multi-Object Decoder)的核心创新在于联合解码:不是一个物体一个物体地重建,而是把所有可见物体的特征同时喂给 decoder,让网络在输出阶段就考虑物体间的空间关系。
单张 RGB 图像
↓
实例分割 (SAM)
↓ 每个物体的掩码 + 裁剪图
Per-Object 特征提取 (ViT backbone)
↓ 特征序列 [f₁, f₂, ..., fₙ]
Multi-Object Decoder (MOD) ← 关键!跨物体注意力
↓ 联合位姿 + 形状输出
物理约束优化
↓ 非穿透 + 接触精化
最终场景网格
关键公式
联合重建的目标函数:
\[\mathcal{L} = \mathcal{L}_\text{shape} + \lambda_1 \mathcal{L}_\text{pose} + \lambda_2 \mathcal{L}_\text{contact} + \lambda_3 \mathcal{L}_\text{penetration}\]其中穿透惩罚项定义为:
\[\mathcal{L}_\text{penetration} = \sum_{i \neq j} \sum_{\mathbf{x} \in \mathcal{S}_i} \max(0, -\phi_j(\mathbf{x}))^2\]对物体 $i$ 表面上的采样点 $\mathbf{x}$,惩罚其落入物体 $j$ 内部($\phi_j < 0$)的程度。
接触项鼓励语义上应该接触的物体真正接触:
\[\mathcal{L}_\text{contact} = \sum_{(i,j) \in \mathcal{C}} \left( \min_{\mathbf{x}} \left[ \phi_i(\mathbf{x})^2 + \phi_j(\mathbf{x})^2 \right] \right)\]其中 $\mathcal{C}$ 是语义上应该接触的物体对集合(从图像语义推断)。
实现
核心:穿透检测与量化
import torch
import numpy as np
def compute_penetration_depth(sdf_i, sdf_j, surface_points_i, surface_points_j):
"""
计算两个物体之间的穿透深度
sdf_i, sdf_j: 各自的 SDF 函数 (callable)
surface_points_*: 表面采样点 [N, 3]
返回: 穿透深度标量,0 表示无穿透
"""
# 物体 i 的表面点在物体 j 内部的深度
phi_j_at_i = sdf_j(surface_points_i) # [N]
phi_i_at_j = sdf_i(surface_points_j) # [M]
# 穿透:SDF 为负值(在物体内部)
penetration_i = torch.clamp(-phi_j_at_i, min=0) # [N]
penetration_j = torch.clamp(-phi_i_at_j, min=0) # [M]
# 最大穿透深度
max_pen = max(penetration_i.max().item(), penetration_j.max().item())
# 平均穿透体积(用于 loss)
mean_pen = (penetration_i.mean() + penetration_j.mean()) / 2
return max_pen, mean_pen
def find_contact_points(sdf_i, sdf_j, bbox, resolution=64, threshold=0.005):
"""
在给定包围盒内搜索两物体的接触点
接触定义:两物体 SDF 均接近 0
"""
# 在 bbox 内均匀采样
x = torch.linspace(bbox[0], bbox[3], resolution)
y = torch.linspace(bbox[1], bbox[4], resolution)
z = torch.linspace(bbox[2], bbox[5], resolution)
grid = torch.stack(torch.meshgrid(x, y, z, indexing='ij'), dim=-1)
pts = grid.reshape(-1, 3) # [N, 3]
phi_i = sdf_i(pts) # [N]
phi_j = sdf_j(pts) # [N]
# 两个物体表面附近的点
near_surface_i = phi_i.abs() < threshold
near_surface_j = phi_j.abs() < threshold
contact_mask = near_surface_i & near_surface_j
contact_points = pts[contact_mask]
return contact_points # [K, 3]
Multi-Object Decoder 骨架
import torch
import torch.nn as nn
class MultiObjectDecoder(nn.Module):
"""
MOD: 联合解码多个物体的位姿和形状
核心是跨物体的 cross-attention,让每个物体感知其邻居
"""
def __init__(self, feat_dim=512, n_heads=8, n_layers=3, latent_dim=256):
super().__init__()
# 跨物体注意力:每个物体 query 其他物体的特征
self.cross_attn_layers = nn.ModuleList([
nn.MultiheadAttention(feat_dim, n_heads, batch_first=True)
for _ in range(n_layers)
])
self.ffn_layers = nn.ModuleList([
nn.Sequential(
nn.Linear(feat_dim, feat_dim * 2),
nn.GELU(),
nn.Linear(feat_dim * 2, feat_dim)
) for _ in range(n_layers)
])
# 输出头:位姿 (9-DoF) + 形状 latent
self.pose_head = nn.Linear(feat_dim, 9) # 3 trans + 6 rot
self.shape_head = nn.Linear(feat_dim, latent_dim)
def forward(self, object_feats):
"""
object_feats: [B, N_obj, feat_dim] N_obj 个物体的特征
返回: poses [B, N_obj, 9], shape_codes [B, N_obj, latent_dim]
"""
x = object_feats
for attn, ffn in zip(self.cross_attn_layers, self.ffn_layers):
# 每个物体 attend 到所有其他物体(包括自身)
attended, _ = attn(x, x, x)
x = x + attended # 残差
x = x + ffn(x) # FFN 残差
poses = self.pose_head(x) # [B, N_obj, 9]
shape_codes = self.shape_head(x) # [B, N_obj, latent_dim]
return poses, shape_codes
物理约束优化
def physics_refinement(initial_poses, sdf_models, n_iters=100, lr=1e-3):
"""
在初始位姿基础上,用物理约束做后处理优化
这是 inference-time 优化,不需要重新训练
"""
# 位姿作为可优化参数
poses = [p.clone().requires_grad_(True) for p in initial_poses]
optimizer = torch.optim.Adam(poses, lr=lr)
for it in range(n_iters):
optimizer.zero_grad()
total_loss = 0.0
# 穿透惩罚
for i in range(len(poses)):
for j in range(i + 1, len(poses)):
# 在物体 i 位姿下的表面点
pts_i = transform_surface_samples(sdf_models[i], poses[i])
# 计算这些点在物体 j SDF 中的值
phi_j = sdf_models[j].query(pts_i, poses[j])
pen = torch.clamp(-phi_j, min=0).mean()
total_loss += pen
total_loss.backward()
optimizer.step()
return [p.detach() for p in poses]
可视化:接触点与穿透区域
import open3d as o3d
def visualize_scene_with_contacts(meshes, contact_points, penetration_regions=None):
"""
可视化场景:物体网格 + 接触点 + 穿透区域(红色高亮)
"""
geometries = []
# 为每个物体分配颜色
colors = [[0.2, 0.6, 0.9], [0.9, 0.6, 0.2], [0.2, 0.9, 0.4],
[0.8, 0.2, 0.8], [0.9, 0.9, 0.2]]
for i, mesh in enumerate(meshes):
mesh.paint_uniform_color(colors[i % len(colors)])
geometries.append(mesh)
# 接触点:黄色大球
if contact_points is not None and len(contact_points) > 0:
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(contact_points)
pcd.paint_uniform_color([1.0, 1.0, 0.0])
geometries.append(pcd)
# 穿透区域:红色高亮(仅 debug 用)
if penetration_regions is not None:
pen_pcd = o3d.geometry.PointCloud()
pen_pcd.points = o3d.utility.Vector3dVector(penetration_regions)
pen_pcd.paint_uniform_color([1.0, 0.0, 0.0])
geometries.append(pen_pcd)
o3d.visualization.draw_geometries(
geometries,
window_name="MessyKitchens Scene Reconstruction",
width=1280, height=720
)
实验
数据集说明
MessyKitchens 与现有数据集的关键区别在于接触精度:
| 数据集 | 场景类型 | 接触真值 | 物体密度 | 遮挡程度 |
|---|---|---|---|---|
| ShapeNet | 合成单物体 | 无 | 单物体 | 无 |
| YCB-Video | 桌面场景 | 近似 | 中等 | 中等 |
| ScanNet | 室内场景 | 无 | 高 | 高 |
| MessyKitchens | 厨房杂乱场景 | 高精度 | 极高 | 极高 |
数据集核心指标对比(论文报告):
| 数据集 | Registration RMSE (mm) ↓ | Inter-object Penetration (cm³) ↓ |
|---|---|---|
| 前代数据集 | ~8.2 | ~12.4 |
| MessyKitchens | ~2.1 | ~0.8 |
定量评估
| 方法 | CD ↓ (cm) | Pose Error ↓ (°) | Penetration ↓ (cm³) | Contact F1 ↑ |
|---|---|---|---|---|
| SAM 3D (单物体) | 2.8 | 12.3 | 18.6 | 0.34 |
| Instant3D | 3.1 | 14.7 | 21.2 | 0.28 |
| MOD (本文) | 1.9 | 8.4 | 3.2 | 0.61 |
穿透体积从 18.6 降到 3.2 cm³,Contact F1 从 0.34 提升到 0.61——这两个数字最能说明方法的价值。
工程实践
实际部署考虑
计算量:单张图推理时间约 2-4 秒(V100),物体数量线性影响 Decoder 部分(注意力计算 $O(N^2)$)。超过 20 个物体的密集场景需要分块处理。
内存:每个物体的 SDF 网络约 20MB,10 个物体的场景 ~200MB GPU 内存,在 RTX 3090 上完全可行。
输出格式:最终输出标准 .obj 或 .ply 格式,可直接导入 PyBullet、MuJoCo、Isaac Sim。
接触检测的常见坑
坑 1:接触阈值不统一
# 错误:阈值是物理单位,要随场景尺度变化
CONTACT_THRESHOLD = 0.005 # 固定值,小场景 OK,大场景完全漏掉接触
# 正确:相对于物体尺寸归一化
def adaptive_threshold(mesh_i, mesh_j, ratio=0.01):
scale_i = mesh_i.get_axis_aligned_bounding_box().get_extent().max()
scale_j = mesh_j.get_axis_aligned_bounding_box().get_extent().max()
return min(scale_i, scale_j) * ratio
坑 2:SDF 查询坐标系混乱
# 错误:忘记把世界坐标系点变换到物体局部坐标系
phi = sdf_model.query(world_points) # 结果完全错误
# 正确:先逆变换到物体局部坐标系
def query_sdf_world(sdf_model, pose, world_points):
T_inv = torch.linalg.inv(pose) # 4x4 变换矩阵的逆
local_points = (T_inv[:3, :3] @ world_points.T).T + T_inv[:3, 3]
return sdf_model.query(local_points)
坑 3:物理优化陷入局部最优
推理时的物理精化优化对初始位姿很敏感。当物体严重遮挡时,MOD 输出的初始位姿误差可能超过优化盆地半径,直接用 Adam 跑 100 步无法收敛。解决方案是先做粗粒度的随机重启(n_restarts=5),再做精细优化。
什么时候用 / 不用?
| 适用场景 | 不适用场景 |
|---|---|
| 机器人抓取规划(需要精确接触) | 实时场景重建(2-4秒/帧不够) |
| 物理仿真数据生成 | 动态场景(物体运动中) |
| 稀疏视图场景理解 | 非刚体物体(布料、液体) |
| 数字孪生构建 | 极端光照(镜面厨具反光严重) |
| 机器人学习的演示数据 | 物体超过 30 个的大型场景 |
与其他方法对比
| 方法 | 优点 | 缺点 | 核心适用场景 |
|---|---|---|---|
| NeRF / 3DGS | 渲染质量高,几何细节好 | 无物体分解,无物理约束 | 场景外观重建、新视角合成 |
| BundleSDF | 视频序列重建单物体,鲁棒 | 单物体,无接触处理 | 机器人操作前的单物体扫描 |
| SAM 3D | 单张图多物体,速度快 | 各自独立,穿透严重 | 快速场景理解(精度要求不高) |
| MOD (本文) | 联合推理,接触准确,物理合理 | 慢(2-4s),强依赖形状先验 | 需要物理正确的机器人学习场景 |
我的观点
这个方向的价值被低估了。 大多数 3D 重建工作只关注渲染质量(PSNR/SSIM),但对机器人来说,”物体放在哪里、怎么接触”比”渲染出来好不好看”重要得多。MessyKitchens 把评估标准从视觉相似度转移到物理正确性,这是正确的方向。
离实际应用的距离:推理时间是主要瓶颈,目前 2-4 秒/帧适合离线规划,不适合在线感知。随着 ViT 加速(flash attention、量化)和形状先验的泛化能力提升,2 年内实现实时化是可期的。
值得关注的开放问题:
- 非刚体接触(堆叠的软袋、变形的包装)
- 透明物体(厨房里的玻璃杯是硬伤)
- 接触动态变化时的增量更新(不需要每帧全量重建)
- 与 LLM 的结合:用语言描述”这个杯子在那个盘子旁边”来初始化接触先验
官方代码和数据集:messykitchens.github.io,论文链接:arxiv.org/abs/2603.16868
Comments