SIMART:把单体网格拆成可仿真的关节资产

SIMART:把单体网格拆成可仿真的关节资产

一句话说清楚:SIMART 把一个只有外形、没有“哪里能动”信息的 3D 网格,变成“分好零件的高保真网格 + 关节/物性说明书(URDF/JSON)”;关键做法是让 MLLM 在稀疏 3D token、图像和文字上联合推理,再把它预测的部件种子投回原始网格,而不是重新生成一份粗糙网格。

先看结论

  • 输入:一个原始单体网格、它的 RGB 渲染图,以及任务指令。
  • 输出:每个功能部件的分割网格;部件之间的父子关系、关节类型/轴/原点/限位;尺度和若干物性字段。
  • 核心直觉
    1. 空体素绝大多数没有信息,先用 Sparse 3D VQ-VAE 只保留有表面的格子。
    2. 图像擅长回答“这是什么、哪个是把手”,3D token 擅长回答“它在空间哪里”,让 Qwen3-VL 一起看。
    3. MLLM 只需生成部件的稀疏几何线索;最终三角面仍从输入网格继承,所以细节和纹理不被生成器抹掉。
    4. 结构化输出直接变成 URDF 的 links、joints 和 limits。
  • 真正的创新点不是单独的分割器或单独的关节回归器,而是“稀疏几何表示 + 语义/几何/运动联合生成 + 原网格高保真回填”这条闭环。

1. 问题是什么

1.1 静态网格为什么还不能直接进仿真

常见的生成模型会给出一个漂亮的 GLB/OBJ,但它通常是一个整体。机器人或物理引擎还需要知道:

  • 哪些三角面属于底座、门、抽屉、把手等功能部件;
  • 部件之间的父子层级;
  • 关节是旋转(revolute)、平移(prismatic)还是固定;
  • 关节轴、原点和运动范围;
  • 尺度、密度、摩擦等仿真参数。

缺少这些信息时,网格只能“看起来像门”,不能真的绕铰链打开。

1.2 形式化任务

把输入写成三种模态:

\[\mathcal I=\{I_{\mathrm{vis}},G_{\mathrm{geo}},T_{\mathrm{txt}}\}, \]

其中 \(I_{\mathrm{vis}}\) 是 RGB 图像,\(G_{\mathrm{geo}}\) 是原始网格,\(T_{\mathrm{txt}}\) 是任务指令(例如“把物体拆成不同功能部件并给出物性”)。

目标是生成仿真资产:

\[\mathcal A=(\mathcal M_{\mathrm{seg}},\mathcal P_{\mathrm{sim}}). \]

\(\mathcal M_{\mathrm{seg}}=\{m_1,\ldots,m_n\}\) 是分部件网格;\(\mathcal P_{\mathrm{sim}}\) 是运动和物理元数据。这里的“生成”有一个重要含义:SIMART 生成的是部件归属和运动说明,细节网格尽量沿用输入,而不是从零采样三角面。

1.3 为什么不用一条传统流水线

单独的“先分割、再猜关节、最后拼装”容易出现因果断裂:分割边界不一定符合机械运动,猜出的轴也可能穿不过对应零件。直接从图像回归 URDF 则常牺牲几何细节。另一个瓶颈是密集体素:\(64^3\) 网格里大量位置是空的,却会占用 MLLM 的上下文和显存。

2. 一张图看懂全流程

flowchart TD A["原始单体网格"] --> B["坐标对齐与归一化"] B --> C["64×64×64 表面体素"] C --> D["Sparse 3D VQ-VAE"] D --> E["稀疏序列:坐标 + K"] B --> F["RGB 等距渲染图"] E --> G["Qwen3-VL-8B 联合推理"] F --> G H["任务指令"] --> G G --> I["部件稀疏 token + 结构化 JSON"] I --> J["VQ-VAE 解码为部件种子"] B --> K["同一份高保真原网格"] J --> L["高斯种子 + 网格邻接平滑"] K --> L L --> M["分部件网格"] I --> N["父子关系、关节和物性"] M --> O["URDF 组装"] N --> O O --> P["可交互资产"]

