☰
从一段手机视频到 3D 人体白模:SAM2 + GVHMR 实践
2026/10/2 11:46:09 网站建设 项目流程

最近做了个很有意思的实践

这个项目到底做了什么?

一句话:给一段手机拍的视频,先用 SAM2 把人抠出来,再用 GVHMR 恢复出标准的 SMPLX 人体模型,最终得到可以在三维空间里任意旋转观看的 3D 白模动画。

整个流程用到的核心技术栈:

技术用途运行环境
SAM2(Segment Anything Model 2)视频人物分割Meta
Depth Anything V2单目深度估计CPU
3D Gaussian Splatting逐帧 3D 重建CPU (简化版)
GVHMR人体运动恢复CPU
SMPLX参数化人体模型PyTorch

不需要深度相机,不需要动作捕捉服,不需要绿幕背景。只需要一段普通的手机视频和一台有 CPU 的电脑。

为什么要做这件事?

视频是二维的。你的眼睛(和相机)只能看到投影到平面上的光影信息,深度信息在成像的那一刻就丢失了。一个人侧身站着和正面站着,在照片上可能看起来差不多大。更麻烦的是,视频里的人可能在走动、转身、手臂挥舞——每一帧的姿态都不一样,而你需要从这些二维画面里"猜"出三维的形状和运动。

想象一个具体场景:你站在商场门口,看到一个街舞选手在表演 Breaking。你掏出手机拍了一段 10 秒的视频。你想把这段舞蹈"保存"下来,不是作为一段平面视频,而是作为一个可以在三维空间里 360 度观看、可以暂停在任何一帧仔细看动作细节的 3D 数字人。

这就是我们要做的事。


实战:用自己的数据集走完全流程

光说不练假把式。下面我们用data/custom/long_video这个数据集,完整走一遍处理流程。

这个数据集包含 300 帧图像(1280x720 分辨率,30 FPS),以及对应的 300 张分割掩码。掩码中白色区域是人物,黑色区域是背景——这是用 SAM2 做视频目标追踪后得到的。

整个处理流程可以用一张图概括:

原始视频 (300帧) -> SAM2分割 -> 深度估计 -> 3D点云 -> 4D组装 -> 高斯训练 -> GVHMR人体建模 -> 3D白模

第一步:SAM2 视频分割

虽然我们的数据集已经有了掩码,但值得了解一下这些掩码是怎么来的。

SAM2(Segment Anything Model 2)是 Meta 推出的通用分割模型,支持视频目标追踪。你给它一张图片和一个提示(点、框或掩码),它就能在后续帧中持续追踪同一个目标。

在我们的场景中,SAM2 的propagate_in_videoAPI 被用来追踪视频中的每一帧人物,输出二值掩码。掩码文件名格式为frame_000000_target_1.png,其中target_1表示追踪到的第 1 个目标实例。

SAM2 相比逐帧分割的优势在于:它利用了帧间的时间一致性,追踪结果更连贯,不会出现"第 10 帧追踪到人、第 11 帧突然追踪到树"这种跳帧问题。

下面是我们在 kun 数据集上的 SAM2 分割效果展示(左边是原图,右边是分割掩码叠加效果,绿色区域为检测到的人物):

kun 数据集的 SAM2 分割效果。绿色区域为 SAM2 追踪到的人物掩码,可以看到即使在人物转身、走动等复杂动作下,分割结果依然连贯稳定。

分割的质量直接决定了后续重建的效果。如果掩码把背景也包含进来了,重建出来的 3D 模型就会"胖"一圈;如果掩码把人的手臂切掉了一部分,那部分就永远重建不回来了。

第二步:深度估计

有了掩码,我们知道"哪些像素属于人"。但光知道哪些像素属于人还不够——我们还需要知道这些像素距离相机有多远。

这就是深度估计的任务。我们使用Depth Anything V2模型,它是一个轻量级的单目深度估计模型,可以在 CPU 上运行。

深度估计的输出是一张深度图:每个像素的值表示该点距离相机的远近。

这里有一个微妙的问题:单目深度估计只能给出相对深度,而不是绝对深度。也就是说,它知道"A 点比 B 点远",但不知道"A 点距离相机 exactly 3.5 米"。我们需要一个缩放因子来把相对深度转换为绝对深度。在我们的实验中,这个缩放因子设为 5.0 米。

关键公式是针孔相机模型的反投影:

X = (u - cx) * depth / fx Y = (v - cy) * depth / fy Z = depth * scale

其中(u, v)是像素坐标,(cx, cy)是主点,(fx, fy)是焦距,depth是深度图的值。通过这个公式,我们可以把每个像素从二维图像空间"反投影"到三维世界空间。

在我们的实验中,相机参数设置为fx=fy=600, cx=640, cy=360,深度缩放因子为5.0米。

