[Amazon Bedrock] 簡単な下絵から、多数のイラストを自動生成してアイデアを膨らませる

2023.10.09

1 はじめに

CX 事業本部 delivery部の平内(SIN)です。

Amazon Bedrockを使用することで、生成AIを軽易に利用することが可能ですが、カテゴリ「Image」に分類される、Stability AI の Stable Diffusion XL(Preview) では、画像の生成が可能になっています。

生成AIで画像作成する場合、入力するプロンプトが、非常に大きな影響を与えますが、思った通りの出力を得ることは、比較的難しい作業となります。このため、ビジネスのシーン等で、即戦力として活用するのは、まだ、ちょっとハードルの高いものになっているかも知れません。

そこで、今回は、イラストなどの作成時に、簡単な下絵を書いて、そこから、「アイデアを膨らませるために、たくさんのサンプルを生成する」というイメージで作業してみました。

2 フリーハンド

最初にフリーハンドで書いた絵で試してます。(絵心のカケラもない私が、頑張って書いたイラストです)

設定したプロンプトは、以下のとおりです。

prompt = "cute puppy,light smile, In front of the doghouse with the red roof,illustration"

3 パワーポイント

次は、パワーポイントで頑張って書いた絵で試しました。

プロンプトは、以下のとおりです。

prompt = "sticker illustration of cute puppy,light smile, In front of the doghouse with the red roof,vectorized"

4 作業手順

作業は、実装した create_sample_illustration.pyと、下絵を配置して行います。

% tree .
.
├── create_sample_illustration.py
└── dog.png

実行すると、6枚の画像が生成されます。

% python3 create_sample_illustration.py
20231009_021923.png
20231009_021930.png
20231009_021935.png
20231009_021940.png
20231009_021945.png
20231009_021950.png
.
├── create_sample_illustration.py
├── data
│   ├── 20231009_021923.json
│   ├── 20231009_021923.png
│   ├── 20231009_021930.json
│   ├── 20231009_021930.png
│   ├── 20231009_021935.json
│   ├── 20231009_021935.png
│   ├── 20231009_021940.json
│   ├── 20231009_021940.png
│   ├── 20231009_021945.json
│   ├── 20231009_021945.png
│   ├── 20231009_021950.json
│   ├── 20231009_021950.png
│   └── sample_20231009_021950.png
└── dog.png

xxxxxx_xxxxxx.pngが、サイズ(512*512)の生成された画像で、xxxxxx_xxxxxx.jsonが、その諸元です。また、最後に、sample_xxxxxx_xxxxxx.pngが出力されますが、こちらは、作成された6枚の絵を一覧できるように合成した画像となります。

5 create_sample_illustration.py

コードは、大きくは、「イラストを生成するクラス(IllustrationGenerator)」と、最後に「サンプル画像を生成するクラス(Sample_Images)」の2つで構成されています。

Stable Diffusion XLを使用してい、boto3で画像生成しているのは、前者のIllustrationGeneratorです。

import io
import math
import os
import json
import datetime
import random
import base64
from PIL import Image, ImageDraw, ImageFont
import boto3


# サンプル画像を生成するクラス
class Sample_Images:
    def __init__(self, size):
        self.index = 0
        self.size = size
        self.create_max = 6
        font_path = "/System/Library/Fonts/ヒラギノ角ゴシック W5.ttc"
        self.font_size = 30
        height = math.floor(self.size * (self.create_max / 3))
        width = math.floor(self.size * (self.create_max / 2))
        self.base_image = Image.new("RGB", (width, height))
        self.draw = ImageDraw.Draw(self.base_image)
        self.font = ImageFont.truetype(font_path, size=self.font_size)

    def append(self, image, name):
        x = int(self.index % (self.create_max / 2) * self.size)
        y = int(math.floor(self.index / (self.create_max / 2)) * self.size)
        self.base_image.paste(image, (x, y))
        self.draw.text(
            (x, y + self.size - self.font_size - 5),
            name,
            fill=(0, 0, 0),
            font=self.font,
        )
        self.index += 1

    def save(self, filename):
        self.base_image = self.base_image.resize((900, 600))
        self.base_image.save(filename)