读图时可以把它分成两条路:上方是“理解并做决定”的 MLLM 路径,下方是“把决定落实到原网格”的几何后端。两条路最后在 URDF 处汇合。

3. 方法原理:从整体到细节

3.1 整体框架:谁负责什么

SIMART 把职责分得很清楚:

部分 负责的事情 不负责的事情
Sparse 3D VQ-VAE 把表面几何压成少量带坐标的离散 token,并能解码回占据体素 不理解“这是门还是抽屉”
RGB/ViT 提供外观和类别语义 不单独决定 3D 面的边界
Qwen3-VL-8B 联合判断部件、功能、父子关系和关节参数 不直接输出最终三角面
几何后端 把部件 token 变成种子,再在原网格上分面 不重新理解自然语言
URDF 组装器 把分件和运动参数组织成仿真描述 不纠正错误的语义预测

因此,SIMART 更准确的理解是:MLLM 做结构化决策,确定性几何程序做高保真落地

3.2 核心模块 A:Sparse 3D VQ-VAE

它负责什么

把一个归一化后的 \(64\times64\times64\) 表面占据网格,编码成一个最多 \(8\times8\times8\) 的粗网格;只把有几何的粗格子序列化给 MLLM。

为什么需要它

如果把所有 \(512\) 个粗格子都写入序列,模型会反复处理“这里是空的”。但去掉空格后,序列失去位置顺序:模型知道“有一个 token”,却不知道它在左上还是右下。因此 SIMART 同时做两件事:

  1. 用专门的零码表示空区域,让解码器知道缺省值是什么;
  2. 给每个保留下来的 token 显式附上 \((x,y,z)\) 坐标。

具体怎么做

输入先是表面体素张量:

\[X\in\{0,1\}^{1\times64\times64\times64}. \]

3D-UNet 编码器把它变成 \(16\times16\times16\)、通道数为 \(8\) 的潜表示。将每个 \(2\times2\times2\) 邻域内的 8 个空间位置、各自的 8 个通道重排并拼接后,得到:

\[Z\in\mathbb R^{8\times8\times8\times64}. \]

码本为 \(\mathcal C=\{\mathbf e_0,\ldots,\mathbf e_{4095}\}\),其中 \(\mathbf e_0\) 约定为 zero token。令 \(o_i\) 表示粗格子是否被表面占据,量化意图可写成:

\[\hat z_i= \begin{cases} \mathbf e_0,&o_i=0,\\ \displaystyle\arg\min_{\mathbf e_j\in\mathcal C\setminus\{\mathbf e_0\}} \lVert z_i-\mathbf e_j\rVert_2,&o_i=1. \end{cases} \]

空格不进入 MLLM 序列;有表面的格子写成三个原子 token:

\[\langle\mathrm{voxel}\rangle\ \ [xyz]\ \ [K], \qquad xyz=64x+8y+z, \]

其中 \(x,y,z\in[0,7]\)\(K\in[0,4095]\) 是码本索引。显式坐标使可变长度序列仍然保留拓扑位置。

解码时,把未出现的粗格子填回 index \(0\),查码本并逆重排回 \(16^3\times8\),再由对称的 3D-UNet 恢复 \(64^3\) 占据预测。实际送给后端的是预测占据点的坐标,称为部件 seed。

训练 VQ-VAE 的必要目标包括二元交叉熵(BCE)重建项和 VQ 的两部分损失:

\[\mathcal L_{\mathrm{total}} =\mathcal L_{\mathrm{rec}}(G_{\mathrm{geo}},\hat G_{\mathrm{geo}}) +\lVert\operatorname{sg}[E(G_{\mathrm{geo}})]-\hat z\rVert_2^2 +\beta\lVert E(G_{\mathrm{geo}})-\operatorname{sg}[\hat z]\rVert_2^2, \]

其中 \(\operatorname{sg}\) 表示停止梯度。重建项让几何不丢,commitment 项让编码器和码本彼此稳定。

输入、输出和形状

阶段 形状/对象 含义
输入 \(1\times64\times64\times64\) 表面占据
编码器输出 \(8\times16\times16\times16\) 低分辨率潜特征
聚合后 \(8\times8\times8\times64\) 每个粗格一个 64 维向量
量化结果 \(8\times8\times8\) 个整数 每格一个 \(K\)
序列化 \(N_{\mathrm{occ}}\) 组三元 token 仅保留占据格
解码输出 \(1\times64\times64\times64\) 可变成部件 seed 点云