第三步:逐帧 3D 点云生成

有了深度图和掩码,生成 3D 点云就是水到渠成的事:

  1. 遍历掩码中所有白色像素(属于人物的像素)
  2. 对每个像素,用上面的反投影公式计算其 3D 坐标
  3. 从原始图像中读取该像素的 RGB 颜色
  4. 将(X, Y, Z, R, G, B)保存为一个 3D 点

每帧生成 5,000 到 17,000 个点(取决于人物在画面中的大小和掩码覆盖面积)。所有点保存为 PLY 格式的点云文件。

这个过程对每一帧独立执行。300 帧视频,每帧生成一个点云文件,最终得到 300 个 PLY 文件。

每个点云文件包含了该帧中人物的完整 3D 几何信息:形状、姿态、颜色。但它们是"各自为政"的——第 10 帧的点云和第 11 帧的点云之间没有显式的关联。

第四步:4D 时序组装

把 300 个独立的 3D 点云按时间顺序排列,就得到了一个 4D 序列。

"4D"这个概念其实很简单:3D 空间 + 1D 时间。每一帧是一个 3D 快照,按时间排列就形成了 4D 序列。

在我们的实现中,这些帧点云被组织在一个统一的数据结构中,支持逐帧播放、时间插值和变形追踪。

第五步:3D Gaussian Splatting 训练

点云虽然直观,但渲染质量有限。每个点就是一个小圆球,放大看会有明显的颗粒感。而且点云不支持新视角合成——你只能从原来相机的角度看,不能"绕到背后"去看。

为了得到更高质量的渲染和新视角合成能力,我们对每一帧训练3D Gaussian Splatting模型。

3D Gaussian Splatting 的核心思想是:用一组三维高斯椭球来表示场景。每个高斯有 5 组参数:

参数维度含义
Position (xyz)3高斯中心的 3D 位置
Scale (s)3三个轴的缩放比例
Rotation (q)4四元数表示的旋转
Opacity (a)1不透明度
SH Coeffs48+球谐函数系数,表示从不同角度看过去的颜色

训练过程是:初始化高斯参数(从点云初始化),然后通过可微分渲染器渲染出图像,与原始图像比较,计算损失,反向传播更新参数。经过反复迭代,高斯的位置、大小、颜色都会逐渐优化,最终渲染出和原始图像几乎一样的效果。

在我们的 CPU 模式下,每帧训练 50 次迭代,最终损失约 0.73。虽然不如 GPU 训练几千次的效果好,但已经能看出人物的基本形状和颜色分布了。

第六步:GVHMR 人体运动恢复

前面的步骤给了我们每一帧的 3D 形状,但这些形状是"散装"的——没有统一的人体模型约束。点云可能看起来像个人,但它没有"24 个关节、10 个体型参数"这样的结构化表示。

GVHMR(Gravity-View Human Motion Recovery)的加入,让我们能从单目视频中恢复出标准的 SMPLX 人体模型参数。

GVHMR 的核心创新是重力视角坐标系(Gravity-View Coordinates)。传统方法直接在相机坐标系或世界坐标系下预测人体运动,但这两种坐标系都有问题:相机坐标系下,人体的运动会随着相机转动而"虚假"变化;世界坐标系下,"上"方向的定义是任意的。GVHMR 利用重力方向定义了一个中间坐标系,让预测变得稳定且物理合理。

GVHMR 的处理流程分三步:

  1. YOLO 追踪:在每一帧中检测并追踪人物边界框
  2. ViTPose 姿态估计:提取人体关键点特征
  3. Transformer 推理:将特征序列输入 GVHMR 模型,输出 SMPLX 参数

GVHMR 的输出是每帧的 SMPLX 参数:

  • body_pose(63 维):21 个关节的旋转角度,编码了人体的姿态
  • betas(10 维):体型参数,决定了人是高是矮、是胖是瘦
  • global_orient(3 维):全局朝向,决定了人面朝哪个方向
  • transl(3 维):全局位移,决定了人在空间中的位置

有了这些参数,我们就可以用 SMPLX 模型渲染出标准的 3D 人体网格(白模)。这个白模有 10,475 个顶点和 20,908 个三角面片,是一个完整的人体表面模型。

下面是我们用 GVHMR 对 自己数据集生成的 SMPLX 白模动画:

GVHMR 恢复的 SMPLX 人体模型。可以看到人物的行走、转身等动作被准确还原,体型保持一致,运动流畅自然。坐标系已修正为 Z 轴朝上,人物自然站立。

这个白模动画展示了 GVHMR 的核心能力:从单目视频中恢复出完整的人体运动参数。注意,这里的坐标系已经修正为 Z 轴朝上(重力反方向),所以人物是自然站立的,而不是趴着或躺着。


