ChatGPTの絵から3Dモデルの部品を大量生成する。

## はじめに
ChatGPTの画像生成が強化されて、任意の空間の絵を生成するだけでなく、その中にあるオブジェクトの絵をバラバラにして出力できるようになったので、それを使ってローカルimage to meshを行えば、原理的には好きなだけ3Dモデルを生成できますよね、という話です。

もちろん、今までも「赤いリンゴの絵」とか指示して画像を作ってimage to meshを行うことができたんですが、絵を構成する部品を抜き出せるようになって、色、スタイルなどが一貫した3Dモデルを大量に作れるようになるのが嬉しいです。

下の画像の中にあるオブジェクトは、全てChatGPTが生成した1枚の絵から作成した3Dモデルです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/410a920c-25a6-4609-acc8-7104cd7a1843.png)

これの作り方を解説します。

## ChatGPTでの作業

こちらはシンプルです。ChatGPTで　

```
画像を作成する ヨーロッパスタイルのカフェの内部、 
イラスト風、アイソメトリック プロジェクション
```

というようなプロンプトを与えて、画像を作ります。ここで重要なのが"**アイソメトリック プロジェクション**"というキーワードです。これを入れることで、なるべく遠近のない画像にします。ちなみに、オブジェクトが多すぎるのも後の作業が大変なので気をつけましょう。

以下の作業では、ChatGPTで実際に生成したこちらの画像を使います。

<img width="400" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/3598aafd-d11b-42fd-a493-c3a555cb1ea0.png">


この画像に対して、さらにChatGPTで

```
この絵の中にある家具や電化製品などを3Dモデルとして再現したいです。
それぞれを分けて、1枚のシートの上に並べた状態で表示してください。
各パーツは3Dモデリングの参考になるように、形状が分かりやすいようにしてください。
テクスチャは元の絵を反映させ、背景色は白にしてください。
```
という指示を出します。すると、以下のように絵の中の「部品」をバラバラにして、一枚の絵にまとめてくれます。

<img width="600" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/5664b4b6-263d-44ae-821e-f2324901d30b.png">

部品同士が重なってしまうなら、「お互いが重ならないよう、アイテムとアイテムの間には空白を確保してください。」など、プロンプトを追加しましょう。大きいアイテムがあると重なりやすいので、それは別に出力しても良いでしょう。

本当なら、ここで部品ごとの画像にしてくれれば良いのですが、ChatGPTは一度に1枚しか画像を出せないので、部品全部を一枚にまとめた画像を作り、次の作業でバラバラの画像にします。


## 部品ごとの画像を作る

私が用意した、[パーツ抽出ツールのサイト](https://v0-parts-extraction.vercel.app/)に行ってください。

<img width="600" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/04ffc3b0-e00c-40cc-b4ff-520d811ad330.png">


ここで、先ほどの画像をアップロードすると、自動で部品ごとに分割した画像にしてくれます。右の「ZIPでダウンロード」ボタンでまとめて画像をダウンロードしましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/1adbd72d-8c17-48e9-8b80-e46d4c7dadd3.png)

「背景オプション」は、Image to Meshのツールによって好む背景色が微妙に異なるので用意しました。大抵は、透明か白で良いはずですが、うまくいかないならカスタム背景色を選択し、＋（カラーピッカー）で色を選択してください。

## pinokioを用いた3Dモデル生成ライブラリのインストール

次に、画像から3Dモデルを生成、つまりImage to Meshにより部品の画像を3Dモデルにします。Meshyなどのクラウドサービスを使っても良いんですが、数が多いとコスト的に厳しいですよね。

