DALL-E3で親子モンスターの生成をしてみる

こんにちは！

以前から、キャラクターの生成を自動化したく、画像生成AIをうまく活用できないかと考えてました。

考えてるうちにどんどん発展していったので、まずは一度やってみることにしました:slight_smile:

# やりたいこと
- パラメータをランダムに決定
- モンスターを自動生成
- 二体の親モンスターから子モンスターを生成

# 環境構築
Pythonの仮想環境を作ります。
```bash
python -m venv create-monster
cd create-monster
```
ディレクトリに移動した直後の状態
```bash
$ ls
bin/		include/	lib/		pyvenv.cfg
```
仮想環境の有効化
```bash
source bin/activate
```
必要なライブラリのインストール
```bash
pip install openai requests python-dotenv uuid requests
```
# openaiとは
OpenAIのライブラリです。
クライアントを作成して操作します。

# python-dotenvとは
環境変数を.envから読み込むためのライブラリです。

# ディレクトリ構造
最終的なディレクトリ構造はこちら
```bash
create-monster/
├── .env
├── bin/
├── create-child.py
├── create-parent.py
├── include/
├── lib/
└── pyvenv.cfg
```

# コード
まずは親モンスターの生成コード。
これを2回実行して、モンスターを二体作ります。
```Python:create-parent.py
import openai
import os
import random
import json
import uuid
import requests
from dotenv import load_dotenv

load_dotenv()

api_key = os.getenv("OPENAI_API_KEY")

if not api_key:
    raise ValueError("OPENAI_API_KEY環境変数が設定されていません。")

# OpenAIクライアントのインスタンスを作成
client = openai.OpenAI(api_key=api_key)

def generate_monster_gene():
    colors = ['red', 'blue', 'green', 'yellow', 'purple', 'black', 'white', 'pink', 'gold', 'silver']
    hat_colors = ['none'] * 98 + ['black', 'red']
    accessories_options = ['none'] * 20 + ['earrings']
    types = ['monster'] * 70 + ['dragon'] * 20 + ['devil'] * 9 + ['god']
    backgrounds = ['ruins'] * 50 + ['battlefield'] * 49 + ['moon']

    gene = {
        'body': random.choice(colors),
        'eye': random.choice(colors),
        'hat': random.choice(hat_colors),
        'accessories': random.choice(accessories_options),
        'type': random.choice(types),
        'background': random.choice(backgrounds),
    }
    return gene

def generate_visual_description(gene):
    """遺伝子情報からビジュアル記述を生成"""
    parts = [
        f"body: {gene['body']}" if gene['body'] != 'none' else "",
        f"eye: {gene['eye']}" if gene['eye'] != 'none' else "",
        f"hat: {gene['hat']}" if gene['hat'] != 'none' else "",
        f"type: {gene['type']}",
        f"accessories: {gene['accessories']}" if gene['accessories'] != 'none' else "",
    ]
    description_parts = ', '.join(part for part in parts if part)
    description = f"A full-body view of a cyber punks, {description_parts}, facing front. The background is {gene['background']}. The image should be photo-realistic."

    return description

def request_dall_e_image(description):
    """DALL-E 3 APIを使用して画像を生成し、保存する関数"""
    response = client.images.generate(
        prompt=description,
        n=1,  # 生成する画像の数
        size="512x512"  # 画像のサイズ
    )
    # 画像URLを取得
    image_url = response.data[0].url

    # 画像をダウンロードして保存
    image_response = requests.get(image_url)
    image_filename = f"image_{uuid.uuid4()}.png"
    with open(image_filename, 'wb') as file:
        file.write(image_response.content)
    
    return image_filename

# モンスター情報の生成
monster_gene = generate_monster_gene()

# ビジュアル記述の生成
description = generate_visual_description(monster_gene)

# DALL-E 3による画像生成
image_filename = request_dall_e_image(description)

# 情報と画像のファイル名を紐付けて保存
monster_data = {
    'gene': monster_gene,
    'image_filename': image_filename
}
uuid_key = str(uuid.uuid4())
with open(f"{uuid_key}.json", 'w') as file:
    json.dump(monster_data, file)

print(f"モンスターの情報と画像ファイル名が保存されました：{uuid_key}.json")
```

