MENU
アーカイブ

HunyuanWorld-Mirrorで3D Gaussian Splattingデータを生成してplyファイルとして保存するやり方を紹介します

HunyuanWorld-Mirrorを実行できるComfyUIのカスタムノードが無かったので、ChatGPTに聞きながらColab上でプログラム書いて実行してみました。

生成されるデータの中に3D Gaussian Splattingデータがあったのでplyファイルとして保存してWebのViewerで表示させてみたのでそのやり方を紹介します。

目次

HunyuanWorld-Mirrorとは

TencentのHunyuanチームがオープンソースの世界モデル「Hunyuan World Models」シリーズの新モデルとして発表しました。

あらゆる入力、最大限の柔軟性と忠実度:多様な幾何学的事前情報(カメラ姿勢、固有パラメータ、深度マップ)を柔軟に統合し、構造的曖昧性を解消するとともに、幾何学的に一貫した3D出力を保証します。

Colabでの実行

まずGithubからクローンします。

スクロールできます
!git clone https://github.com/Tencent-Hunyuan/HunyuanWorld-Mirror.git
%cd HunyuanWorld-Mirror

続いて、必要なモジュールをインストールします。

スクロールできます
!pip install torch torchvision  # ColabのGPUに合わせてバージョンを調整
!pip install lightning pytorch-lightning lightning-utilities
!pip install ninja
!pip install gsplat
!pip install -r requirements.txt
!pip install open3d

次から実際のPythonのプログラム部分です。

スクロールできます
import torch
import open3d as o3d
import numpy as np
from pathlib import Path

from src.models.models.worldmirror import WorldMirror
from src.utils.inference_utils import extract_load_and_preprocess_images

device = “cuda” if torch.cuda.is_available() else “cpu”

モデルをHugging Faceからダウンロードします。

URL:https://huggingface.co/tencent/HunyuanWorld-Mirror/tree/main

checkpointsフォルダを作成してそこにダウンロードしたmodel.safetensorsを入れておきます。

スクロールできます
# モデルロード
model = WorldMirror.from_pretrained(“tencent/HunyuanWorld-Mirror”, cache_dir=”./checkpoints”).to(device)
model.eval()

入力用のフォルダを作成してそこに画像を入れておきます。

スクロールできます
# 入力画像前処理
img_dir = Path(“./images”)   # input.jpg を置いた場所
inputs = {}
imgs = extract_load_and_preprocess_images(
    img_dir, fps=1, target_size=1064
).to(device)

以下のプログラムで推論を実行して出力データが取得できます。

スクロールできます
views = {
    “img”: imgs,
}

# 推論
with torch.no_grad():
    outputs = model(views, cond_flags=[0], is_inference=True)

print(outputs.keys())

出力データには色んなものが入っていて、キーの一覧をプリントすると以下のようなものがあります。

スクロールできます
dict_keys([‘camera_params’, ‘camera_poses’, ‘camera_intrs’, ‘depth’, ‘depth_conf’, ‘pts3d’, ‘pts3d_conf’, ‘normals’, ‘normals_conf’, ‘gs_depth’, ‘gs_depth_conf’, ‘splats’])

それぞれのキーの意味としては以下になります。

カメラ系

key内容用途
camera_paramsカメラの内部+外部パラメータ再投影・レンダリング
camera_intrs内部パラメータ(K行列)深度→3D変換
camera_poses外部パラメータ(姿勢)カメラ位置・向き

深度・点群

key内容用途
depth深度マップ(H×W)距離情報
depth_conf深度の信頼度ノイズ除去
pts3d各ピクセルの3D座標(H×W×3)点群生成
pts3d_conf各点の信頼度点群フィルタ

法線

key内容用途
normals各点の法線ベクトルメッシュ化・ライティング
normals_conf法線の信頼度ノイズ除去

Gaussian Splatting

key内容用途
gs_depthGaussian Splatting用深度Splat最適化
gs_depth_confGaussian Splatting用深度の信頼度フィルタ
splatsGaussian Splatting用パラメータ式3DGS / Web表示

点群データをplyファイルとして保存する処理が以下になります。

スクロールできます
# — データ取り出し —
pts3d = outputs[“pts3d”][0]          # (S, H, W, 3) or (H, W, 3)
conf  = outputs[“pts3d_conf”][0]     # (S, H, W) or (H, W)
images = views[“img”][0]             # (S, 3, H, W)

# 次元整理
if pts3d.ndim == 4:   # (S,H,W,3) → 1枚目だけ使う
    pts3d = pts3d[0]
    conf = conf[0]
    images = images[0]