# イラストを生成するクラス
class IllustrationGenerator:
    def __init__(self, size, prompt, data_path, input_image):
        self.boto3_bedrock = boto3.client("bedrock-runtime", region_name="us-east-1")
        self.model_id = "stability.stable-diffusion-xl"
        self.size = size
        self.data_path = data_path

        # パラメータ
        self.config = {
            "filename": "",
            "seed": 0,
            "prompt": prompt,
            "size": self.size,
            "steps": 50,
            "cfg_scale": 30,
            "start_schedule": 0.6,
            "style_preset": "anime",
            "input_image": input_image,
        }
        # 入力画像
        self.input_image_b64 = self.__image_to_base64(
            Image.open(input_image).resize((self.size, self.size))
        )

    def __image_to_base64(self, image):
        buffer = io.BytesIO()
        image.save(buffer, format="PNG")
        return base64.b64encode(buffer.getvalue()).decode("utf-8")

    def __base64_to_image(self, base64_str):
        return Image.open(io.BytesIO(base64.decodebytes(bytes(base64_str, "utf-8"))))

    def create(self):
        # 日時文字列をファイル名に使用する
        basename = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")

        # 毎回変化する設定値
        self.config["filename"] = basename + ".png"
        self.config["seed"] = random.randint(0, 100000)

        # 入力パラメータ
        body = json.dumps(
            {
                "text_prompts": [{"text": self.config["prompt"], "weight": 1.0}],
                "cfg_scale": self.config["cfg_scale"],
                "seed": self.config["seed"],
                "start_schedule": self.config["start_schedule"],
                "steps": self.config["steps"],
                "style_preset": self.config["style_preset"],
                "init_image": self.input_image_b64,
            }
        )

        # 推論
        response = self.boto3_bedrock.invoke_model(body=body, modelId=self.model_id)

        # 推論結果の画像取得
        response_body = json.loads(response.get("body").read())
        response_image = self.__base64_to_image(
            response_body["artifacts"][0].get("base64")
        )

        # レスポンス画像の保存
        response_image.save("{}/{}.png".format(self.data_path, basename))
        # 設定値の保存
        with open("{}/{}.json".format(self.data_path, basename), "w") as f:
            json.dump(self.config, f, ensure_ascii=False)

        # 生成された画像と名前を返す
        return (response_image, basename)


def main():
    size = 512
    data_path = "./data"
    if not os.path.exists(data_path):
        os.makedirs(data_path)

    # 元画像
    input_image = "./dog.png"
    # 作成するイラストの説明
    # prompt = "cute puppy,light smile, In front of the doghouse with the red roof,illustration"
    prompt = "sticker illustration of cute puppy,light smile, In front of the doghouse with the red roof,vectorized"

    # サンプル画像をまとめるクラス
    sample_images = Sample_Images(size)
    # イラスト生成クラス
    illustration_generator = IllustrationGenerator(size, prompt, data_path, input_image)

    for i in range(6):
        (image, basename) = illustration_generator.create()
        sample_images.append(image, "{}.png".format(basename))
        print("{}.png".format(basename))

    sample_images.save("{}/sample_{}.png".format(data_path, basename))


if __name__ == "__main__":
    main()

6 最後に

今回は、アイデア出しのために、簡単な下絵から、たくさんのイラストを生成する作業をやってみました。

オンデマンドで使用する Stable Diffusion XL のコストは、1枚、$0.018です。

(サイズ 512*512 以下 ステップ 51以下の場合の料金です。上記のコードは、この範囲内で動作してます)

Amazon Bedrock Pricing

これぐらいのコストであれば、多くのサンプルを出力して、スピーディーにより良いアイデアが得られれば、費用対効果は大きいのではと思いました。

※「フリーハンド」と「パワーポイント」の違いは、微妙ですが、一応、差が出ているような気がしてます。