次に、二体のモンスターから新しいモンスターを生成するコード。
```python:create-child.py
import openai
import os
import random
import json
import uuid
import requests
import sys
from dotenv import load_dotenv

load_dotenv()

api_key = os.getenv("OPENAI_API_KEY")

if not api_key:
    raise ValueError("OPENAI_API_KEY環境変数が設定されていません。")

# OpenAIクライアントのインスタンスを作成
client = openai.OpenAI(api_key=api_key)

def combine_genes(gene1, gene2):
    """二つのモンスターの情報を組み合わせる"""
    combined_gene = {
        'body': random.choice([gene1['body'], gene2['body']]),
        'eye': random.choice([gene1['eye'], gene2['eye']]),
        'hat': random.choice([gene1['hat'], gene2['hat']]),
        'accessories': random.choice([gene1['accessories'], gene2['accessories']]),
        'type': random.choice([gene1['type'], gene2['type']]),
        'background': random.choice([gene1['background'], gene2['background']]),
    }
    return combined_gene

def generate_child_monster(uuid1, uuid2):
    """二つのUUIDに基づいて親モンスターの情報を読み込み、子モンスターを生成する"""
    with open(f"{uuid1}.json", 'r') as file:
        parent1_data = json.load(file)

    with open(f"{uuid2}.json", 'r') as file:
        parent2_data = json.load(file)

    child_gene = combine_genes(parent1_data['gene'], parent2_data['gene'])
    return child_gene

def request_dall_e_image(description):
    """DALL-E 3 APIを使用して画像を生成し、保存する関数"""
    response = client.images.generate(
        prompt=description,
        n=1,  # 生成する画像の数
        size="512x512"  # 画像のサイズ
    )
    # 画像URLを取得
    image_url = response.data[0].url

    # 画像をダウンロードして保存
    image_response = requests.get(image_url)
    image_filename = f"image_{uuid.uuid4()}.png"
    with open(image_filename, 'wb') as file:
        file.write(image_response.content)
    
    return image_filename

def generate_visual_description(gene):
    """情報からビジュアル記述を生成"""
    parts = [
        f"body: {gene['body']}" if gene['body'] != 'none' else "",
        f"eye: {gene['eye']}" if gene['eye'] != 'none' else "",
        f"hat: {gene['hat']}" if gene['hat'] != 'none' else "",
        f"accessories: {gene['accessories']}" if gene['accessories'] != 'none' else "",
        f"type: {gene['type']}",
    ]
    description_parts = ', '.join(part for part in parts if part)
    description = f"A full-body view of a cyber punks, {description_parts}, facing front. The background is {gene['background']}. The image should be photo-realistic."
    return description

def main():
    if len(sys.argv) != 3:
        print("Usage: python script.py <uuid1> <uuid2>")
        sys.exit(1)

    uuid1 = sys.argv[1]
    uuid2 = sys.argv[2]

    # 子モンスター情報の生成
    child_monster_gene = generate_child_monster(uuid1, uuid2)

    # 子モンスターのビジュアル記述を生成し、画像を生成
    child_description = generate_visual_description(child_monster_gene)
    child_image_filename = request_dall_e_image(child_description)

    # 子モンスター情報と画像を保存
    child_monster_data = {
        'gene': child_monster_gene,
        'image_filename': child_image_filename
    }
    child_uuid = str(uuid.uuid4())
    with open(f"{child_uuid}.json", 'w') as file:
        json.dump(child_monster_data, file)

    print(f"子モンスターの情報と画像ファイル名が保存されました：{child_uuid}.json")

if __name__ == "__main__":
    main()
```