稀疏主要发生在“占据判断、量化选择和序列化”这层;编码器本身仍可在规则体素张量上计算。典型场景下,送入 MLLM 的 token 数比密集方案减少约 \(70\%\),但这个比例随物体占空比变化。

伪代码

函数 EncodeMesh(mesh):
    mesh64 ← 将网格表面体素化为 64×64×64
    coarse ← 对 mesh64 做 8×8×8 的最大池化  # 判断每个粗格是否有表面
    latent16 ← 3D_UNet_Encoder(mesh64)
    latent8 ← 将每个 2×2×2 邻域重排成 64 维向量

    对每个粗格 i:
        如果 coarse[i] == 空:
            index[i] ← 0                     # zero token
        否则:
            index[i] ← 与 latent8[i] 最近的码本索引 K

    sequence ← 空列表
    对每个 occupied 粗格 (x, y, z):
        xyz ← 64*x + 8*y + z
        追加 "<voxel> xyz index[x,y,z]" 到 sequence
    返回 sequence

函数 DecodePart(sequence):
    grid ← 8×8×8 全部填 0
    读取 sequence 中每个 "<voxel> xyz K"
    将 K 写回 xyz 对应位置
    quantized ← 查码本并逆重排
    occupancy64 ← 3D_UNet_Decoder(quantized)
    返回 occupancy64 中的非空点作为 seed

教学示例(数字为说明用)

假设一个物体只有两个粗格有表面:

  • \((x,y,z)=(1,2,3)\),码本索引 \(K=17\)
  • \((x,y,z)=(6,0,4)\),码本索引 \(K=302\)

它们的线性坐标分别是:

\[64\times1+8\times2+3=83,\qquad 64\times6+8\times0+4=388. \]

所以序列可以写成:

<voxel> 83 17 <voxel> 388 302

其余 \(510\) 个粗格不必写出来,解码时按 zero token 补回。注意 \(K\) 不是世界坐标;它描述该粗格附近的局部几何,位置由 \(xyz\) 单独承担。

示意图

flowchart TD A["64³ 表面占据"] --> B["3D-UNet 编码"] B --> C["16³ × 8 潜特征"] C --> D["2×2×2 邻域聚合"] D --> E["8³ × 64"] E --> F{"粗格是否占据"} F -- "否" --> G["index 0:zero"] F -- "是" --> H["码本最近邻:K"] G --> I["只序列化有表面格"] H --> I I --> J["voxel + xyz + K"] J -. "补零并解码" .-> K["64³ 占据 / seed"]

这张图强调了两个互补设计:zero token 负责“空白如何重建”,坐标 token 负责“非空内容在哪里”。

3.3 核心模块 B:统一 MLLM 的多模态联合推理

它负责什么

让一个 Qwen3-VL-8B 同时完成两类任务:

  1. object2urdf:拆分所有功能部件,并预测运动和物性;
  2. part grounding:给定“门/把手/抽屉”等文字描述,只找出对应的几何 token。

为什么需要三种模态

只看网格,两个外形相似的凸起可能无法区分“把手”和“装饰条”;只看图片,又难以精确指出 3D 位置。文字指令则决定模型当前要回答的是整机结构还是某个部件。三路信息互相补足:

  • 图像提供外观、类别和常识;
  • 坐标化几何 token 提供空间落点;
  • 指令规定输出格式和任务焦点。

论文把它描述成三路特征进入统一 Transformer:

\[F_{\mathrm{vis}}\in\mathbb R^{N_v\times D},\quad F_{\mathrm{geo}}\in\mathbb R^{N_g\times D},\quad F_{\mathrm{txt}}\in\mathbb R^{N_t\times D}, \]

总长度为 \(L=N_v+N_g+N_t\)。公开推理流程的具体落地方式是:把“voxel xyz K”作为带特殊标记的几何文本段,与图像和用户问题一起交给 Qwen 处理器;因此可以把它理解为“概念上的三模态融合,工程上以坐标+离散索引文本注入”。

