Shap-E Text-to-3D Generator による多視点画像からの3次元再構成デモ

Python開発環境,ライブラリ類

ここでは、最低限の事前準備について説明する。機械学習や深層学習を行う場合は、NVIDIA CUDA、Visual Studio、Cursorなどを追加でインストールすると便利である。これらについては別ページ https://www.kkaneko.jp/cc/dev/aiassist.htmlで詳しく解説しているので、必要に応じて参照してください。

Python 3.12 のインストール(Windows 上) [クリックして展開]

以下のいずれかの方法で Python 3.12 をインストールする。Python がインストール済みの場合、この手順は不要である。

方法1:winget によるインストール

管理者権限コマンドプロンプトで以下を実行する。管理者権限のコマンドプロンプトを起動するには、Windows キーまたはスタートメニューから「cmd」と入力し、表示された「コマンドプロンプト」を右クリックして「管理者として実行」を選択する。

winget install --scope machine --id Python.Python.3.12 -e --silent --disable-interactivity --force --accept-source-agreements --accept-package-agreements --override "/quiet InstallAllUsers=1 PrependPath=1 Include_pip=1 Include_test=0 Include_launcher=1 InstallLauncherAllUsers=1"

--scope machine を指定することで、システム全体(全ユーザー向け)にインストールされる。このオプションの実行には管理者権限が必要である。インストール完了後、コマンドプロンプトを再起動すると PATH が自動的に設定される。

