在 3D 重建与具身智能的研究中,Large Reconstruction Models(LRMs)与 Human-Object Interaction(HOI)重建正在快速融合。传统的 HOI 重建通常依赖类别模板、多视角图片或长时间的优化迭代,而 LRMs 提供另一条路径:让模型从图片中直接推理出人和物体的三维表示。这篇文章围绕这一主题,整理 LRM 用于人与物体交互重建时的概念、环境、实现、验证和排错方法。
与普通单物体重建不同,人与物体交互重建要同时解决三类问题:人类姿态与形状估计、物体形状与姿态估计、以及二者之间的接触与遮挡关系。LRM 的优势在于,它可以把大量真实世界交互场景的先验压缩进前馈网络,推理时不需要逐帧优化。下面从任务定义开始讲清楚这套方案为什么成立,再给出一个最小可运行的实现框架。
1. 先理解 LRM 为什么适合人与物体交互重建
1.1 人与物体交互重建到底在重建什么
Human-Object Interaction(HOI)重建的目标,是从一张或多张图像中恢复人体、物体以及二者交互关系的三维表示。常见的输出包括:
- 人体网格或隐式表面,通常用 SMPL、SMPL-X 等参数化模型表达。
- 物体网格或隐式表面,可以用 SDF、NeRF、3D Gaussian Splatting 或显式 mesh 表达。
- 人体与物体的相对位姿,包括旋转、平移和尺度。
- 接触区域,比如手掌与杯子柄、臀部与椅子面、脚底与地面。
单纯把人体和物体分开重建并不是真正的 HOI 重建。交互场景中通常存在大面积遮挡,物体可能被人的手遮挡,人体肢体也可能被物体遮挡。如果两个分支独立重建,最终会把人体穿过物体,或者手部悬空。因此,LRM 的建模方式需要让两个目标共享图像特征,同时输出一个“一致的三维场景”,而不是两个孤立的网格。
1.2 传统方法的两类瓶颈:类别模板与优化速度
在 LRM 出现之前,HOI 重建的主流方法大致分成两类。
第一类是参数化优化方法。它基于 SMPL 拟合人的姿态,再对物体做位姿估计或模板匹配,最后用物理约束和接触约束优化相对位置。这类方法的优点是精度高,可以结合几何先验,但缺点是:
- 每张图都要迭代几百到几千步,速度慢。
- 容易陷入局部最优,特别是手部与物体互相遮挡时。
- 对物体类别敏感,已知类别的模板效果尚可,未知类别很难处理。
第二类是类别专用方法。例如针对椅子、桌子、杯子等常见交互物体,分别训练检测器和形状重建网络。优点是类别内精度高,缺点是无法扩展到开放世界。真实场景中的交互物体类别几乎无限,不可能为每个类别都准备精细模板。
LRM 的思路介于两者之间。它不把物体当作固定类别,而是把“从图像到三维形状”当作一个可学习的前馈映射。训练数据覆盖足够广时,模型可以泛化到训练集中没有见过的物体形状。这正是 LRM 适合 HOI 重建的深层原因。
1.3 LRM 的建模思路:前馈生成一个三维表示
一个典型的 LRM 包含下面几部分:
- 图像编码器:通常是 Vision Transformer(ViT),把输入图片切分成 patch 并编码成 token。
- 三平面解码器:从 token 生成三个正交平面的特征,每个平面保存局部几何和纹理信息。
- 可微渲染器:对任意空间点查询三平面特征,再解码成颜色、密度或 SDF,并通过体渲染或 splatting 生成图像。
在人与物体交互场景中,可以把人体和物体看作是同一个三维空间中的两个语义区域。LRM 不需要分别重建后再拼接,而是在同一个 triplane 或 3D Gaussian 空间里同时表达人和物体,然后增加一个语义分支区分“这个位置属于人、属于物体、还是属于背景”。
这种设计有几个好处:
- 人体和物体的相对位置天然一致,不需要后处理对齐。
- 遮挡区域可以通过可微渲染从像素损失中学习。
- 接触关系体现在同一空间场的拓扑连接中,比单独预测接触点更自然。
当然,LRM 也有自己的代价。它需要大规模 HOI 训练数据,显存占用较高,并且对小物体的细节恢复不如专用优化方法。下面进入工程落地时最需要关注的依赖和数据准备。
2. 环境与数据准备:先对齐依赖,再谈模型
2.1 依赖环境和版本选择
LRM 模型通常使用 PyTorch 实现,训练和推理都依赖 CUDA。部分实现还会用到 3D Gaussian Splatting 的可微光栅化器,或者 NeRF 的渲染模块。下面是研究实验环境中最常见的依赖组合:
conda create -n lrm_hoi python=3.10 -y conda activate lrm_hoi pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install opencv-python pillow numpy trimesh pyrender pip install mcubes scikit-image lpips tensorboard实际项目中,LRM 的代码仓库可能会锁定某些依赖版本。安装前先看仓库的requirements.txt或environment.yml,不要直接使用最新版本。不同版本的 PyTorch 对 CUDA 算子兼容性差异很大,尤其是diff-gaussian-rasterization这类需要自行编译的扩展。
| 组件 | 推荐版本区间 | 说明 |
|---|---|---|
| Python | 3.9 到 3.11 | 版本过低可能导致新算子不支持 |
| PyTorch | 2.0 以上 | 自动求导和编译器能力更稳定 |
| CUDA | 11.8 或 12.1 | 具体以 PyTorch 预编译包为准 |
| diff-gaussian-rasterization | 以项目仓库为准 | 3DGS 渲染需要自行编译 |
| trimesh | 4.x | 用于 mesh 导入导出 |
| mcubes | 最新稳定版 | 从 SDF 提取 mesh 时使用 |
2.2 数据集与标注格式
HOI 重建的数据比普通单物体重建更复杂。常见公开数据集包括 BEHAVE、H2O、InterCap 等,它们通常提供多视角视频、人体 SMPL 参数、物体 mesh 和相机参数。使用前要确认 License 和数据用途,不同数据集的标注规范差异很大。
一份基础的数据组织方式可以设计成这样:
dataset/ sequence_01/ rgb/ 000000.png 000001.png mask_human/ 000000.png 000001.png mask_object/ 000000.png 000001.png cam_meta.json smplx/ tracked_people.json object/ object_mesh.obj关键字段在cam_meta.json中至少需要包含:
- 图像宽高。
- 相机内参矩阵 K。
- 外参 R、T。
- 时间戳或帧索引。
对于人体,推荐使用 SMPL-X 或 SMPL 参数;对于物体,最好提供对象 mesh 或至少提供深度图。如果数据集中没有物体的真实 mesh,可以通过多视角重建得到伪标签,但这会增加训练噪声。
2.3 项目目录设计
研究性代码很容易演变成一串混乱的 notebook 和脚本。稍微克制一点,项目目录可以这样组织:
lrm_hoi/ configs/ train_hoi.yaml inference_hoi.yaml data/ datasets/ transforms.py models/ lrm_hoi.py renderer.py losses.py utils/ camera.py mesh_utils.py metrics.py scripts/ train.py export_mesh.py evaluate.py outputs/ checkpoints/ visualizations/这个结构不复杂,但能保证训练、推理、评估、可视化彼此解耦。接下来直接在models/lrm_hoi.py中实现一个最小化接口。
3. 一个最小可运行框架:从单目 RGB 到交互场景网格
3.1 图片预处理与相机归一化
LRM 的输入通常是归一化到一定尺寸的 RGB 图像,例如 512x512。图像本身要进行中心裁剪或缩放,相机内参也需要同步归一化。如果忽略这一步,输入分布偏移会直接影响重建结果。
import cv2 import numpy as np import torch def preprocess_image(image_path, target_size=512): image = cv2.imread(image_path) image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) h, w = image.shape[:2] scale = target_size / max(h, w) new_w, new_h = int(round(w * scale)), int(round(h * scale)) image = cv2.resize(image, (new_w, new_h)) image = image.astype(np.float32) / 127.5 - 1.0 # 中心填充到 target_size h_, w_ = image.shape[:2] pad_h = target_size - h_ pad_w = target_size - w_ top = pad_h // 2 left = pad_w // 2 image = cv2.copyMakeBorder( image, top, pad_h - top, left, pad_w - left, cv2.BORDER_CONSTANT, value=(-1, -1, -1) ) tensor = torch.from_numpy(image).permute(2, 0, 1).unsqueeze(0) return tensor # 相机内参也需要按相同的缩放和填充做归一化 def normalize_intrinsics(K, original_size, target_size): h, w = original_size scale = target_size / max(h, w) K_norm = K.copy() K_norm[0, 0] *= scale K_norm[1, 1] *= scale K_norm[0, 2] = K[0, 2] * scale + (target_size - w * scale) / 2.0 K_norm[1, 2] = K[1, 2] * scale + (target_size - h * scale) / 2.0 return K_norm这里的关键是:图像缩放后,相机主点坐标也必须跟着移动,否则后续的 ray casting 全部错位。实际项目里,这部分代码必须写单元测试。
3.2 骨干网络与 LRM 解码器
下面代码描述的是一个简化但完整的模型结构:图像编码器提取 token,解码器生成 triplane,再由一个查询模块从 triplane 得到密度和颜色。它用于说明接口设计,不能直接当完整训练脚本使用。
import torch import torch.nn as nn class LRMHOI(nn.Module): def __init__(self, image_size=512, triplane_res=128, plane_channel=32): super().__init__() # 图像骨干,可以替换为任意 ViT 编码器 from torchvision.models import vit_b_16 self.image_encoder = vit_b_16(weights=None) # 将编码器输出映射为 triplane 特征 # 三平面:XY, XZ, YZ,每个平面是 plane_channel 维特征 self.triplane_head = nn.Sequential( nn.Linear(768, 256), nn.ReLU(inplace=True), nn.Linear(256, triplane_res * triplane_res * plane_channel * 3) ) self.triplane_res = triplane_res self.plane_channel = plane_channel def forward(self, image): tokens = self.image_encoder(image) features = self.triplane_head(tokens) B, _ = features.shape C = self.plane_channel R = self.triplane_res features = features.view(B, 3, C, R, R) return { "triplane_xy": features[:, 0], "triplane_xz": features[:, 1], "triplane_yz": features[:, 2] } def query_triplane(self, triplanes, points): # points: [B, N, 3] features = [] for plane_name in ["triplane_xy", "triplane_xz", "triplane_yz"]: plane = triplanes[plane_name] # 实际项目这里会做双线性采样并求和 features.append(torch.zeros(points.shape[:-1] + (self.plane_channel,))) return sum(features)由于 LRM 通常使用预训练 ViT,且不同仓库的实现差异很大,实际使用时要直接复用原作者定义的模型类。上面代码只是把“图像到 triplane”的核心流程表达清楚。
3.3 人体、物体与接触区域的分支设计
HOI 场景需要模型在同一个空间里输出多个语义标签。常见做法是在 triplane 查询后增加几个 head:
- 人体 head:输出人体 SDF 值和语义概率。
- 物体 head:输出物体 SDF 值和语义概率。
- 接触 head:输出该空间点是接触区域的概率。
这样设计的好处是,人体和物体共享同一个 triplane 特征,但解码层可以保留各自语义。物体类别多样化时,物体 head 可以做得更深;人体类别相对单一,人体 head 可以加入 SMPL 先验约束。
class HOIDecoder(nn.Module): def __init__(self, in_channels=32, hidden=64): super().__init__() self.shared_mlp = nn.Sequential( nn.Linear(in_channels, hidden), nn.ReLU(inplace=True) ) self.human_head = nn.Linear(hidden, 1) self.object_head = nn.Linear(hidden, 1) self.contact_head = nn.Linear(hidden, 1) self.color_head = nn.Linear(hidden, 3) def forward(self, plane_feature): shared = self.shared_mlp(plane_feature) human_sdf = self.human_head(shared) object_sdf = self.object_head(shared) contact = self.contact_head(shared) color = self.color_head(shared) return { "human_sdf": human_sdf, "object_sdf": object_sdf, "contact": contact, "color": color }这里没有把人体和物体分开成两套完全独立的网络,而是先共享特征、再分支预测。理由是在交互场景中,接触边界附近的人体和物体特征高度相似,强行分开会让梯度互相屏蔽。
3.4 训练循环示例
训练一个 LRM 风格的 HOI 重建模型,核心循环并不复杂。关键是保证每个 batch 里包含图像、相机参数、人体 mask、物体 mask、采样点真值 SDF 或深度。
def train_step(model, batch, optimizer): image = batch["image"] # [B, 3, H, W] mask_human = batch["mask_human"] # [B, H, W] mask_object = batch["mask_object"] # [B, H, W] points = batch["points"] # [B, N, 3] sdf_human_gt = batch["sdf_human_gt"] # [B, N] sdf_object_gt = batch["sdf_object_gt"] # [B, N] rgb_gt = batch["rgb_gt"] # [B, N, 3] triplanes = model.encode(image) plane_feature = model.query_triplane(triplanes, points) pred = model.decode(plane_feature) loss_human = sdf_loss(pred["human_sdf"], sdf_human_gt) loss_object = sdf_loss(pred["object_sdf"], sdf_object_gt) loss_color = torch.nn.functional.l1_loss(pred["color"], rgb_gt) loss = loss_human + loss_object + 0.5 * loss_color optimizer.zero_grad() loss.backward() optimizer.step() return { "loss": loss.item(), "loss_human": loss_human.item(), "loss_object": loss_object.item(), "loss_color": loss_color.item() }如果使用体渲染而不是直接回归 SDF,训练循环里还要增加渲染步骤。这个示例没有渲染,是为了把“由 triplane 查询得到 SDF”这件事讲清楚。实际复现时,渲染器和 SDF 回归往往需要同时存在,并用可微渲染将pred["color"]与图像像素对齐。
3.5 推理与导出
训练完成后,从模型输出 mesh 的标准流程是:先构建一个稠密网格,查询每个顶点的 SDF,再用 Marching Cubes 提取等值面。下面是常用流程:
import numpy as np import mcubes import trimesh def extract_mesh(model, triplanes, res=256, level=0.0): xs = np.linspace(-1.0, 1.0, res) ys = np.linspace(-1.0, 1.0, res) zs = np.linspace(-1.0, 1.0, res) grid = np.stack(np.meshgrid(xs, ys, zs, indexing="ij"), axis=-1) flat = grid.reshape(-1, 3) with torch.no_grad(): pred = model.query_triplane(triplanes, torch.from_numpy(flat).float().unsqueeze(0)) object_sdf = pred["object_sdf"].squeeze(0).squeeze(-1).numpy() object_sdf = object_sdf.reshape(res, res, res) vertices, triangles = mcubes.marching_cubes(object_sdf, level) # 顶点坐标映射回 [-1, 1] 空间 vertices = vertices / (res - 1) * 2.0 - 1.0 mesh = trimesh.Trimesh(vertices=vertices, faces=triangles) return mesh这里只提取物体 mesh 作为演示。实际项目通常会把人体 mesh 和物体 mesh 分别提取,再保留接触区域。输出 mesh 后,可以用trimesh.export导出为.obj,也可以用 open3d 或 pyrender 可视化。
注意:上面代码中的
model.query_triplane和model.decode都只是接口示意。不同 LRM 实现的三平面采样方式不同,网格范围也可能不是[-1, 1],落地前要以对应仓库的实际坐标约定为准。
4. 关键参数、损失函数与输出含义
4.1 输入分辨率与 token 密度的取舍
LRM 类模型对输入分辨率比较敏感。分辨率提高意味着 ViT 切出的 patch token 更多,模型有更多信息恢复细节,但显存占用也会快速上升。
常见选择:
- 224x224:适合快速验证和消融实验,显存占用小,但小物体重建效果差。
- 384x384:平衡速度与效果,适合大多数单物体和 HOI 实验。
- 512x512 或更高:适合精细 mesh 输出,需要至少 24GB 显存。
在 HOI 场景中,手部和小物体通常只占图像的一小部分。如果输入分辨率过低,物体可能只有十几个 token,重建结果会非常粗糙。实际项目中,可以先在 384x384 上跑通流程,再针对小物体训练高分辨率模型。
4.2 损失函数:Mask、RGB、深度和接触损失
LRM 训练时通常需要组合多种损失,每种损失负责不同维度。
- Mask 损失:帮助模型区分人、物体、背景。
- RGB 损失:保证从新视角渲染出的颜色与真实图片一致。
- 深度或 SDF 损失:监督几何形状,避免表面漂移。
- Eikonal 正则:让 SDF 在空间中满足梯度范数为 1,提升表面稳定性。
- 接触损失:在人体 mesh 和物体 mesh 最近点之间施加小距离约束,减少穿插。
接触损失是 HOI 重建里最需要小心的部分。太大会让模型把人体和物体强行压在一起,太小又会导致手部漂浮。推荐在训练初期给一个较低权重,等 mask 和 SDF 分支基本收敛后再调高。
def contact_loss(human_verts, object_verts, threshold=0.05): """ human_verts: [N, 3] object_verts: [M, 3] 返回接触区域的近似距离损失。 """ # 使用 scipy 的 KDTree 找最近邻 from scipy.spatial import KDTree tree = KDTree(object_verts.detach().cpu().numpy()) dist, idx = tree.query(human_verts.detach().cpu().numpy()) dist_tensor = torch.from_numpy(dist).float().to(human_verts.device) # 只惩罚距离小于阈值的点,避免把整个人拉向物体 contact_mask = (dist_tensor < threshold).float() loss = (dist_tensor * contact_mask).mean() return loss注意,KDTree 在反向传播时不可导,所以示例里使用了detach()。实际实现通常会改用可微距离场查询,但思路是一样的:只约束接触点附近的距离。
4.3 关键超参数速查表
| 参数名 | 建议范围 | 调大影响 | 调小影响 | 错误表现 |
|---|---|---|---|---|
| 输入分辨率 | 224 到 512 | 细节更多,显存更大 | 速度快,但小物体丢失 | 显存溢出或细节模糊 |
| triplane 分辨率 | 64 到 256 | 几何表达更强 | 表面更粗 | 表面空洞或伪影 |
| triplane 通道数 | 16 到 64 | 容量增加 | 容量减少 | 颜色和形状欠拟合 |
| 采样点数 | 64 到 256 | 渲染更精细 | 训练更快 | 表面不连续 |
| 接触损失权重 | 0.01 到 1.0 | 接触更紧密 | 容易穿插漂浮 | 穿插或悬浮 |
| Eikonal 权重 | 0.01 到 0.1 | SDF 更稳定 | 表面抖动 | 重建表面杂散 |
以上数值不是固定的,应该以自己数据集上的验证集指标为准。做消融实验时,每次只改一个参数,否则很难定位是哪个因素引起指标变化。
4.4 学习环境与生产环境的差异
研究实验环境可以接受“离线训练、人工看可视化结果”。但如果要把 LRM 推理能力用到批量数据生产或实时服务中,必须考虑额外工程化问题。
| 维度 | 研究实验环境 | 批量生产环境 |
|---|---|---|
| 推理速度 | 每秒处理几张即可 | 需要批处理或异步任务 |
| 显存控制 | 尽量用最大 batch 提高速度 | 需要限制单卡并发和动态 batch |
| 模型版本 | 训练日志 + checkpoint 即可 | 需要模型注册和版本回滚 |
| 数据输入 | 手工挑选少量图片 | 需要校验图片质量、mask 完整性 |
| 异常处理 | 失败时人工重跑 | 需要自动跳过、记录失败原因 |
| 输出校验 | 肉眼观察 mesh 是否合理 | 需要客观指标和兜底降级策略 |
研究阶段可以容忍训练脚本偶尔中断,但生产环境必须保证一个序列失败不会影响整批任务。因此,推荐在脚本开头加数据完整性检查,并在每个 block 处理完后写入中间结果。
5. 运行验证与常见错误模式
5.1 可视化验证:mesh、点云、渲染图
训练完成后,先不要直接看指标,先可视化几个样本。推荐保存三类图像:
- 重建 mesh 的侧视图和新视角渲染图。
- 人体 mesh 和物体 mesh 重叠后的接触区域放大图。
- 原始输入图和 mask 叠加图。
可视化脚本可以保留在scripts/export_mesh.py中:
python scripts/export_mesh.py \ --ckpt ./outputs/checkpoints/last.pt \ --input ./data/sequence_01/rgb/000000.png \ --output ./outputs/visualizations/sequence_01/ \ --save_obj \ --save_png如果新视角渲染明显不合理,说明 triplane 内部的几何表达有问题,而不是后处理步骤问题。可视化能帮助快速区分是模型问题还是数据问题。
5.2 定量指标:IoU、Chamfer、F-score、接触度量
定量评估通常分为几何、体素和接触三类指标。
| 指标名称 | 作用 | 计算方式 |
|---|---|---|
| IoU | 衡量占用空间重合度 | 真值和预测网格体素化后计算交并比 |
| Chamfer Distance | 衡量表面点云距离 | 双向最近点距离平均 |
| F-score | 衡量表面精度和召回 | 距离小于阈值的点占比 |
| Contact Precision | 衡量接触区域精度 | 预测接触点是否在真值接触范围内 |
实际评估时,建议对人和物体分别计算指标,再额外输出一个“接触区域 IoU”。因为总 IoU 高不代表接触关系正确,有可能人体和物体各自重建得好,但位置错开了。
5.3 两类典型失败模式
第一类是人手和物体边缘粘连。这通常是因为 mask 中手部和物体贴得太近,分割不干净。模型学到的是“两个物体边界可以融合”,而不是真正的接触关系。
第二类是物体完全被遮挡。比如人站在桌子后面,只有上半身可见,物体的下半部分完全被隐藏。LRM 只能依赖先验猜测形状,如果测试数据与训练集分布不同,猜测结果可能很突兀。
这两类失败在单目输入下很难彻底避免。缓解方式包括:使用多视角输入、在训练数据中增加截断样本、增加接触先验。
5.4 排查链路(从现象倒推原因)
当重建结果出现明显错误时,可以按照下面顺序排查:
- 检查输入图片和处理后的 mask。
- 检查相机内参归一化是否正确。
- 检查 triplane 分辨率是否足够。
- 检查人体和物体分支是否共享了不该共享的编码层。
- 检查接触损失权重是否过高或过低。
- 检查训练集与测试集分布是否有明显差异。
例如,如果发现重建的物体整体偏移,优先怀疑相机归一化;如果发现人体网格正常但物体空洞,优先怀疑 object mask 或物体数据量不足;如果发现人和物体相互穿插,但各自指标都很好,优先怀疑接触损失和真实接触标签质量。
注意:排查时不要一次性修改多个因素。每次只改一个条件,保留输出日志和可视化结果,才能形成可复现的实验记录。
6. 最佳实践与下一步扩展
6.1 实验前检查清单
开始训练前,建议逐项确认下面内容:
- 数据集是否包含相机内参、外参和 mask。
- 训练集与验证集是否按序列划分,避免同一交互序列出现在两边。
- 图像预处理代码是否与训练代码使用同一份实现。
- 模型输出空间的范围是否与网格提取范围一致。
- 人体和物体 mask 是否有大面积重叠。
- 是否保存了每个训练 step 的 loss 曲线。
- 是否固定了随机种子,确保可复现。
- 是否在很小 batch 上做过一天步数的过拟合测试。
过拟合小 batch 是这套流程里性价比最高的检查。如果模型连训练集中的一个 batch 都无法重建,那么问题大概率出现在代码层面,而不是数据量或超参。
6.2 常见坑与规避方法
| 常见坑 | 为什么错 | 推荐做法 |
|---|---|---|
| 输入分辨率过低 | 小物体 token 太少,几何表达不足 | 使用 384x384 以上,或在 ROI 区域单独重建 |
| 只优化 RGB 损失 | 颜色容易收敛,几何可能塌缩到下平面 | 增加 SDF/深度损失和 Eikonal 正则 |
| 接触损失权重一开始就很大 | 早期几何未成形,强行拉点导致表面变形 | 先小权重,后期再调高 |
| 人体与物体分支完全独立 | 无法共享图像特征,交互关系难学习 | 共享编码器,只用轻量 head 区分语义 |
| 没有做 mask 质量检查 | mask 边缘噪声会污染 triplane 特征 | 对 mask 做形态学操作,并按序列抽查 |
| 直接使用最新版依赖 | 算子接口或 CUDA 版本不兼容 | 严格锁定仓库要求版本 |
HOI 重建很容易出现“看起来差不多,其实细节全错”的情况。因此,定量指标和可视化必须同时使用,不能只用一张渲染图判断效果。
6.3 从单视图到多视图与视频输入
单目 LRM 是起点,实际项目往往需要多视角或视频输入来获得更稳定的结果。多视角输入时,可以把不同视角的图像分别编码,再用 cross-attention 融合 token。视频输入则可以利用时序一致性,让相邻帧的人体姿态变化更平滑。
这里有一个常见的工程选择:是直接扩展 LRM 网络,还是用后处理多视角融合。直接扩展网络通常效果更好,但训练成本会成倍增加。后处理融合适合快速验证,缺点是融合错误难以修正。建议先在后处理流程中验证多视角对齐,再考虑修改网络结构。
6.4 落地时最值得保留的工程化习惯
最后整理几条对实际项目最有用的习惯:
- 命令行参数统一使用 YAML 配置,不要散落在 python 文件里。
- 每次实验记录 commit、seed、数据集版本和超参,否则无法复现。
- 数据集中的原始图像不要随意覆盖,标注文件按序列独立保存。
- checkpoint 保存时同时保存 optimizer 和 scheduler 状态。
- 评估脚本固定测试集,不允许训练过程中往测试集里增加样本。
LRM 用于 HOI 重建还处于快速演进阶段,新的渲染器、新的三维表示和新的数据集会不断出现。但底层的工程问题不会变:输入分布、数据质量、显存管理、损失平衡、评估口径。把这些基础打牢,后续替换模型结构时会轻松很多。