输出长什么样

object2urdf 输出一个严格 JSON。字段的作用如下:

{
  "object_captions": {
    "name": "物体名称",
    "scale": "真实尺度或尺度数值"
  },
  "parts_captions": {
    "0": {
      "type": "fixed",
      "material": "Plastic",
      "density": "1.2 g/cm^3"
    },
    "1": {
      "type": "revolute",
      "parent": "0",
      "center": [100, 138, 101],
      "axis": [100, 0, 0],
      "limits": [-54, 45],
      "material": "Plastic",
      "density": "1.2 g/cm^3"
    }
  },
  "parts_voxels": {
    "0": "<voxel> xyz K ...",
    "1": "<voxel> xyz K ..."
  }
}

完整模板还可为每个部件填写 Young's Modulus、Poisson's Ratio,以及任务需要的密度和接触相关字段;这些字段与几何 token 分开,属于仿真元数据。

parts_voxels 是“这个部件有哪些稀疏几何 token”,parts_captions 是“这个部件怎样运动、属于谁、是什么材料”。常见运动类型及其后续含义是:
parts_voxels 是“这个部件有哪些稀疏几何 token”,parts_captions 是“这个部件怎样运动、属于谁、是什么材料”。常见运动类型及其后续含义是:

MLLM 类型 直观含义 URDF 中的常见映射
fixed 静止基座或刚性部件 fixed
revolute 绕轴转动 revolute
hinge 铰链式转动 revolute
prismatic 沿轴平移 prismatic
free 无约束自由运动 floating
rigid 刚性连接 fixed

中心通常量化为 \(200\) 个 bin。若离散值为 \(b\),归一化坐标区间为 \([-0.5,0.5]\),则 bin 中心可写为(公开代码实际把输入夹在 1–200):

\[c(b)=-0.5+\frac{b-\tfrac12}{200}. \]

轴用乘以 \(100\) 的整数向量表达,再还原为方向;限位也是归一化数值,最后由 URDF 写出器转换成弧度或距离单位。

grounding 任务的约束更简单:输出的 voxel token 必须是输入序列的子集。这样“找部件”不会凭空生成一个输入中不存在的空间区域。

伪代码

函数 JointReason(image, geometry_sequence, instruction):
    image_features ← ViT(image)
    geometry_features ← 将 "<voxel> xyz K" 编成几何文本/特征
    text_features ← 编码 instruction
    context ← 拼接三种模态

    如果 instruction 是 object2urdf:
        生成 object_captions
        对每个功能部件:
            生成 parts_captions[part_id]
            生成 parts_voxels[part_id]
        返回严格 JSON

    如果 instruction 是 part_grounding:
        从输入 geometry_sequence 中选择与描述匹配的 token
        返回这些 token 的子集

教学示例(数字为说明用)

给模型看一个带盖储物盒的渲染图,并附上稀疏序列。它可能把:

  • 0 解释为盒体,type=fixed;
  • 1 解释为盖子,parent=0、type=revolute;
  • 把盖子 token 放进 parts_voxels["1"];
  • 给出一个沿 \(x\) 方向的轴和开合限位。

如果问题改成“找出盖子的功能部件”,模型不必输出 URDF,只需从整物体 token 中复制属于盖子的那一部分。这种“同一表示支撑两种问法”是联合框架的一个实用好处。

示意图

sequenceDiagram participant V as RGB 图像 participant G as 稀疏几何序列 participant T as 任务指令 participant M as Qwen3-VL-8B participant O as JSON 或 token 子集 V->>M: ViT 视觉上下文 G->>M: voxel xyz K T->>M: object2urdf 或 grounding M->>O: 部件几何 + 运动/物性

图中的关键不是把三个输入简单拼接,而是让同一次推理同时约束“语义部件”和“空间 token”,从而减少分割与关节预测互相打架。

3.4 核心模块 C:从部件 token 到原始高保真网格

它负责什么

MLLM 只给出稀疏 token,分辨率仍然是粗的。这个模块把每个部件 token 解码成 seed 点云,再在原始网格上决定每个顶点和三角面的归属。

为什么不直接把解码体素当最终网格