# numpy化
xyz = pts3d.cpu().numpy().reshape(-1, 3)
conf = conf.cpu().numpy().reshape(-1)
rgb = images.permute(1, 2, 0).cpu().numpy().reshape(-1, 3)

# 0–255へ
rgb = (rgb * 255).clip(0, 255).astype(np.uint8)

# — フィルタ条件 —
conf_th = 0.6   # ← 0.4〜0.8くらいで調整OK

mask = (
    np.isfinite(xyz).all(axis=1) &
    (conf > conf_th)
)

xyz_f = xyz[mask]
rgb_f = rgb[mask]

print(f”Points before: {len(xyz)}, after filtering: {len(xyz_f)}”)

# — PLY保存 —
ply_path = “/content/drive/MyDrive/HunyuanWorld-Mirror/output/colored_pointcloud_conf_filtered_00001.ply”
with open(ply_path, “w”) as f:
    f.write(“ply\nformat ascii 1.0\n”)
    f.write(f”element vertex {len(xyz_f)}\n”)
    f.write(“property float x\nproperty float y\nproperty float z\n”)
    f.write(“property uchar red\nproperty uchar green\nproperty uchar blue\n”)
    f.write(“end_header\n”)
    for p, c in zip(xyz_f, rgb_f):
        f.write(f”{p[0]} {p[1]} {p[2]} {c[0]} {c[1]} {c[2]}\n”)

print(“Saved:”, ply_path)

Gaussian Splattingデータをplyファイルで保存する処理になります。

スクロールできます
import struct

splats = outputs[“splats”]   # ← ここが修正点

# 取り出し
means   = torch.cat(splats[“means”], dim=0).cpu().numpy()      # (N,3)
scales  = torch.cat(splats[“scales”], dim=0).cpu().numpy()    # (N,3)
quats   = torch.cat(splats[“quats”], dim=0).cpu().numpy()     # (N,4)
sh      = torch.cat(splats[“sh”], dim=0).cpu().numpy()        # (N,1,3)
opacity = torch.cat(splats[“opacities”], dim=0).cpu().numpy() # (N,1)

rgb = sh[:, 0, :]          # (N,3)

# HunyuanWorld → GS Viewer 変換
scales = np.log(scales + 1e-8)
opacity = np.log(opacity / (1 – opacity + 1e-8) + 1e-8)

N = means.shape[0]
print(“Num Gaussians:”, N)

# — PLY形式(3DGS互換) —
gs_path = “/content/drive/MyDrive/HunyuanWorld-Mirror/output/gaussian_splats_binary_00026.ply”
with open(gs_path, “wb”) as f:
    header = “ply\n”
    header += “format binary_little_endian 1.0\n”
    header += f”element vertex {N}\n”
    header += “property float x\n”
    header += “property float y\n”
    header += “property float z\n”
    header += “property float f_dc_0\n”
    header += “property float f_dc_1\n”
    header += “property float f_dc_2\n”
    # SH高次が無いのでダミー0で埋める
    for i in range(0, 45):
        header += f”property float f_rest_{i}\n”
    header += “property float opacity\n”
    header += “property float scale_0\n”
    header += “property float scale_1\n”
    header += “property float scale_2\n”
    header += “property float rot_0\n”
    header += “property float rot_1\n”
    header += “property float rot_2\n”
    header += “property float rot_3\n”
    header += “end_header\n”

    f.write(header.encode(“utf-8”))   # ← ヘッダは文字列→bytes

    for i in range(N):
        x,y,z = means[i]
        r,g,b = rgb[i]
        o     = float(opacity[i])
        sx,sy,sz = scales[i]
        q0,q1,q2,q3 = quats[i]

        f.write(struct.pack(
            “<3f 3f 45f 1f 3f 4f”,
            x, y, z,
            r, g, b,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            o,
            sx, sy, sz,
            q0, q1, q2, 3,
        ))

print(“Saved Gaussian Splat PLY:”, gs_path)

plyファイルはフォーマットに自由度がありすぎて、普通の点群データもGaussian Splattingデータも両方扱えてしまいます。

さらにGaussian SplattingデータをWebのViewer等で表示しようと思ったら、Viewer側が期待する形式で保存する必要があります。

プロパティ名意味
x y z位置
scale_0 scale_1 scale_2ガウスサイズ
quat_0 quat_1 quat_2 quat_3回転(Quaternion)
opacity不透明度
f_dc_0 f_dc_1 f_dc_2色(SH 0次)
f_rest_*SH 高次係数

以下のようにplyファイルの中身を確認できます。

スクロールできます
gs_path = “/content/drive/MyDrive/HunyuanWorld-Mirror/output/gaussian_splats_binary_00026.ply”
with open(gs_path, “rb”) as f:
    print(f.read(1600))

