MonoHair 复现:权重、重建资产与 demo 数据(组织)
这个仓库包含四部分:
- MonoHair(CVPR 2024)重建所用的 DeepMVSHair 权重;
- 在作者提供的两个假发样例上,我们自己跑出的发丝重建结果;
- 在 big_wavy1 上做剪发、物理 demo 时生成的派生数据;
- 处理真人数据时 MonoHair 需要的 SMPL-X、PIXIE、DELTA 模型文件(仅本人使用,见上面的警告)。
生成这些数据的代码在 Snowflake-Pink/monohair-repro(私有)。
hf download PincoLi/monohair-repro --local-dir monohair-repro-hf
# 只要某一部分,可以加 --include "reconstructions/*"(或 "weights/*"、"derived/*")
文件
personal_licensed/
assets_data/ 解压好的 14 个文件,直接放进 MonoHair/assets/data/
original_downloads/ 原始下载文件,未改动
weights/
DeepMVSHair/ infer_inner 推断头发内部用的网络权重(见下文)
SHA256SUMS
reconstructions/
big_wavy1/ connected_strands.hair 最终发丝(173,205 根,1,437 万点,173 MB)
bust_long_tsfm.obj 头肩网格(与发丝同一坐标系),material_0.* 是它引用的材质
scalp_tsfm.obj 头皮网格(发根所在区域)
cam_params.json 作者数据里的相机参数,同一坐标系
compare_with_authors.json 与作者发布结果的对比指标
baseline_authors_full_vs_full1.json 作者两版结果之间的差异(基线)
short_curly1/ 同上(143,516 根,545 万点,66 MB;没有基线文件)
derived/big_wavy1/
haircut/big_wavy1_haircut.npz 剪发 demo 的全部预计算数据
physics/big_wavy1_model.npz 可仿真的发丝模型(5 mm 间距的弹性杆)
physics/cantilever.json 弯曲刚度标定结果(物理脚本运行时会读)
physics/support_scan.json 发根支撑强度扫描:支撑越强,静置后越接近重建形状
physics/demo_keyframes/meta.npz 物理 demo 的元数据(剪线、剪断时刻、头部转角、量化范围)
physics/demo_keyframes/pos_0001.npy 第 1 帧:重力下静置 6 s 之后,即"带物理的重建"
physics/demo_keyframes/pos_0140.npy 第 140 帧:第一次转头之后、下剪之前
physics/demo_keyframes/pos_0300.npy 第 300 帧:剪完并再次转头之后
videos/ 两个 demo 的 1080p 视频和封面
重建质量
| case | 发丝数 | Chamfer(与作者结果) | 5 mm 内的点(我们→作者 / 作者→我们) | 方向一致性 |cos| |
|---|---|---|---|---|
| big_wavy1 | 173,205 | 1.82 mm | 97.5% / 97.9% | 0.934 |
| short_curly1 | 143,516 | 1.62 mm | 98.7% / 99.0% | 0.891 |
作者自己发布的两版 big_wavy1 结果之间相差 2.40 mm,所以我们的结果在"同一方法重跑一次"的正常波动范围之内。
已知问题:约 6% 的发丝没有长在头皮上(是悬空的碎段)。物理模型里,这些碎段挂在最近的有根发丝上。
.hair 格式(MonoHair 原生格式,小端序)
| 字段 | 类型 | 说明 |
|---|---|---|
n_strands |
uint32 | 发丝数 |
n_points |
uint32 | 总点数 |
| 每根发丝的点数 | uint16 × n_strands | |
| 点坐标 | float32 × 3 × n_points | 每根发丝从发根到发梢依次排列 |
import struct
import numpy as np
def load_hair(path):
with open(path, 'rb') as f:
n, npts = struct.unpack('<II', f.read(8))
cnt = np.frombuffer(f.read(2 * n), '<u2').astype(np.int64)
pts = np.frombuffer(f.read(12 * npts), '<f4').reshape(-1, 3)
off = np.concatenate([[0], np.cumsum(cnt)])
return pts, off # 第 i 根发丝 = pts[off[i]:off[i + 1]]
坐标系
- 重建坐标系(
.hair、.obj、cam_params.json):单位米,y 轴向上,脸朝 +z。头部大约在 y = 1.4–2.0 m 的位置。 - Blender 坐标系(
derived/下的所有文件):单位米,z 轴向上,脸朝 −y。由重建坐标系变换得到:先(x, y, z) → (x, −z, y),再减去平移shift。平移让水平方向以头发包围盒为中心,地面 z = 0 在头肩网格的最低处。big_wavy1_model.npz里存了shift。
派生数据
**big_wavy1_model.npz**(物理模型)
| 字段 | 说明 |
|---|---|
pos、off |
重采样到 5 mm 间距的发丝,格式同上 |
rooted |
每根发丝的发根是否在头皮上(距头肩网格 1.5 cm 内) |
parent |
没有根的发丝挂在哪个点上(全局点索引;有根的为 −1) |
bust_v、bust_f |
头肩网格(碰撞体) |
shift、seg |
坐标平移、杆的分段长度 |
物理 demo 关键帧
pos_XXXX.npy 用 uint16 量化存储,发丝划分用 meta.npz 里的 off(与 big_wavy1_model.npz 相同):
m = np.load('meta.npz')
q = np.load('pos_0001.npy')
P = m['qlo'] + q / 65535.0 * (m['qhi'] - m['qlo']) # (N, 3),单位米,精度约 21 µm
meta.npz 里的主要字段:
cut_abs:每根发丝被剪掉的第一个点的全局索引,这个点及以后的部分是剪下的发尾;−1 表示不剪。tcut:剪刀经过这根发丝的帧号。yaw:每帧头部绕 z 轴的转角。zc、slope:剪线高度为zc + slope · y。F0–F1:剪刀绕头一圈的帧范围。rise:每根被剪发丝的新发梢在剪后的回弹量(mm)。
仿真参数:
- 头发:直径 70 µm,密度 1.3 g/cm³,E = 3 GPa;
- 每帧 40 个子步,24 fps;
- 发根支撑 3e-3 N/m,5 cm 衰减,发梢处为 0。
静置后与重建形状平均相差 11.7 mm(中位数 2.6 mm,p95 62 mm)。
**big_wavy1_haircut.npz**(剪发 demo,纯几何,不含物理)
| 字段 | 说明 |
|---|---|
pos、off、rad |
重采样到 2.5 mm 的发丝,以及渲染用的半径(发根 45 µm,渐细到发梢 20 µm) |
ci |
每根发丝被剪掉的第一个点(局部索引;等于点数表示不剪) |
Pc |
剪线与发丝的交点 |
tcut |
剪刀经过的帧号 |
cut_strands |
被剪的发丝 |
cpos、coff、crad |
剪下的发尾 |
cen、vel、axis、omega、land_dt、lift、spread、land_floor |
发尾作为刚体下落、翻转、落地的参数 |
rmax |
每个方位角上剪刀的半径 |
meta |
[FPS, 帧数, 开剪帧, 剪完帧, 起始方位角, 剪线高度, 剪线斜率, 重力, 地面高度, 最大翻转角] |
bust_v、bust_f |
头肩网格 |
字段的具体用法见代码仓库里的 scripts/haircut_prep.py 和 scripts/haircut_render.py。
权重
weights/DeepMVSHair/ 与上游仓库 commit 38c3bce 下 submodules/DeepMVSHair/trained/ 里的文件完全相同(见 SHA256SUMS),没有重新训练或修改。放在这里,是为了在访问 GitHub 很慢的环境(比如 SenseCore 容器)里也能下载。
| 目录 | 说明 |
|---|---|
wky10-30-undirection/ |
MonoHair 默认配置实际使用的这组(configs/dense_sample_real/vit_standard.txt),我们的复现也用这组 |
wky04-19/、wky09-21-depth/、wky10-14-cat_depth/ |
上游一起发布的其他版本 |
每组包括 occ.pth(占据)、ori.pth(方向)和训练统计 stats_*.txt。放回代码目录:
cp -r monohair-repro-hf/weights/DeepMVSHair/* MonoHair/submodules/DeepMVSHair/trained/
MODNet、CDGNet、face-parsing 的预训练模型不在这里;需要时用上游的 fetch_pretrained_model.sh 下载。
需要注册的模型文件(personal_licensed/,仅本人使用)
只有处理真人数据(多视角头部拟合)时才需要;作者的假发样例用的是固定的头模,用不到。
assets_data/ 是 MonoHair 的 configs/Bust_fit/base.yaml 所需的文件,放回代码目录:
cp monohair-repro-hf/personal_licensed/assets_data/* MonoHair/assets/data/
原始文件(original_downloads/) |
来源 | 解压出的文件 |
|---|---|---|
smplx/SMPLX_NEUTRAL_2020.npz |
SMPL-X,SMPL-X 2020 | 本身 |
smplx/smplx_mano_flame_correspondences.zip |
SMPL-X,MANO and FLAME vertex indices | MANO_SMPLX_vertex_ids.pkl、SMPL-X__FLAME_vertex_ids.npy |
pixie/utilities.zip |
PIXIE | flame2smplx_tex_1024.npy、smplx_tex.*、smplx_hand.obj、SMPLX_to_J14.pkl、uv_face*_mask.png 等 11 个 |
delta/delta_utilities.zip |
DELTA(上游 fetch_data.sh) |
FLAME_masks.pkl(另有 head_template_mesh_mouth.obj、subdiv_level_1/,没有放进 assets_data/) |
delta/delta_utilities2.zip |
DELTA | FLAME_albedo_from_BFM.npz(解压后 1.26 GB) |
这些文件各自受其官方许可约束(非商业研究用途、禁止再分发),不适用本仓库其余部分的 CC BY-NC 4.0。
许可与出处
输入数据(作者的假发样例)、MonoHair 代码和 DeepMVSHair 权重都是 CC BY-NC 4.0,这里的权重和结果也遵守这一许可:只能非商业使用,并需要注明出处。
Keyu Wu, Lingchen Yang, Zhiyi Kuang, Yao Feng, Xutao Han, Yuefan Shen, Hongbo Fu, Kun Zhou, Youyi Zheng. MonoHair: High-Fidelity Hair Modeling from a Monocular Video. CVPR 2024.