\(64^3\) 占据体素足以指出“盖子大概在上面”,却不一定保留原网格的倒角、纹理和细小孔洞。如果直接导出体素表面,仿真外观会变粗。把 seed 当作“软提示”,把原始网格当作“最终几何”,就能兼顾语义定位和几何保真。

具体怎么做

设部件 \(p\) 的解码 seed 集合是 \(S_p\),原网格顶点是 \(v\)。先计算顶点到该部件最近 seed 的距离 \(d(v,S_p)\),并用高斯核初始化归属概率:

\[P_0(v,p)= \frac{\exp\left(-\frac{d(v,S_p)^2}{2\sigma^2}\right)} {\sum_q\exp\left(-\frac{d(v,S_q)^2}{2\sigma^2}\right)}. \]

\(\sigma\) 与网格包围盒尺度成比例。随后利用网格边构成的邻接图 \(W\) 做平滑:

\[P_{t+1}=(1-\lambda)P_t+\lambda W P_t, \]

其中 \(W\) 的每一行归一化为邻居平均。平滑让相邻顶点倾向于拥有一致标签,减少锯齿和孤立碎片。最后:

  1. 每个顶点取概率最大的部件;
  2. 每个三角面按三个顶点多数投票;
  3. 若某个预测部件完全没有面,用离其 seed 质心最近的面开始 BFS,补出一个最小连通片;
  4. 从原网格导出分件,保留可继承的原始外观信息。

输入、输出和关键量

输入/中间量 形状/含义
原始网格 顶点 \(V\times3\)、面 \(F\times3\)
每部件解码结果 seed 集合 \(S_p\),形状约为 \(M_p\times3\)
顶点概率 \(V\times P\)
顶点标签 长度 \(V\)
面标签 长度 \(F\)
输出 每个部件的 OBJ/网格子集

伪代码

函数 ProjectSeedsToMesh(original_mesh, part_sequences):
    对每个部件 p:
        seeds[p] ← DecodePart(part_sequences[p])

    对每个原网格顶点 v:
        对每个部件 p:
            d[p] ← 最近 seed 到 v 的距离
            prob[v,p] ← exp(-d[p]^2 / (2*sigma^2))
        归一化 prob[v,:]

    重复 smooth_iter 次:
        prob ← (1-lambda)*prob + lambda*邻接图邻居平均(prob)

    vertex_label[v] ← argmax(prob[v,:])
    对每个三角面 f:
        face_label[f] ← 三个顶点标签的多数票

    对每个完全没有面的部件:
        从最近面开始沿面邻接图 BFS 补到默认的 100 面

    按 face_label 从 original_mesh 提取并导出各部件
    返回分件网格

教学示例(数字为说明用)

想象一条由四个相邻顶点组成的“盒盖边缘”:

v0 —— v1 —— v2 —— v3

盖子 seed 靠近 \(v_0,v_1\),盒体 seed 靠近 \(v_2,v_3\)。单纯最近邻会得到“盖子、盖子、盒体、盒体”。如果某个边界顶点因为网格噪声暂时偏向盒体,邻接平滑会把两侧的局部证据传播给它,使边界更连续。之后三角面再做多数投票,最终仍然使用原网格的三角形,而不是用四个点重新造面。

示意图

flowchart LR A["部件 voxel token"] --> B["VQ-VAE 解码"] B --> C["部件 seed 点云"] D["原始高保真网格"] --> E["顶点到 seed 距离"] C --> E E --> F["高斯归属概率"] F --> G["网格邻接图平滑"] G --> H["顶点 argmax"] H --> I["三角面多数投票"] I --> J["分部件网格"]

可以把 seed 理解成“在原网格上撒的彩色图钉”:图钉告诉程序每个功能部件大概落在哪里,真正的表面细节仍来自原网格。

3.5 核心模块 D:把结构化结果组装成 URDF

它负责什么

把分件网格变成 URDF 的 links,把 parts_captions 变成 joints,并把中心、轴和限位转换到仿真引擎使用的形式。

为什么需要显式的运动树

“盖子属于盒子”不仅是语义关系,还决定变换传播:盒子固定时,盖子绕自身关节轴转;如果还有抽屉,抽屉应挂在盒体或相应父 link 下。parent 字段把这些关系组织成树。