方法2:インストーラーによるインストール

  1. Python 公式サイト(https://www.python.org/downloads/)にアクセスし、「Download Python 3.x.x」ボタンから Windows 用インストーラーをダウンロードする。
  2. ダウンロードしたインストーラーを実行する。
  3. 初期画面の下部に表示される「Add python.exe to PATH」に必ずチェックを入れてから「Customize installation」を選択する。このチェックを入れ忘れると、コマンドプロンプトから python コマンドを実行できない。
  4. 「Install Python 3.xx for all users」にチェックを入れ、「Install」をクリックする。

インストールの確認

コマンドプロンプトで以下を実行する。

python --version

バージョン番号(例:Python 3.12.x)が表示されればインストール成功である。「'python' は、内部コマンドまたは外部コマンドとして認識されていません。」と表示される場合は、インストールが正常に完了していない。

AIエディタ Windsurf のインストール(Windows 上) [クリックして展開]

Pythonプログラムの編集・実行には、AIエディタの利用を推奨する。ここでは、Windsurfのインストールを説明する。Windsurf がインストール済みの場合、この手順は不要である。

管理者権限コマンドプロンプトで以下を実行する。管理者権限のコマンドプロンプトを起動するには、Windows キーまたはスタートメニューから「cmd」と入力し、表示された「コマンドプロンプト」を右クリックして「管理者として実行」を選択する。

winget install --scope machine --id Codeium.Windsurf -e --silent --disable-interactivity --force --accept-source-agreements --accept-package-agreements --custom "/SP- /SUPPRESSMSGBOXES /NORESTART /CLOSEAPPLICATIONS /DIR=""C:\Program Files\Windsurf"" /MERGETASKS=!runcode,addtopath,associatewithfiles,!desktopicon"
powershell -Command "$env:Path=[System.Environment]::GetEnvironmentVariable('Path','Machine')+';'+[System.Environment]::GetEnvironmentVariable('Path','User'); windsurf --install-extension MS-CEINTL.vscode-language-pack-ja --force; windsurf --install-extension ms-python.python --force; windsurf --install-extension Codeium.windsurfPyright --force"

--scope machine を指定することで、システム全体(全ユーザー向け)にインストールされる。このオプションの実行には管理者権限が必要である。インストール完了後、コマンドプロンプトを再起動すると PATH が自動的に設定される。

関連する外部ページ

Windsurf の公式ページ: https://windsurf.com/

必要なライブラリをシステム領域にインストール

管理者権限コマンドプロンプトで以下を実行する。管理者権限のコマンドプロンプトを起動するには、Windows キーまたはスタートメニューから「cmd」と入力し、表示された「コマンドプロンプト」を右クリックして「管理者として実行」を選択する。

REM PyTorch をインストール(GPU対応版)
set "CUDA_TAG=cu126"
set "PYTHON_PATH=C:\Program Files\Python312"
"%PYTHON_PATH%\Scripts\pip" install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/%CUDA_TAG%
pip install git+https://github.com/openai/shap-e.git
pip install trimesh matplotlib ipywidgets

Shap-E Text-to-3D Generator

ソースコード

# プログラム名: Shap-E Text-to-3D Generator
# 特徴技術名: Shap-E (条件付き拡散モデル)
# 出典: Jun, H., & Nichol, A. (2023). Shap-E: Generating conditional 3D implicit functions. arXiv:2305.02463
# 特徴機能: 条件付き拡散モデルによる暗黙関数パラメータの生成。テキストプロンプトから3Dアセットを生成し,
#           NeRFとテクスチャメッシュの多表現出力に対応するフレームワークである
# 学習済みモデル: OpenAI公式Shap-E学習済みモデル(text300M: テキスト条件,transmitter: 3D表現変換)
#                  公式URL: https://github.com/openai/shap-e
# 方式設計(本実装の要点):
#   入力: テキストプロンプト(文字列)
#   出力: 3D形状メッシュ(PLY/OBJ/STL)を保存する(本実装では形状のみを対象とする)
#   処理手順:
#     1. テキストから潜在表現を生成
#     2. decode_latent_mesh(公式API)でメッシュ抽出
#     3. メッシュ品質処理(重複面・縮退面の除去,法線処理)と中心化(スケールは変更しない)
#     4. 形式別に保存および可視化
# 調整可能な設定値(本実装の既定値):
#   GUIDANCE_SCALE: 生成品質と多様性のバランス(推奨域に合わせ 5.0)
#   GENERATION_STEPS: Karrasサンプリングのステップ数(64)
# 前準備:
#   pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
#   pip install git+https://github.com/openai/shap-e.git
#   pip install trimesh matplotlib ipywidgets

import os
import numpy as np
import torch
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D  # 可視化に使用
import trimesh
from trimesh.repair import fix_normals
from shap_e.diffusion.gaussian_diffusion import diffusion_from_config
from shap_e.models.download import load_model, load_config
from shap_e.diffusion.sample import sample_latents
from shap_e.util.notebooks import decode_latent_mesh
import warnings
warnings.filterwarnings("ignore", message="exception rendering with PyTorch3D")
warnings.filterwarnings("ignore", message="falling back on native PyTorch renderer")

# 調整可能な設定値
GUIDANCE_SCALE = 5.0           # 推奨域に合わせる
GENERATION_STEPS = 64          # Karrasステップ数
SEED = 42                      # 乱数シード(再現性)
OUTPUT_DIR = "output"          # 出力ディレクトリ
VISUALIZATION_SIZE = (12, 8)   # 可視化サイズ(インチ)

# 出力ディレクトリ作成
os.makedirs(OUTPUT_DIR, exist_ok=True)

# 乱数シードの設定
torch.manual_seed(SEED)
np.random.seed(SEED)

# ガイダンス表示
print("=== Shap-E Text-to-3D Generator ===")
print("概要: テキストプロンプトから3Dモデルを生成します")
print("操作方法: プロンプトを入力後、Enterキーを押してください")
print("注意事項: 初回実行時はモデルのダウンロードに時間がかかります")
print("=" * 40)

def load_shap_e_models():
    """Shap-Eモデルの読み込みと実行デバイスの決定を行う"""
    print("Shap-Eモデルを読み込んでいます...")

    # GPU/CPU自動選択
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    print(f'デバイス: {str(device)}')
    # GPU使用時の最適化
    if device.type == 'cuda':
        torch.backends.cudnn.benchmark = True

    # Shap-Eモデルと拡散設定の読み込み
    xm = load_model('transmitter', device=device)
    model = load_model('text300M', device=device)
    diffusion = diffusion_from_config(load_config('diffusion'))

    return xm, model, diffusion, device

def generate_3d_from_text(prompt, model, diffusion, device):
    """テキストから潜在表現を生成する"""
    print(f"テキストプロンプト: {prompt}")
    print("3D形状を生成しています...")

    # デバイスに応じたFP16/FP32選択
    use_fp16 = (device.type == 'cuda')

    # 潜在表現の生成(Karrasサンプリングを使用)
    latents = sample_latents(
        batch_size=1,
        model=model,
        diffusion=diffusion,
        guidance_scale=GUIDANCE_SCALE,
        model_kwargs=dict(texts=[prompt]),
        progress=True,
        clip_denoised=True,
        use_fp16=use_fp16,
        use_karras=True,
        karras_steps=GENERATION_STEPS,
    )

    return latents

def extract_mesh_from_latent(latent, xm):
    """潜在表現からメッシュを抽出する(公式API: decode_latent_mesh を使用)"""
    # 公式経路でメッシュ抽出
    mesh_obj = decode_latent_mesh(xm, latent)
    tri_mesh = mesh_obj.tri_mesh()

    # 頂点・面の取得(Tensor/Numpy両対応)
    if hasattr(tri_mesh.verts, 'cpu'):
        vertices = tri_mesh.verts.cpu().numpy().astype(np.float32)
    else:
        vertices = np.array(tri_mesh.verts, dtype=np.float32)
    if hasattr(tri_mesh.faces, 'cpu'):
        faces = tri_mesh.faces.cpu().numpy().astype(np.int32)
    else:
        faces = np.array(tri_mesh.faces, dtype=np.int32)

    # Trimeshオブジェクトを作成
    mesh = trimesh.Trimesh(vertices=vertices, faces=faces)

    # メッシュの品質処理(重複面・縮退面の除去,法線処理)
    print("メッシュの品質向上処理を実行しています...")
    mesh.remove_duplicate_faces()
    mesh.remove_degenerate_faces()
    fix_normals(mesh)  # 公式API(trimesh.repair)で法線処理

    # 中心化(スケールは変更しない)
    mesh.vertices -= mesh.vertices.mean(axis=0)

    print("Shap-Eによる3D形状生成が完了しました(形状のみ、色情報なし)")
    return mesh

def save_3d_mesh(mesh, filename_base):
    """3Dメッシュを複数形式(PLY/OBJ/STL)で保存する"""
    print("3Dメッシュを保存しています...")

    # PLY形式で保存
    ply_path = os.path.join(OUTPUT_DIR, f"{filename_base}.ply")
    mesh.export(ply_path)
    print(f"PLYファイルを保存しました: {ply_path}")

    # OBJ形式で保存
    obj_path = os.path.join(OUTPUT_DIR, f"{filename_base}.obj")
    mesh.export(obj_path)
    print(f"OBJファイルを保存しました: {obj_path}")

    # STL形式で保存
    stl_path = os.path.join(OUTPUT_DIR, f"{filename_base}.stl")
    mesh.export(stl_path)
    print(f"STLファイルを保存しました: {stl_path}")

def visualize_3d_mesh(mesh, title="Generated 3D Model"):
    """3Dメッシュを可視化する"""
    print("3Dモデルを可視化しています...")

    vertices = mesh.vertices
    faces = mesh.faces

    # 頂点数のチェック(0の場合は可視化を中止)
    if len(vertices) == 0:
        print("警告: 頂点が存在しません。可視化をスキップします。")
        return

    # 3Dプロット
    fig = plt.figure(figsize=VISUALIZATION_SIZE)
    ax = fig.add_subplot(111, projection='3d')

    # メッシュの可視化
    ax.plot_trisurf(vertices[:, 0], vertices[:, 1], vertices[:, 2],
                    triangles=faces, alpha=0.9, cmap='viridis')

    # 軸ラベルとタイトル
    ax.set_xlabel('X')
    ax.set_ylabel('Y')
    ax.set_zlabel('Z')
    ax.set_title(f'{title}\nVertices: {len(vertices)}, Faces: {len(faces)}')

    # 軸範囲の調整
    x_range = vertices[:, 0].max() - vertices[:, 0].min()
    y_range = vertices[:, 1].max() - vertices[:, 1].min()
    z_range = vertices[:, 2].max() - vertices[:, 2].min()

    # 範囲が0の場合のデフォルト値設定
    max_range = max(x_range, y_range, z_range) / 2.0
    if max_range == 0:
        max_range = 1.0

    mid_x = (vertices[:, 0].max() + vertices[:, 0].min()) * 0.5
    mid_y = (vertices[:, 1].max() + vertices[:, 1].min()) * 0.5
    mid_z = (vertices[:, 2].max() + vertices[:, 2].min()) * 0.5

    ax.set_xlim(mid_x - max_range, mid_x + max_range)
    ax.set_ylim(mid_y - max_range, mid_y + max_range)
    ax.set_zlim(mid_z - max_range, mid_z + max_range)

    # 範囲の表示
    print(f"X軸範囲: {x_range:.3f}")
    print(f"Y軸範囲: {y_range:.3f}")
    print(f"Z軸範囲: {z_range:.3f}")

    if z_range < 0.01:
        print("警告: Z軸の範囲が小さいです。モデルが平面的になっている可能性があります。")

    plt.tight_layout()
    plt.show()

# モデル読み込み
xm, model, diffusion, device = load_shap_e_models()

# ユーザー入力
prompt = input("3D生成用のテキストプロンプトを入力してください: ")

# 3D形状生成
latents = generate_3d_from_text(prompt, model, diffusion, device)

# メッシュ抽出(公式API経路に一本化)
mesh = extract_mesh_from_latent(latents[0], xm)

# ファイル名ベースの作成
filename_base = prompt.replace(' ', '_').replace('/', '_').replace('\\', '_')[:50]

# 3D出力の保存
save_3d_mesh(mesh, filename_base)

# 可視化
visualize_3d_mesh(mesh, f"Generated: {prompt}")

print("\n処理が完了しました。")
print("生成された3Dモデルは以下の形式で保存されています:")
print("- PLYファイル: 3D編集ソフトで使用可能")
print("- OBJファイル: 汎用3D形式")
print("- STLファイル: 3Dプリンタ用")

# 推奨プロンプト
print("\n3D形状生成のヒント:")
print("- 単一のオブジェクトを指定(例: 'a chair', 'a vase')")
print("- 立体的な形状を明示(例: '3D model of a tree', 'volumetric sphere')")
print("- シンプルで明確な記述を使用")