.envも忘れずに。
```text:.env
OPENAI_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
```

# 実行
それでは、実行してみましょう！
### 親モンスターの生成
以下のコマンドを実行します。
```bash
python create-parent.py
```
まずは一体目
```
$ python create-parent.py 
モンスターの情報と画像ファイル名が保存されました：0f4c17ba-ba04-4d7e-bdb7-8380c64cb165.json
```
生成されたJSONファイルの中身
```json
{"gene": {"body": "red", "eye": "blue", "hat": "none", "accessories": "none", "type": "dragon", "background": "battlefield"}, "image_filename": "image_54cc99a4-ec56-4c4d-a405-ac50eb47bb9c.png"}
```
生成されたモンスター画像
いい感じですね！
![image_54cc99a4-ec56-4c4d-a405-ac50eb47bb9c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407770/2a8f40d3-82cf-aab1-8438-b0c0fc5a9c60.png)

続いて二体目。
```bash
$ python create-parent.py 
モンスターの情報と画像ファイル名が保存されました：ba6a5483-93e8-4e45-aedf-19bfc046a1a9.json
```
生成されたJSONファイルの中身
```json
{"gene": {"body": "purple", "eye": "black", "hat": "none", "accessories": "none", "type": "dragon", "background": "ruins"}, "image_filename": "image_96637356-8d3c-4143-8d73-43ab20b03a5b.png"}
```

生成されたモンスター画像
こちらもいいですね。
![image_96637356-8d3c-4143-8d73-43ab20b03a5b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407770/38fbbce5-9e51-6d7b-ed7b-74c2348175d3.png)

### 子モンスターの生成
親モンスターのJSONファイル名になっているUUIDを二つ渡します。
```bash
$ python create-child.py 0f4c17ba-ba04-4d7e-bdb7-8380c64cb165 ba6a5483-93e8-4e45-aedf-19bfc046a1a9
子モンスターの情報と画像ファイル名が保存されました：92534141-4885-4215-8f45-cd57de147637.json
```
生成されたJSONファイルの中身
```json
{"gene": {"body": "purple", "eye": "black", "hat": "none", "accessories": "none", "type": "dragon", "background": "battlefield"}, "image_filename": "image_08b0f2b1-4a65-40d9-a2ea-2905831ad041.png"}
```
生成されたモンスター画像
雰囲気出てますね！
![image_08b0f2b1-4a65-40d9-a2ea-2905831ad041.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407770/29610bb9-3914-cc02-b92f-1d0f77243e34.png)

試しに、同じコマンドで再生成してみます。
```bash
$ python create-child.py 0f4c17ba-ba04-4d7e-bdb7-8380c64cb165 ba6a5483-93e8-4e45-aedf-19bfc046a1a9
子モンスターの情報と画像ファイル名が保存されました：88439ef4-b0ca-45c5-84f6-2dece87cde50.json
```
生成されたJSONファイルの中身
```json
{"gene": {"body": "purple", "eye": "blue", "hat": "none", "accessories": "none", "type": "dragon", "background": "battlefield"}, "image_filename": "image_f8d7a0ff-81f6-4833-9fa7-4806073b201f.png"}
```

生成されたモンスター画像
おー！個人的に好きです。
![image_f8d7a0ff-81f6-4833-9fa7-4806073b201f.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407770/c8719844-5204-0f05-bdb5-2fa3777b2b12.png)

# まとめ
DALL-E3を利用して、モンスターの自動生成と掛け合わせをしてみました。
掛け合わせと言っても、パラメータを引き継いでいるだけなので、厳密にはちょっと違うかもしれませんね。

今回はドラゴン同士の掛け合わせになったので、別のタイプが出るまでやってみようと思います。

色々とチャレンジすると楽しいですね！
みなさんも一緒に頑張りましょう！:smiley: