简介:由脸书研究院开源的高清全身三维人体重建工具,只需单张图片即可生成带几何细节的三维人体模型,适用于虚拟现实、游戏开发、个性化娱乐等场景,面向计算机视觉研究人员和三维建模开发者。资源包共65个文件、约389KB,以35个Python源文件为主,涵盖网络结构、训练与推理脚本;另有配置文件、Shell脚本、Markdown文档、CSV标注列表、JSON关键点示例及图片,并附使用说明和许可协议,目录结构清晰,便于按模块阅读。Python代码实现HGPIFuNet等核心网络,完整覆盖单图三维重建的数据预处理、特征提取、网格生成流程;Shell脚本提供演示与批处理入口,文档可帮助快速上手环境配置与运行。目前已有101人学习下载,对希望从代码层面理解单图三维重建原理的学习者而言,是一份轻量完整的参考实现,可用于复现实验、二次开发或作为毕业设计、课题研究的基础代码。
1. 单张图片重建全身3D模型:这个方向到底能落地什么
把一张普通手机照片丢进模型,十几秒后导出一个人体网格,这个流程在2020年前还是论文里的demo,现在已经是游戏、电商、动画、医学康复领域都在用的常规工具。3D人体重建的落地价值在于:它绕开了三维扫描仪和多视角相机阵列,把获取人体模型的成本压到一张图,且当输入是全身照片时,可以直接输出带骨架语义的SMPL-X参数化模型,而不是一个“像人也不是人”的mesh点云。适合谁?中小型工作室做虚拟试衣、独立游戏开发者做角色原型预演、医疗康复团队做体态对比,以及做运动分析的算法工程师——你们不需要买设备,只需要一台有NVIDIA显卡的Linux机器和足够的耐心。
这篇笔记我从模型选型讲到参数调优,再到真实的翻车场景,全程围绕单张图片这个约束展开。先说结论:如果输出目标是“能直接进Blender调动作的人体资产”,优先选SMPL-X参数化路线;如果目标是“高精度表面细节”,选隐式神经场。两者不是替代关系,后面会展开讲为什么、怎么做、坑在哪。
2. 从SMPL-X到PIFuHD:选对模型路线,单图重建才算入门
2.1 为什么单张图片重建是个“病态问题”
单张图片天然丢掉了深度信息,同一个2D轮廓可能对应无穷多个3D姿态,这就是所谓的病态问题。人体重建领域的解法不是靠“猜”,而是靠“先验”——用大量三维人体扫描数据训练一个统计模型,让模型知道“人一般长什么样、关节一般怎么摆”。如果你的输入是一个人像照片,那么模型实际上是在做两件事:先估计2D关键点和人体分割掩码,再在这个先验约束下反推3D参数。
这决定了你在做单张图片的3D建模时,精度上限不由模型单独决定,而由输入图片质量和前置检测决定。图片模糊、肢体被遮挡、裁切到手脚边缘、逆光导致轮廓和背景混在一起,这些都会直接压垮重建结果。不是模型不行,是喂给模型的信息本身不足。
2.2 两条主流路线的取舍:参数化人体模型 vs 隐式神经场
业内做单图重建,主流就两条路线:
第一是参数化人体模型,代表性成果是SMPL和它的升级版SMPL-X。这类模型把人体编码成姿态参数(joint rotation)、形状参数(betas)和表情参数(expression),输出的是一个带拓扑结构的网格,顶点数量固定(SMPL是6890个顶点,SMPL-X是10475个顶点),可以直接驱动骨架动画。好处是稳定、轻量、可动画化,坏处是表面细节有限——衣服皱褶、头发丝、面部微表情都不在里面,只能表示“裸身”或紧身衣状态的人体。
第二是隐式神经场,代表性成果是PIFu、PIFuHD。这类方法用一个神经网络回归一个符号距离场或占用场,输出的是一个高分辨率mesh,能把衣服纹理和脸部细节都重建出来。好处是细节惊人,坏处是计算开销大、输出网格拓扑不固定,动画绑定基本没法直接用。
选哪条?看你的下游应用。我做角色绑定和动作驱动时一定选SMPL-X,因为骨骼结构和蒙皮权重已经内置了;我做数字人静态展示或3D打印时选PIFuHD,因为要的是“像”,不是“能摆动作”。中间还有一种折中:先跑SMPL-X拿到姿态和形状,再用PIFuHD做表面细化,把SMPL-X网格作为粗对齐初值喂给PIFuHD做细化。
2.3 我要选哪条路线:看输出需求而不是看热度
网上很多教程一上来就推PIFuHD,因为它效果炫、论文图好看。但如果你要做游戏角色、虚拟主播、动画绑定,PIFuHD输出的网格基本是“一次性资产”,想让它跟着骨骼动起来需要重新拓扑和刷权重,工作量远超重建本身。反过来,如果你做电商展示、鞋服试穿效果图,SMPL-X的裸身网格又满足不了——你客户要的是穿着衣服的立体人像,不是光着的人体模型。
所以我的建议是先明确一个问题:这个模型是要进绑定管线还是进渲染管线做静态展示?进绑定管线,SMPL-X是唯一合理选择;做静态展示,PIFuHD更合适。选型本身没有绝对优劣,只有合不合适。
提示:如果你不确定下游用途,先用SMPL-X搭起全流程,后续想加细节再单独跑PIFuHD作细化。SMPL-X的输出可以反过来作为隐式方法的输入初值,这是一条已经被验证过的pipeline组合。
3. 用PyMAF-X在本地跑通最小流程:从一张JPG到全身3D网格
3.1 环境准备与依赖安装
我在Ubuntu 20.04 + CUDA 11.3 + PyTorch 1.11 的环境下跑通过PyMAF-X,这是目前把单图预测SMPL-X参数做得比较稳的开源实现。Python版本建议用3.8或3.9,太新的版本容易遇到torchvision和detectron2的兼容问题。
# 创建虚拟环境,Python版本锁死3.8 conda create -n pymafx python=3.8 conda activate pymafx # 安装PyTorch,注意cuda版本要和驱动匹配 pip install torch==1.11.0+cu113 torchvision==0.12.0+cu113 --extra-index-url https://download.pytorch.org/whl/cu113 # 安装detectron2,PyMAF-X的2D关键点检测依赖它 python -m pip install detectron2 -f https://dl.fbaipublicfiles.com/detectron2/wheels/cu113/torch1.11/index.html # 克隆并安装PyMAF-X依赖 git clone https://github.com/HongwenZhang/PyMAF-X.git cd PyMAF-X pip install -r requirements.txt这里有个关键点:detectron2必须和torch版本严格对齐,版本错配最常见的现象就是模型加载后跑forward时直接报“undefined symbol”错误,或者2D关键点检测没有输出。如果你用的是更高的PyTorch版本,去detectron2官方仓库找对应wheel包名,别硬装。
3.2 最小推理脚本:输入单张图片,输出SMPL-X参数与网格
PyMAF-X仓库里自带demo脚本,但默认参数有很多是面向论文复现的,直接用容易踩坑。我一般按下面的方式改:
import torch import numpy as np from torchvision import transforms from PIL import Image from pymafx.core.config import run_config from pymafx.models import SMPLX from pymafx.utils.renderer import Renderer from pymafx.utils.geometry import rotation_matrix_to_angle_axis import cv2 # 加载预训练权重,pyMAFX_model.pth是官方提供的checkpoint model = SMPLX.from_pretrained('path/to/pyMAFX_model.pth') model.eval() model.cuda() img = cv2.imread('input.jpg') img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 关键一步:把图片放缩到224x224之前先按长边等比缩放,防止人形被压扁 scale = 224 / max(img.shape[:2]) new_w, new_h = int(img.shape[1] * scale), int(img.shape[0] * scale) img_resized = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LINEAR) # 再padding到正方形 canvas = np.zeros((224, 224, 3), dtype=np.uint8) canvas[:new_h, :new_w] = img_resized input_tensor = transforms.ToTensor()(canvas).unsqueeze(0).cuda() with torch.no_grad(): pred = model(input_tensor) # 返回SMPL-X参数字典 smplx_params = pred['smplx_params'] # 包含 global_orient, body_pose, betas, left_hand_pose, right_hand_pose, jaw_pose, leye_pose, reye_pose, expression # 从参数恢复mesh顶点 smplx_model = SMPLX(model_path='path/to/smplx_model.pkl') output = smplx_model(betas=smplx_params['betas'], body_pose=smplx_params['body_pose'], global_orient=smplx_params['global_orient'], left_hand_pose=smplx_params['left_hand_pose'], right_hand_pose=smplx_params['right_hand_pose'], jaw_pose=smplx_params['jaw_pose'], leye_pose=smplx_params['leye_pose'], reye_pose=smplx_params['reye_pose'], expression=smplx_params['expression']) vertices = output.vertices.detach().cpu().numpy().squeeze() # 保存为OBJ,方便直接拖进Blender或MeshLab with open('output.obj', 'w') as f: for v in vertices: f.write(f"v {v[0]:.6f} {v[1]:.6f} {v[2]:.6f}\n") for face in smplx_model.faces: f.write(f"f {face[0]+1} {face[1]+1} {face[2]+1}\n")这段代码的逻辑拆开说:模型输入是224×224的RGB图,但不能直接resize——人像长宽比一旦被改变,后续姿态估计会严重偏移。我习惯先按比例缩放到224的框内,再用黑色pad补齐,这样既满足网络输入尺寸又不破坏人的比例。输出侧的关键是把smplx_params里的11个参数组还原成顶点,这一步必须重新实例化一个SMPLX模型来做前向驱动,PyMAF-X网络本身只输出参数,不输出顶点。
注意:这里的
smplx_model.pkl是SMPL-X的官方模型文件,需要去SMPL-X官网注册下载,PyMAF-X不会自动帮你下载。这个文件缺失会直接报错,不是代码问题。
跑通上图这个最小流程大概需要6GB显存,实测RTX 3060 Laptop也能跑,只是速度会稍慢。如果你只有4GB显存,可以先把输入分辨率降到192,但重建质量会肉眼可见地下降。
4. 把重建结果变成能用的资产:mesh输出、姿态对齐与后处理参数
4.1 输出格式与坐标系:为什么拿到的模型是“躺”着的
SMPL-X输出的mesh默认是在SMPL自身坐标系下,这个坐标系的原点在骨盆位置,X轴向右、Y轴向上、Z轴朝向屏幕外侧。但是你在Blender里导入OBJ时,会发现模型是横躺着的,这是因为SMPL使用右手坐标系Y-up,而Blender也是Y-up——问题出在旋转矩阵的三维空间转换上。最直接的解决办法是导入时手动旋转X轴-90度,或者写个脚本一次性处理。
# 用Python快速把SMPL坐标转成Blender友好坐标 import trimesh import numpy as np mesh = trimesh.load('output.obj') # 先绕X轴旋转-90度,让人的朝向从“面朝Z”变成“面朝Y” rotation = trimesh.transformations.rotation_matrix(-np.pi/2, [1, 0, 0]) mesh.apply_transform(rotation) mesh.export('output_blender.obj')4.2 重建网格的后处理:平滑、补洞与顶点减面
SMPL-X网格本身是干净的流形网格,很少出现非流形边或开放边,但如果你跑的是PIFuHD,输出网格几乎一定会有小洞和噪声面。常见的后处理链是:MeshLab/QEM简化去噪 → 封闭孔洞 → 平滑3次 → 检查自相交。我会先检查网格的拓扑有效性再决定要不要减面,因为减面参数错了会把脖子和肩膀融在一起。
# meshLab命令行的减面示例,目标面数10万 meshlabserver -i output.ply -o output_simplified.ply -s simplify.mlxsimplify.mlx里设置quadric edge collapse的target face数为100000,这个数值对SMPL-X本身就太低了——它的面数大概在2万左右,不需要减面;但PIFuHD的原始网格动辄几十万面,减到10万级别才能流畅导入Unity。
4.3 模型导入Blender/Maya前的全局缩放与单位设置
SMPL-X的单位是米(meters),而Blender默认单位也是米,理论上是对的。但很多次我遇到导入后模型巨大或微小的现象,原因往往是OBJ导出时精度丢失:6位小数对于米单位来说精度到了微米级,足够用;但如果你把顶点坐标直接复制到其他程序时被转成了整数类型,模型就直接崩溃成碎块了。我习惯在OBJ导出时强制保留6位小数,并确认OBJ头部没有o对象分组信息丢失——有些工具会把多组mesh合并成一个object,导致材质的UV和顶点索引对不上。
另外有一个很实用的技巧:在Blender里导入OBJ后,用Scale属性检查一下模型整体尺寸,如果人的高度不在1.6~1.9米范围,就应该怀疑单位换算出了问题。SMPL-X模型默认高度大约1.75米,偏差超过5%就要回头查你的缩放管线了。
5. 单图重建避坑指南:五个让人翻车的真实踩坑记录
5.1 输入图片背景太乱,重建出来像“融化”的人
现象:背景里有人、有柱子、有窗帘皱褶,重建的3D模型在肩部和臀部出现了大量无意义的突起和凹陷。
原因:PyMAF-X的前置是2D关键点检测和人体分割mask,当背景里有和人体边缘灰度值接近的物体时,mask会把背景物体的一小部分划进人体范围,SMPL-X参数化模型被迫用形状参数去拟合这个错误的分割区域,最终体现在mesh表面。
解决:先跑一次分割模型(如Mask2Former或YOLOv8-seg),把人体mask提取出来,把背景填充成中性灰色(RGB 127,127,127)再输入PyMAF-X。注意不能把背景填成纯黑或纯白,因为模型是基于ImageNet均值归一化的,极端亮度会直接扭曲特征提取。
5.2 人的边缘被裁切,手部直接残缺
现象:输入照片里手腕或脚踝正好在画面边界,重建结果里手部完全缺失或扭曲成“爪子”。
原因:PyMAF-X在处理输入时固定把图像resize/pad到224×224,如果人的肢体在原始图中被切到边缘,那么padding之后肢体的比例会被压缩或部分丢失,2D关键点检测到的手腕位置不可信,模型被迫脑补手部姿态。
解决:在预处理时做一次人体检测,如果检测框超出图像边界,先把整张图按比例扩展四周画布(用镜像填充或边缘复制),确保人物完整出现在框内。不要直接用“检测框+resize”的方式裁剪人体,因为检测框本身不一定完全包住张开的手指和脚趾。
5.3 显存不够,batch size调小时效果突然变差
现象:显存8GB,跑单张图没问题;一跑批量就OOM,把batch size调到1却又发现重建的pose和单图输入时差别很大。
原因:PyMAF-X的推理结果和batch size本身无关,但和随机裁剪/翻转的数据增强有关。仓库里的demo代码在推理时如果用了和训练一致的随机增强流程,batch size变化会导致每次跑出来的人体朝向和位置略有不同。
解决:在推理模式下,显式关闭数据增强(model.eval()+ 设torch.no_grad()还不够,还要检查数据加载器有没有把shuffle和augment设为False)。如果你是自己的脚本,在推理前把transforms.Compose里的RandomAffine、ColorJitter全部去掉,只保留归一化和resize。这个坑非常隐蔽,我当时排查了一整天才定位到是数据增强在推理时没关干净。
5.4 全身照片出来的却只有半身结果
现象:输入一张正常全身照(人物占画面高度80%),输出的SMPL-X却只有上半身,膝盖以下完全消失。
原因:这是最容易让人误判的问题。PyMAF-X有两个独立模型:一个是全身模型(HMR2.0-based),一个是上半身模型。当输入图像中的人体检测框高宽比超过2.0时,默认配置会触发上半身模型的切换。这是为了适配“半身照”的优化策略,但用在全身照上就是灾难。
解决:在配置文件中强制设置MODEL.FOCAL_LENGTH和DATASET.BODY_MASK参数,或者直接指定使用smplx_pymafx全身模型,不让它自动切换。具体做法是把pymafx/config/defaults.py里的USE_FULL_BODY设为True,并在推理时传入force_full_body=True。
5.5 同一张图跑两次结果不一样
现象:同一张图、同一个权重文件、同一台机器,两次推理出的SMPL-X参数略有差异,mesh顶点的最大位移有3~5厘米。
原因:如果代码里没有显式设置随机种子,PyMAF-X的2D关键点检测器或后续的优化步骤用到了随机采样(比如SMPLify-X优化),那么每次运行的局部最优解会不一样。单图重建本身是一个高度非凸的优化问题,小扰动就会让姿态落在不同的局部极小值。
解决:在推理脚本最前面设置torch.manual_seed(0)、np.random.seed(0),并且把smplx_model的num_global_orient_bins和num_body_pose_bins选得足够大(默认是128),减少角度分桶带来的量化误差。如果你要实现“可复现的重建”,这个种子设置是关键。
提示:第5.5条在PIFuHD上表现更夸张,隐式神经场叠加了网络推断的随机性,同图两次重建在手指部位的差异可能超过5厘米。如果做批量处理并需要结果稳定,建议固定好随机种子并且锁死推理线程数。
6. 从一张图到动画资产:SMPL-X转FBX/glTF并检查绑定
拿到SMPL-X网格只是第一步,如果要做动画驱动,需要把mesh转成FBX或glTF,同时保留骨骼和蒙皮权重。SMPL-X本身有官方提供的Blender插件和转换工具,可以直接导出FBX,但导出的结果有几个隐藏坑:一是骨骼名称是中性化命名(如pelvis、spine1),导入Unity后需要重新映射;二是指尖骨骼是SMPL-X特有的24+15结构,不是标准Mixamo骨架,直接套Mixamo动画会有穿模。
我常用的验证方法:把转好的FBX导入Blender,摆一个T-pose,然后在Pose Mode下手动旋转右臂骨骼,观察肘部弯折是否符合解剖学极限。如果肘部翻转角度超过150度或者肩膀出现拉伸变形,说明蒙皮权重转移有问题,需要检查SMPL-X转FBX时的权重重映射表。
用glTF的话,有一个额外好处——可以直接用three.js在浏览器里检查模型,但这需要把SMPL-X的blendshape转成glTF的morph target。这个转换要注意:SMPL-X的表情参数是52维的expression,对应52组morph target位移,转glTF时要逐一验证顶点索引对齐,否则表情一旦驱动就是错的。我建议在Blender里先把表情blendshape烘焙成shape keys再导出glTF,而不是直接用插件一键转,虽然麻烦但可控。
如果有余力可以更进一步:把单图重建结果和视频动捕结合——先用PyMAF-X从单帧恢复体型,再用骨骼动画序列驱动这个静态mesh,得到一个全身3D建模的动态版。我自己的习惯是每处理一批新数据就做一个回归测试:固定5张不同姿态的图片,跑完重建后检查mesh中心点位置、网格顶点数量、人脸轮廓完整性这三项指标,确保改动任一步骤都没有破坏基础结果。希望这些参数和踩坑记录帮你少走弯路。
本文还有配套的精品资源,点击获取