具体怎么做

对每个部件创建一个 link,挂上 visual 和 collision 网格。对每个非基座部件创建 joint:

  • parent 指向父 link;
  • child 指向当前 link;
  • origin 是量化中心还原后的连续坐标;
  • axis 是缩放后的方向向量;
  • limits 对旋转关节转成角度单位,对平移关节转成距离单位。

为了让部件围绕关节中心运动,写出器还会把子 link 的网格原点平移到关节中心的相对坐标。

伪代码

函数 BuildURDF(parts_mesh, parts_captions):
    创建 robot
    对每个部件 p:
        创建 link l_p
        link.visual ← parts_mesh[p]
        link.collision ← parts_mesh[p]

    以 part 0 作为 base;若不存在则取首个部件
    对每个非 base 部件 p:
        meta ← parts_captions[p]
        joint_type ← 将 hinge/free/rigid 映射到 URDF 类型
        创建 joint(parent=meta.parent, child=l_p)
        joint.origin ← 解码 meta.center
        joint.axis ← 按 100 的比例还原 meta.axis
        如果 joint_type 是 revolute 或 prismatic:
            写入 meta.limits
        将 l_p 的网格原点移到关节中心的相对位置

    输出 URDF XML

教学示例(数字为说明用)

考虑两个部件:

link l_0:盒体,fixed
link l_1:盒盖,revolute,parent=0
axis=[100, 0, 0]
center=[100, 138, 101]
limits=[-54, 45]

这里 [100,0,0] 表示大致沿 \(x\) 轴;center 是离散坐标,不是直接的米数。按 \(200\) 个 bin 的归一化规则,[100,138,101] 的连续中心约为 [-0.0025, 0.1875, 0.0025](这是教学换算)。URDF 最终会有 l_0、l_1 和一个连接它们的旋转 joint。

示意图

flowchart TD A["l_0:盒体 base"] -->|"revolute,沿 x 轴"| B["l_1:盒盖"] A -->|"prismatic"| C["l_2:抽屉或滑轨"] D["parts_captions"] --> E["URDF joints"] F["分件 OBJ"] --> G["URDF links"] E --> H["仿真资产"] G --> H

这棵树把“哪个部件跟着哪个部件动”写成了仿真器可执行的变换关系。

3.6 训练与推理:两个阶段、一次生成

VQ-VAE 训练

Sparse 3D VQ-VAE 先在约 \(500\,000\) 个、遵循 TRELLIS 数据分布的物体子集上预训练/适配,采用重建和 VQ 损失。论文报告的实现从 TRELLIS VAE 初始化,随后进行两个阶段、每阶段 \(60\,000\) steps 的训练,使用 8 张 A100。

MLLM 指令微调

MLLM 使用 Qwen3-VL-8B。训练集合包含约 \(39\,600\) 个物体:

  • \(5\,600\) 个 articulated 模型;
  • \(34\,000\) 个静态模型,用来补足形状和类别常识;
  • 每个 articulated 模型渲染约 20 个运动状态;
  • object2urdf 和 part grounding 各构造约 \(960\,000\) 个 QA 对。

监督目标不是一张独立的分割 mask,而是“稀疏部件 token + 结构化文字字段”。论文报告 MLLM 微调 30,000 steps、使用 32 张 A100;没有另行提出一个新的 MLLM 几何损失。

训练流程示意

flowchart LR A["500k 几何物体"] --> B["Sparse VQ-VAE"] B --> C["重建损失 + VQ 损失"] D["39.6k 物体与运动状态"] --> E["URDF QA + grounding QA"] E --> F["Qwen3-VL 指令微调"] C --> G["几何编码器/解码器"] F --> H["联合推理模型"] G --> H

VQ-VAE 学会“一个稀疏序列如何还原几何”,MLLM 学会“在这个序列中怎样选部件并写运动说明”;两者在推理阶段串起来,而不是把三角网格直接当作语言模型的输出词表。

训练伪代码

函数 TrainVQVAE(objects):
    对每个 mesh:
        target ← 64³ 表面占据
        indices ← EncodeMesh(target)
        reconstruction ← Decode(indices)
        用重建误差、codebook 误差和 commitment 误差更新 VQ-VAE