そこでローカルImage to Meshライブラリを使います。いくつかありますが、そのなかで強力なものの一つが[Hunyuan3D-2](https://github.com/Tencent/Hunyuan3D-2)です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/6bc96eb2-7fb5-4f8f-b77b-8923c92bb2da.png)

ただ、これ、インストールが結構面倒で使いづらいです。

そこで、[pinokio](https://pinokio.computer/)というツールを使います。このツールは、ライブラリのインストールを自動的に行い、hugging faceでみるようなGUIをローカルで使いながらモデルを走らせることができます。今回はこれを使って省力化します。なお、実行条件の詳細は不明ですが、[こちらの記事によると](https://forest.watch.impress.co.jp/docs/serial/blenderwthing/1662339.html)、以下のような実行環境が必要なようです。

- VRAMが8GB以上のNVIDIA GPU
- GPUドライババージョンが550以降
- システムメモリが24GB以上

ディスクスペースも50GBくらいは必要と思います。

さて、環境が大丈夫そうなら、まず、pinokioのサイトからwindows版のアプリをダウンロードしてください。インストーラーを実行すると以下のような警告が出ますが、「詳細情報」をクリックすると「実行」ボタンが出てきて先に進めます。

<img width="400" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/3235b3e0-a7c9-45d5-86d4-52720fc952da.png">


設定画面が出てきたら、とりあえずデフォルトのままでOKすると、スタート画面になるので、Visit Discover Pageに進みます

<img width="600" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/c37de3e4-2fdd-454c-b7b9-253ee6e101db.png">

ここで利用可能なモデルが出てくるのですが、Hunyuan3D-2がみあたらないです。

<img width="500" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/afd40f72-d90d-47b4-9678-c3ca95b47939.png">

そこで、"Download from URL"を選び、```https://github.com/pinokiofactory/Hunyuan3D-2.git```をgit URLとして入力してください。


<img width="500" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/40db9efb-714b-4919-acfb-2c24a7ea6afe.png">

One-Click Install with Pinokioボタンを押します。インストールすべきパッケージなど一覧が出きたら、installボタンを押して、インストールを開始してください。コンソールの画面が出てきてインストールが進行します。その後も、画面の指示に従ってください。

<img width="500" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/c036dd8b-11f3-401f-91b9-40ac3e5ee2d2.png">


その様子を見ているとわかるのですが、仮想環境envに必要なものをインストールしています。これが後で重要になるので、覚えておいてください。

すべて終わると、下のような画面になります。

<img width="500" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/8fe26bd3-6f73-41e6-9a02-3d4507d70126.png">

## 画像から3Dモデル生成のバッチ処理

さて、これで、必要なものはすべてインストールされたはずです。デフォルト設定なら、```C:\pinokio\api\Hunyuan3D-2.git\app```に、ファイルが構成されているはずです。コマンドプロンプトを開いて、そのディレクトリへ移動してください。以下のようなファイルがあるはずです。

<img width="500" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/d967d7da-fd2f-4903-97a6-cbe0656e49c1.png">


ここで、まず、仮想環境を立ち上げます。
```
.\env\Scripts\activate
```

つぎに、3D生成を行うローカルサーバーを、以下のコマンドで起動します。たくさんいろいろダウンロードするので、使えるようになるには時間かかります。
```
python api_server.py --enable_tex --host 0.0.0.0 --port 8080
```
なお、--enable_texオプションを入れることで、3Dメッシュ生成とテクスチャ生成が行われます。入れないとメッシュだけになります。

無事ダウンロードが終わってサーバーが準備できたら、以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/fedbfbd4-ef79-472c-9d73-cae999fef787.png)

そうしたら、画像を与えて3Dモデル生成をリクエストするスクリプトを用意します。

```generate.py
import base64
import requests
import json

def send_image_to_api():
    # 画像ファイルをbase64エンコード
    with open('assets/demo.png', 'rb') as image_file:
        img_b64_str = base64.b64encode(image_file.read()).decode('utf-8')

    # APIエンドポイントとリクエストデータの設定
    url = "http://localhost:8080/generate"
    headers = {
        "Content-Type": "application/json"
    }
    payload = {
        "image": img_b64_str,
        "texture": True
    }

    # POSTリクエストの送信
    response = requests.post(url, headers=headers, json=payload)

    # レスポンスの保存
    if response.status_code == 200:
        with open('test1.glb', 'wb') as f:
            f.write(response.content)
        print("GLBファイルが正常に保存されました。")
    else:
        print(f"エラーが発生しました。ステータスコード: {response.status_code}")

if __name__ == "__main__":
    send_image_to_api()
```
これを```C:\pinokio\api\Hunyuan3D-2.git\app```に保存します。そして、先ほどのサーバー用のものとは別にコマンドプロンプトを開き、appフォルダに移動して```python generate.py```で実行してください（こちらは仮想環境不要です）。

うまくいけば、テクスチャ付きの3Dモデルが生成されるはずです。サーバー側は

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/48846002-ca4a-4bb2-ad24-023b07c1345c.png)