实战二:用 kun 数据集验证流程

为了验证处理流程对不同数据集的适应性,我们用data/custom/kun数据集又跑了一遍完整流程。

kun 数据集特点

kun 数据集包含908 帧图像(比 long_video 多了 3 倍),同样有对应的 SAM2 分割掩码。这个数据集的人物动作更加复杂,包含了更多的转身和姿态变化。

批处理策略

908 帧视频如果一次性送入 GVHMR 推理,预处理(YOLO + ViTPose + ViT 特征提取)会超过 10 分钟,很容易超时。我们的解决方案是分批处理:

  1. 将 908 帧按每批 150 帧切分,共 6 批
  2. 每批独立创建 masked 视频、运行 GVHMR 推理、渲染白模
  3. 最后将所有批次的白模帧合并,重新切分为 5 个均匀大小的 GIF
批次帧范围帧数预处理耗时推理耗时
00-149150~8 min0.40s
1150-299150~8 min0.38s
2300-449150~8 min0.38s
3450-599150~6 min0.27s
4600-749150~6 min0.28s
5750-899150~6 min0.27s

总计处理了 900 帧(最后 8 帧因太短被跳过),生成了 450 帧白模动画。

坐标调整:让人物踩到地面

在渲染白模时,我们发现了一个问题:人物的脚没有踩在地面上,而是悬浮在空中或者陷入地下。

这是因为 GVHMR 输出的transl参数是相对于某个参考点的,不一定保证脚底刚好在 Z=0 的平面上。

我们的解决方案是:逐帧调整 Z 坐标,让每帧的最低点(通常是脚底)对齐到 Z=0。

# 对每一帧min_z=vertices[:,2].min()# 找到最低点vertices[:,2]-=min_z# 整体上移,让最低点到达 Z=0

这个简单的调整让白模动画看起来更加自然——人物像是站在地面上,而不是漂浮在空中。

结果展示

450 帧白模动画(600x600 分辨率,15 FPS)被均匀切分为 5 个 GIF,每个约 5 MB:

第 1 段(帧 0-89,0-6 秒):

第 2 段(帧 90-179,6-12 秒):

第 3 段(帧 180-269,12-18 秒):

第 4 段(帧 270-359,18-24 秒):

第 5 段(帧 360-449,24-30 秒):

kun 数据集的 SMPLX 白模动画,共 450 帧(30 秒)。5 个 GIF 均匀覆盖整个视频序列。坐标已调整,脚底踩在地面上。灰色半透明平面表示地面(Z=0)。

对比 long_video 和 kun

指标long_videokun
总帧数300908
处理帧数150900 (6批 x 150)
视频时长5 秒30 秒
总预处理耗时~6.6 分钟~42 分钟
总推理耗时0.26 秒~2.3 秒
白模帧数75450
GIF 数量1 个5 个 (每个 ~5 MB)

两个数据集的处理结果都非常成功,证明了我们的流程具有良好的泛化能力。


技术细节深挖

为什么选择 Gaussian Splatting 而不是 NeRF?

NeRF(Neural Radiance Field)用神经网络隐式表示场景,渲染质量高但训练慢。3D Gaussian Splatting 用显式的几何基元(高斯椭球)表示场景,渲染速度快(可以实时),训练也快(分钟级)。

对于我们的任务,Gaussian Splatting 的优势更明显:

  1. 可编辑性:每个高斯是一个独立的几何基元,可以单独移动、缩放、删除
  2. 可组合性:不同帧的高斯可以组合在一起,形成 4D 模型
  3. 渲染速度:可微分光栅化器让训练和渲染都很快
  4. 显式表示:不像 NeRF 那样是一个"黑盒"神经网络,高斯的参数是直观的、可解释的

CPU 模式下的挑战

我们的实验环境是纯 CPU(没有 GPU)。这带来了一些挑战:

  1. 深度估计慢:Depth Anything V2 在 CPU 上每帧需要几秒
  2. 高斯训练慢:可微分渲染在 CPU 上比 GPU 慢 10-100 倍
  3. GVHMR 推理:Transformer 模型在 CPU 上推理需要几分钟(预处理约 6-8 分钟,推理不到 1 秒)

但 CPU 模式的好处是:任何人都能跑,不需要昂贵的 GPU。对于演示和验证目的,CPU 模式足够了。

掩码质量对重建的影响

分割掩码的质量是整个流水线的瓶颈。如果掩码不准确:

  • 过分割(掩码包含了背景):重建出来的 3D 模型会有"鬼影",人物周围漂浮着背景的碎片
  • 欠分割(掩码切掉了人物的一部分):人物的手臂、腿等部位可能缺失,重建不完整