函数 TrainMLLM(QA_pairs):
    对每个 QA:
        context ← 图像 + 稀疏几何序列 + 指令
        target ← 部件 token 子集或完整 JSON
        按指令微调目标更新 Qwen3-VL

推理时的信息流

flowchart TD A["训练好的 VQ-VAE"] --> E["推理"] B["训练好的 Qwen3-VL"] --> E C["输入 GLB"] --> D["对齐、归一化、渲染"] D --> F["体素化与稀疏 token"] F --> E E --> G["生成严格 JSON"] G --> H["逐部件解码 seed"] H --> I["原网格图分割"] G --> J["运动字段解析"] I --> K["OBJ/分件网格"] J --> L["URDF"] K --> L

推理本身不需要逐部件调用一个分割网络:一次 MLLM 生成可以同时给出所有部件的 token 和运动说明,后面再用确定性几何程序完成落地。

4. 贯穿示例:把单体储物盒变成可打开的资产

下面是一个缩小的教学示例,数字只为帮助理解,不是论文的实验样本。

步骤 1:统一坐标和观察方式

输入是一个单体 box.glb,里面盒体和盖子没有独立 link。先把模型对齐到右手坐标系,约定 \(+Z\) 向上,并按包围盒居中、缩放到 \([-0.5,0.5]\)。同时渲染一张等距视图,让模型能看到“上方那一片像盖子”。

步骤 2:得到稀疏几何提示

体素化后假设只有 6 个粗格有表面。序列可能类似:

<voxel> 83 17
<voxel> 94 21
<voxel> 388 302
...

其中每组三元组的第一个整数是位置,最后一个整数是局部几何码。它们不是部件标签,只是整件物体的压缩几何描述。

步骤 3:MLLM 联合回答

给出指令:

Describe the object with real scale and separate the object to different
functional parts with each physical properties.

模型可能生成:

{
  "object_captions": {"name": "storage box", "scale": 40.0},
  "parts_captions": {
    "0": {"type": "fixed"},
    "1": {
      "type": "revolute",
      "parent": "0",
      "center": [100, 138, 101],
      "axis": [100, 0, 0],
      "limits": [-54, 45]
    }
  },
  "parts_voxels": {
    "0": "<voxel> 83 17 ...",
    "1": "<voxel> 388 302 ..."
  }
}

这里有两个相互配合的结果:parts_voxels["1"] 指出盖子的大致几何区域,parts_captions["1"] 指出它应挂在盒体上并绕哪根轴转。

步骤 4:回填原始网格

盖子 token 解码成一小团 seed。程序把 seed 与原始网格顶点做距离比较,并沿网格邻接关系平滑。最后,盒体和盖子的 OBJ 都是原始网格的面子集,所以倒角、纹理和细节不会被 \(64^3\) 体素直接替代。

步骤 5:生成 URDF 并交给仿真器

URDF 里 l_0 是固定盒体,l_1 是盖子,j_1 是旋转关节。仿真器可以把关节角从下限逐渐推到上限,观察盖子是否绕正确位置打开。若再加入机器人夹爪,便能把这个资产用作操作策略的环境。

如果改问一个局部问题

把指令换成“找出盖子的功能部件”,模型只需返回盖子 token 子集。后端仍然走“解码 seed → 原网格分面”,因此 grounding 结果与整机分解共享同一套几何坐标。

5. 为什么有效,以及何时会失效

5.1 四个互相咬合的有效性来源

  1. 稀疏 token 降低了上下文压力:空体素不再逐个消耗注意力,复杂多部件物体更容易放进 MLLM 上下文。
  2. 坐标和语义同时存在:图像告诉模型“它像门”,xyz 告诉模型“门在这里”,局部码 K 再提供表面形状线索。
  3. 预测与原网格解耦:MLLM 的粗粒度输出只负责播种,三角面来自输入,因此不会因为生成序列短而牺牲最终几何细节。
  4. 结构化输出减少接口摩擦:部件 token、父子关系和 joint 参数在一次回答中对齐,能直接接到 URDF/Isaac Sim 一类下游工具。