のようになっているはずです。生成されたglbファイルは```generate.py```と同じところにあります。

<img width="400" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/7e7f4c0b-0c3a-4196-aa85-0e4cefc5c3a3.png">

あとは、流れ作業です。以下のような、スクリプトを作り、```assets/myimages```に上で用意した部品画像をまとめて入れて、3Dモデルにしましょう

```generate_myimages.py
import base64
import requests
import json
import os
import time

def send_image_to_api(image_path, output_path, retries=3, timeout=600):
    print(f"\n--- 処理開始: {image_path} ---")
    start_time = time.time()

    with open(image_path, 'rb') as image_file:
        img_b64_str = base64.b64encode(image_file.read()).decode('utf-8')

    url = "http://localhost:8080/generate"
    headers = {"Content-Type": "application/json"}
    payload = {
        "image": img_b64_str,
        "texture": True
    }

    for attempt in range(1, retries + 1):
        try:
            print(f"  → API送信中 (試行 {attempt}/{retries})...")
            response = requests.post(url, headers=headers, json=payload, timeout=timeout)

            if response.status_code == 200:
                with open(output_path, 'wb') as f:
                    f.write(response.content)
                duration = time.time() - start_time
                minutes = int(duration // 60)
                seconds = int(duration % 60)
                print(f"  ✅ 成功: {output_path} に保存（処理時間: {minutes}分 {seconds}秒）")
                return
            else:
                print(f"  ❌ ステータスコード: {response.status_code}")

        except requests.exceptions.Timeout:
            print("  ⚠️ タイムアウトが発生しました。")
        except Exception as e:
            print(f"  ⚠️ エラー発生: {e}")

        time.sleep(5)  # リトライ間隔

    duration = time.time() - start_time
    minutes = int(duration // 60)
    seconds = int(duration % 60)
    print(f"  ❌ 最終的に失敗: {image_path}（処理時間: {minutes}分 {seconds}秒）")

def process_all_images():
    input_dir = 'assets/myimages'
    output_dir = 'assets/output_glb'
    os.makedirs(output_dir, exist_ok=True)

    image_files = sorted([
        f for f in os.listdir(input_dir)
        if f.lower().endswith(('.png', '.jpg', '.jpeg'))
    ])

    total = len(image_files)
    print(f"=== 全{total}枚の画像を処理します ===")

    for idx, filename in enumerate(image_files, 1):
        image_path = os.path.join(input_dir, filename)
        base_name = os.path.splitext(filename)[0]
        output_path = os.path.join(output_dir, f'{base_name}.glb')
        print(f"\n📄 [{idx}/{total}] {filename} を処理中...")
        send_image_to_api(image_path, output_path)

    print("\n=== 全処理が完了しました ===")

if __name__ == "__main__":
    process_all_images()

```


全処理が完了したら、```app/assets/output_glb```を見てください。glbモデルが出来ているはずです。

<img width="500" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58503/02cf0895-0698-481f-bce7-4f4240b62fe1.png">

完成したモデルの画像は、この記事のトップにあります。概ね良好な生成でしたが、植物の葉などが一部欠けてますね。この辺は、モデルの改善を期待しましょう。

完成したモデルは、ブラウザやVRヘッドセットで出来栄えを確認しましょう。簡単なツールを作ったので、以下の記事からアクセスしてください。

[3Dモデルをブラウザに放り込んで、スマホやVision Proでサクッと見る話](https://qiita.com/yanosen_jp/items/573538278b9273b98785)

## おわりに

今回は1枚の絵から12個のオブジェクトを作りましたが、その絵から別のシチュエーションの絵をChatGPTに作らせて手順を繰り返せば、同じスタイルの3Dオブジェクトを大量に作ることができます。いままで、3Dモデルを購入してバーチャル空間を作ろうとすると、ほしいモデルが足りなかったり、スタイルがバラバラでうまくいかないことが多かったんですが、今回の方法を用いれば簡単に一貫したスタイルの3Dモデルを作れるでしょう。

この方法で、いろいろなバーチャル空間を作っていきたいです。