ちゃんと作成できていれば以下のようになっているはずです。

スクロールできます
b’ply\nformat binary_little_endian 1.0\nelement vertex 429667\nproperty float x\nproperty float y\nproperty float z\nproperty float f_dc_0\nproperty float f_dc_1\nproperty float f_dc_2\nproperty float f_rest_0\nproperty float f_rest_1\nproperty float f_rest_2\nproperty float f_rest_3\nproperty float f_rest_4\nproperty float f_rest_5\nproperty float f_rest_6\nproperty float f_rest_7\nproperty float f_rest_8\nproperty float f_rest_9\nproperty float f_rest_10\nproperty float f_rest_11\nproperty float f_rest_12\nproperty float f_rest_13\nproperty float f_rest_14\nproperty float f_rest_15\nproperty float f_rest_16\nproperty float f_rest_17\nproperty float f_rest_18\nproperty float f_rest_19\nproperty float f_rest_20\nproperty float f_rest_21\nproperty float f_rest_22\nproperty float f_rest_23\nproperty float f_rest_24\nproperty float f_rest_25\nproperty float f_rest_26\nproperty float f_rest_27\nproperty float f_rest_28\nproperty float f_rest_29\nproperty float f_rest_30\nproperty float f_rest_31\nproperty float f_rest_32\nproperty float f_rest_33\nproperty float f_rest_34\nproperty float f_rest_35\nproperty float f_rest_36\nproperty float f_rest_37\nproperty float f_rest_38\nproperty float f_rest_39\nproperty float f_rest_40\nproperty float f_rest_41\nproperty float f_rest_42\nproperty float f_rest_43\nproperty float f_rest_44\nproperty float opacity\nproperty float scale_0\nproperty float scale_1\nproperty float scale_2\nproperty float rot_0\nproperty float rot_1\nproperty float rot_2\nproperty float rot_3\nend_header\n\xfb\x1cC\xbf\x066\xb6\xbeu\xad\x03@\xa4\x98\xf9>\x99\xa4W?\x03\xf1\xb7?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00′

plyfileというモジュールを使うと、データの読み書きを簡単にできたります。

スクロールできます
!pip install plyfile

以下のように読みだして、データの中身のそれぞれの値の最小値や最大値を確認して、WebのViewerの期待する形になっているか見ることが出来ます。

スクロールできます
from plyfile import PlyData
import numpy as np

# file_path = “/content/drive/MyDrive/HunyuanWorld-Mirror/output/cactus_splat3_30kSteps_142k_splats.ply”
file_path = “/content/drive/MyDrive/HunyuanWorld-Mirror/output/gaussian_splats_binary_00020.ply”

# PLYファイルを読み込み
plydata = PlyData.read(file_path)

# vertex要素を取得
vertex = plydata[‘vertex’]

# データをNumPy配列として取得
# XYZ位置情報
xyz = np.stack([vertex[‘x’], vertex[‘y’], vertex[‘z’]], axis=1)

# 色情報 (f_dc_0, f_dc_1, f_dc_2 が直流成分、球面調和関数)
# f_rest_0 ~ f_rest_44 が高周波成分
features_dc = np.stack([vertex[‘f_dc_0’], vertex[‘f_dc_1’], vertex[‘f_dc_2’]], axis=1)

# 透明度 (opacity)
opacity = vertex[‘opacity’]

# ガウシアンのスケール (scale_0, scale_1, scale_2)
scale = np.stack([vertex[‘scale_0’], vertex[‘scale_1’], vertex[‘scale_2’]], axis=1)

# ガウシアンの回転 (rot_0, rot_1, rot_2, rot_3) – クォータニオン
rotation = np.stack([vertex[‘rot_0’], vertex[‘rot_1’], vertex[‘rot_2’], vertex[‘rot_3’]], axis=1)

f_rest = np.stack([vertex[‘f_rest_0’], vertex[‘f_rest_1’], vertex[‘f_rest_2’], vertex[‘f_rest_3’]], axis=1)

print(“xyz min/max =”, xyz.min(), xyz.max())
print(“scales min/max =”, scale.min(), scale.max())
print(“opacity min/max =”, opacity.min(), opacity.max())
print(“f_dc min/max =”, features_dc.min(), features_dc.max())
print(“rotation min/max =”, rotation.min(), rotation.max())
print(“f_rest min/max =”, f_rest.min(), f_rest.max())

まとめ

Colab上でPythonプログラムを書いてHunyuanWorld-Mirrorを実行してWebのViewerの表示できるフォーマットのplyファイルに保存して表示することができました。

3Dモデルに関連した他の記事

生成した3Dモデルを3Dプリンタで出力するなら…

Trellisで別視点の複数枚画像から生成する方法

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次