5.2 论文报告的结果应该怎样读

论文在 ID 与 AI-generated 测试对象上报告了以下 SIMART 结果(数值越高越好或越低越好见指标名):

指标 ID AI-generated
关节类型准确率 Type 0.928 0.831
轴误差 Axis 0.080 0.136
原点误差 Origin 0.111 0.145
部件 IoU 0.690 0.777
部件 Chamfer Distance 0.087 0.079

part grounding 在 AI-generated 集合上报告 IoU 0.807、Chamfer Distance 0.018。这个结果的重点不是“一个模型万能”,而是说明:在陌生外观上,坐标化 token 加上图像语义,仍能把自然语言部件落到原网格。

消融实验给出的直观结论是:

  • 密集 token 配置在复杂对象上会触发显存不足;
  • 只做强制稀疏但没有 zero token,重建和运动指标变差;
  • 加入 zero token 后,平均 token 数进一步降到约 516;
  • 再加入视觉输入,类型、中心和几何指标继续改善。

论文另行构建的 AIGC 测试部分规模是 10 余类、36 个统一资产:先用自动方法过分割,再由人工合并为 2–4 个功能部件并标注轴和关节。因此这些结果能说明方法在新外观上的潜力,但不等于已经覆盖真正无限制的开放世界。

5.3 方法边界

  • 输入缺失无法靠稀疏编码修复:AIGC 网格若没有冰箱内部结构、门缝或支撑件,模型只能根据外观猜,可能出现部件碎裂或错位。
  • 斜向关节更难:离散几何 token 对非正交轴的连续方向回归不够细,罕见的斜轴和复杂多级机构更容易出错。
  • 物性是估计值,不是测量值:密度、杨氏模量、摩擦等主要来自视觉/语言先验;用于严肃物理实验前仍应校准。
  • 坐标约定必须一致:训练和推理假设右手坐标系、\(+Z\) 向上,并要求渲染、体素化、回填使用同一朝向。输入预处理时应检查物体“正面”是否一致。
  • 公开入口是 mesh 而不是真实照片:当前推理脚本从 GLB 生成单张等距 RGB 图;真实拍摄图像和多视角观测并不是这份脚本的直接入口。
  • 公开规格有一处版本遗留:主文和公开推理脚本使用 \(8\times8\times8\)、64 维 token、4096 码本、xyz 范围 0–511;附录的旧版 system prompt 仍写 \(16\times8\times8\)、8192 码本和 0–1023。理解当前流程时应以前一组为准。
  • 量化和 URDF 仍要核对版本:论文公式对非空格子写的是排除 zero code,但公开量化器的最近邻分支仍可能把 index 0 纳入候选;这属于实现约定,不能把 zero token 当成自动保证。公开的简化写出器主要落地 links、视觉/碰撞网格、joints、轴和限位;预测的材料、密度、真实尺度等字段会保存在 JSON,但完整惯量、接触参数和尺度应用需要下游补充。公开写出器把旋转限位按数值除以 100 后再乘 \(2\pi\) 转弧度,而附录文字对 100 的角度含义有不同说法,实际部署应核对所用配置。
  • 纹理保真也有层次:方法设计是让分件继承输入网格的几何和外观;公开的简化 OBJ 导出不保证所有材质/纹理元数据都被写入 URDF。

6. 一页记住这个方法

单体网格
  → 对齐、归一化、渲染
  → 64³ 表面体素
  → Sparse VQ-VAE:只保留 occupied 的 <voxel> xyz K
  → Qwen3-VL:同时输出部件 token 与 URDF/物性 JSON
  → 每个部件 token 解码成 seed
  → seed 在原始高保真网格上做高斯初始化 + 图平滑
  → 分件网格 + 关节树 + URDF
  → 机器人仿真或 VR/AR 交互

最值得记住的三句话:

  1. 稀疏表示解决“模型看不下这么多 3D token”的问题。
  2. MLLM 负责理解功能和运动,不直接负责重建每个三角面。
  3. seed 回投原网格,才把“会动”与“长得精细”同时保住。
posted @ 2026-09-10 10:50  S-X-Q  阅读(13)  评论(0)    收藏  举报