在我们的数据集中,SAM2 的掩码质量总体不错,但有些帧的掩码边缘不够精确(比如手指、头发丝),这会导致重建的细节丢失。

中间结果数据一览

让我们用数字说话。以下是处理过程中产生的关键数据:

点云统计

指标数值
总帧数300 帧 (采样处理 42 帧)
每帧点数5,000 - 17,000
总点数约 400,000 个 3D 点
点格式XYZ + RGB (6 个浮点数)
文件格式PLY
相机参数fx=fy=600, cx=640, cy=360

高斯训练统计

指标数值
训练帧第 0、20、40 帧
每帧迭代50 次
每帧高斯数约 870-1,000 个
最终损失~0.73
图像尺寸180x320 (缩小以加速)
损失函数L1 + SSIM

GVHMR 推理统计

指标数值
处理帧数150 帧/批 (5 秒/批)
预处理耗时~6-8 分钟/批 (CPU)
推理耗时0.27-0.40 秒/批
SMPLX 顶点数10,475
SMPLX 面数20,908
输出参数body_pose(63) + betas(10) + orient(3) + transl(3)

掩码覆盖率变化

有趣的是,掩码覆盖率(掩码中白色像素占整帧的比例)在整个视频序列中是变化的:

  • 第 0 帧:1.5%(人物在画面中很小)
  • 第 75 帧:6.5%(人物走近了)
  • 第 135 帧:26.7%(人物占据了画面的四分之一)
  • 第 140 帧:31.0%(人物最大的一帧)
  • 第 190 帧:8.8%(人物走远了)

这种覆盖率的变化直接影响点云的密度:覆盖率大的帧,点云更密集,重建质量更高;覆盖率小的帧,点云更稀疏,细节可能丢失。


踩过的坑和经验教训

在跑这个流水线的过程中,我们遇到了不少坑。这里分享几个最有价值的经验:

坑一:坐标系方向搞反了

SMPLX 模型的默认坐标系是 Y 轴朝上,但 Matplotlib 的 3D 绘图是 Z 轴朝上。如果你不做坐标转换,渲染出来的白模就是趴着或者躺着的——看起来像是人在做俯卧撑。

解决方法很简单:交换 Y 和 Z 轴,并翻转前后方向:

vertices_fixed[:,0]=vertices[:,0]# X 不变vertices_fixed[:,1]=-vertices[:,2]# Y = -Zvertices_fixed[:,2]=vertices[:,1]# Z = Y

坑二:CPU 模式下 chumpy 不兼容

SMPL 模型的原始格式依赖 chumpy 库,而 chumpy 在 Python 3.10+ 上已经不能用了(inspect.getargspec被移除了)。解决方法是使用项目中提供的SmplLite和SmplxLite类,它们绕过了 chumpy 的依赖,直接用 PyTorch 张量加载模型参数。

坑三:深度缩放因子的选择

深度缩放因子(depth_scale)决定了重建出来的 3D 模型的绝对大小。设得太小,人物就像一个手办;设得太大,人物像一个巨人。我们最终选择了 5.0 米,这是一个比较合理的值——它让重建出来的人物高度大约在 1.7-1.8 米之间,接近真实人体身高。

但这个值不是万能的。如果你的视频里人物离相机很近,可能需要调小;如果人物离得很远,可能需要调大。最好的方法是:先跑一帧,看看重建出来的人物大小是否合理,然后调整缩放因子。


总结

整个项目做的事情可以用一句话概括:用 SAM2 把人从视频里抠出来,用 GVHMR 恢复出标准的 SMPLX 人体模型,最终得到可以在三维空间里任意旋转观看的 3D 白模动画。

核心技术栈:SAM2(分割)+ Depth Anything V2(深度估计)+ 3D Gaussian Splatting(3D 重建)+ GVHMR(人体运动恢复)+ SMPLX(参数化人体模型)。

我们的实验展示了完整的处理流程:从视频出发,经过分割、深度估计、点云生成、4D 组装、高斯训练、GVHMR 人体建模,最终得到一个多层次的 3D 人体资产。

这个资产可以用于:

  • 动作回放:在 3D 查看器中任意角度观看人物动作
  • 动作编辑:修改 SMPL 参数来调整姿态
  • 虚拟现实:将数字人导入 VR/AR 场景
  • 动作分析:提取运动学参数用于运动科学研究
  • 游戏开发:将真实人物的动作导入游戏引擎

从一段普通视频到一个完整的 3D 数字人——而我们在纯 CPU 环境下走通了整个流程,证明了这项技术的可及性。


GVHMR 项目:https://github.com/Kehan-L/GVHMR
SAM2 项目:https://github.com/facebookresearch/sam2
Depth Anything V2:https://github.com/DepthAnything/Depth-Anything-V2

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询