Diffusers
Safetensors
AR / test_vae.py
xfcghj's picture
Upload folder using huggingface_hub
f0fc238 verified
Raw
History Blame Contribute Delete
8.16 kB
import io
import os
import tarfile
import pickle
import zstandard
import matplotlib.pyplot as plt
import numpy as np
import torch
import trimesh
# 引入刚才写好的 VAE 包装类
from models.vae import TripoSGVaeWrapper
# 引入 TripoSG 官方提供的层次化几何提取工具
from triposg.inference_utils import hierarchical_extract_geometry
# 配置路径
archive_path = "/home/dataset-assist-0/usr/lh/ysh/dw/RL/AR/data/DyMesh_50000v_16f_0001_part_00"
triposg_weights = "/home/dataset-assist-0/usr/lh/ysh/dw/RL/AR/TripoSG/pretrained_weights/TripoSG"
output_image_path = "vae_reconstruction_test.png"
def get_first_mesh_frame(archive_file):
"""从数据集中提取第一个有效物体的第0帧"""
with open(archive_file, 'rb') as fh:
dctx = zstandard.ZstdDecompressor()
with dctx.stream_reader(fh) as reader:
with tarfile.open(fileobj=reader, mode='r|') as tar:
for member in tar:
if member.isfile():
f = tar.extractfile(member)
if f is not None:
data = pickle.load(io.BytesIO(f.read()))
if isinstance(data, dict) and 'vertices' in data and 'faces' in data:
if data['vertices'].shape[0] == 16:
# 提取第0帧 (形状为 [V, 3]) 和 共享的面片
v_f0 = data['vertices'][0]
faces = data['faces']
short_name = os.path.basename(member.name).split('.')[0]
return v_f0, faces, short_name
raise RuntimeError("未在压缩包中找到合法的 16帧 动态 Mesh 数据。")
if __name__ == "__main__":
device = "cuda" if torch.cuda.is_available() else "cpu"
dtype = torch.float16
# 1. 读取原数据并抽取静态帧
print("📦 正在从数据包中读取测试 Mesh...")
raw_vertices, raw_faces, obj_name = get_first_mesh_frame(archive_path)
print(f"成功提取物体 [{obj_name}] 的第一帧. 顶点数: {raw_vertices.shape[0]}, 面数: {raw_faces.shape[0]}")
# 2. 对原网格在内存中做归一化,以便后续做对齐可视化
mesh_raw = trimesh.Trimesh(vertices=raw_vertices, faces=raw_faces, process=False)
max_edge = np.max(mesh_raw.bounding_box.extents)
mesh_raw.vertices = (mesh_raw.vertices - mesh_raw.center_mass) * (2.0 / max_edge)
# 3. 初始化 VAE 包装模型
print("🔮 正在初始化冻结的 TripoSG VAE 模型...")
vae_wrapper = TripoSGVaeWrapper(weights_dir=triposg_weights, device=device, dtype=dtype)
# 4. 编码 (Mesh -> Latent Code)
# 修改 3. 编码部分的调用逻辑:
print("🚀 正在将 Mesh 表面密集点云编码至隐空间...")
# 将 numpy 数组显式转换为 torch.Tensor
v_tensor = torch.from_numpy(raw_vertices)
f_tensor = torch.from_numpy(raw_faces)
latent_code = vae_wrapper.encode_mesh(v_tensor, f_tensor, num_samples=204800)
print(f"编码成功!隐特征张量形状: {latent_code.shape}")
# 5. 解码重建 (Latent Code -> SDF -> Marching Cubes Mesh)
print("📐 正在通过层次化八叉树提取器(Hierarchical Octree)解码并重建 Mesh...")
geometric_func = lambda x: vae_wrapper.decode_latent(latent_code, sampled_points=x)
output_geometry = hierarchical_extract_geometry(
geometric_func,
device=device,
bounds=(-1.005, -1.005, -1.005, 1.005, 1.005, 1.005),
dense_octree_depth=8, # 密度深度
hierarchical_octree_depth=9, # 恢复细节的八叉树总深度
)
# 将提取出的解算数据转换为 trimesh 对象
recon_v, recon_f = output_geometry[0]
mesh_recon = trimesh.Trimesh(vertices=recon_v.astype(np.float32), faces=recon_f, process=False)
print(f"重建成功!重建后 Mesh 顶点数: {mesh_recon.vertices.shape[0]}, 面数: {mesh_recon.faces.shape[0]}")
# 🌟 5.5 进行几何指标定量评估 (Evaluation Metric) - 使用 Scipy KDTree 修复版本
print("📊 正在执行 KDTree 算法,评测重建精度指标...")
from scipy.spatial import KDTree
num_eval_samples = 50000
# 在两个几何表面分别均匀采样 5万 个测试点
pts_raw, _ = trimesh.sample.sample_surface(mesh_raw, num_eval_samples)
pts_recon, _ = trimesh.sample.sample_surface(mesh_recon, num_eval_samples)
# 建立空间平衡二叉树,用于秒级检索最近邻点
tree_raw = KDTree(pts_raw)
tree_recon = KDTree(pts_recon)
# 1. 计算重建点到真值点云的最近距离 (Recon -> Raw) -> 代表重建偏离误差
dists_recon_to_raw, _ = tree_raw.query(pts_recon, k=1)
# 2. 计算真值点到重建点云的最近距离 (Raw -> Recon) -> 代表细节遗漏误差
dists_raw_to_recon, _ = tree_recon.query(pts_raw, k=1)
# 计算均值
mae_recon_to_raw = np.mean(dists_recon_to_raw)
mae_raw_to_recon = np.mean(dists_raw_to_recon)
# 模拟双向倒角距离 (Chamfer Distance)
chamfer_dist_approx = mae_recon_to_raw + mae_raw_to_recon
# 计算在 1% 空间尺度阈值下的 F-Score
obj_scale = np.linalg.norm(mesh_raw.bounding_box.extents)
tau = 0.01 * obj_scale # 阈值设置为物体对角线长度的 1%
precision = np.mean(dists_recon_to_raw < tau)
recall = np.mean(dists_raw_to_recon < tau)
f_score = (2 * precision * recall) / (precision + recall) if (precision + recall) > 0 else 0.0
# 打印评估报告
print("\n" + "="*50)
print("📈 VAE Mesh 自编码重建评测报告 (KDTree 加速版):")
print("="*50)
print(f"1. 重建偏离误差 (Recon -> Raw MAE): {mae_recon_to_raw:.6f} (越小越好)")
print(f"2. 细节遗漏误差 (Raw -> Recon MAE): {mae_raw_to_recon:.6f} (越小越好)")
print(f"3. 倒角距离指标 (Chamfer Distance): {chamfer_dist_approx:.6f} (越小越好)")
print(f"4. 表面对齐分数 (F-Score @ 1%): {f_score * 100:.2f}% (越大越好)")
print("="*50 + "\n")
# 6. 使用 Matplotlib 绘制同视角对比图
print("🎨 正在绘制原网格与 VAE 重建网格的对比效果图...")
fig = plt.figure(figsize=(14, 6))
# 共同视角参数控制,防止发生视差偏离
all_points = np.concatenate([mesh_raw.vertices, mesh_recon.vertices], axis=0)
mid_x, mid_y, mid_z = (all_points.max(axis=0) + all_points.min(axis=0)) / 2.0
max_range = (all_points.max(axis=0) - all_points.min(axis=0)).max() / 2.0
# 子图 1:原始 Mesh(归一化后)
ax1 = fig.add_subplot(1, 2, 1, projection='3d')
ax1.plot_trisurf(mesh_raw.vertices[:, 0], mesh_raw.vertices[:, 1], mesh_raw.vertices[:, 2],
triangles=mesh_raw.faces, cmap='coolwarm', edgecolor='none', alpha=0.7)
ax1.set_title(f"Original Mesh (Normalized)\n({obj_name})", fontsize=12, weight='bold')
ax1.set_xlim(mid_x - max_range, mid_x + max_range)
ax1.set_ylim(mid_y - max_range, mid_y + max_range)
ax1.set_zlim(mid_z - max_range, mid_z + max_range)
ax1.axis('off')
ax1.view_init(elev=20, azim=45) # 设定固定视角
# 子图 2:VAE 解码重建的 Mesh
ax2 = fig.add_subplot(1, 2, 2, projection='3d')
ax2.plot_trisurf(mesh_recon.vertices[:, 0], mesh_recon.vertices[:, 1], mesh_recon.vertices[:, 2],
triangles=mesh_recon.faces, cmap='viridis', edgecolor='none', alpha=0.7)
# 在标题上动态附带上 F-Score 信息,方便肉眼和指标联动对照
ax2.set_title(f"Reconstructed Mesh\n(F-Score: {f_score * 100:.2f}%)", fontsize=12, weight='bold')
ax2.set_xlim(mid_x - max_range, mid_x + max_range)
ax2.set_ylim(mid_y - max_range, mid_y + max_range)
ax2.set_zlim(mid_z - max_range, mid_z + max_range)
ax2.axis('off')
ax2.view_init(elev=20, azim=45) # 设定相同视角
plt.tight_layout()
plt.savefig(output_image_path, dpi=150, bbox_inches='tight')
plt.close()
print(f"💾 对比可视化结果已成功保存至: {output_image_path}")