見出し画像

Stable Diffusionのパラメーターをランダムに設定して画像生成する(2)

こちらの記事を投稿してから約1年経って、紹介しているプログラムも動かなくなっていました。そこで2023/7/16に全面的に書き直しました。

前回、自宅のパソコンに作成したStable Diffusionを使って画像生成するときの入力設定をGUI化し、また画像生成時に使えるパラメーターをランダムで設定するようにPythonでコードを作成しました。詳しい内容は以前に作成した記事を見ていただけると幸いです。


1.新たな機能を追加する

上の記事で紹介した方法で実際に使ってみると、想定以上の画像が生成されました。それと同時に、気に入った画像を自分で少しだけパラメーターを変えて画像を再生成したら、もっと良い画像を生成できるのではないかという欲が出てきました。そこで今回はこの機能をコードに反映したいと思います。まずGUI画面のイメージは以下のようにしたいと思います。

CustomTkinterで作成したGUI画面

前回のGUIに追加する項目は、Guidance scale(プロンプトと出力画像の類似度)、Inference steps(画像生成に費やすステップ数)、Seeds(シード値)です。

新たに追加した「画像を開いて情報を取得する」ボタンで呼び出したい画像を選択すると、その画像のプロンプト、プロンプトと出力画像の類似度、画像生成に費やすステップ数、シード値をGUIに反映されるようにします。

このように画像を読み込むとGUI上に画像を作成したときのプロンプトや各種パラメーターが反映されますので、気に入った画像を読み込み、値を少し変更して画像生成していきます。空欄にしたパラメーターはランダムな値を使います。

2.追加機能を設計する

画像の情報はデータベースに保存します。StableDiffusionで画像生成したタイミングで、その画像のファイル名、プロンプト、ネガティブプロンプト、各種パラメーターをデータベースに書き込みます。

画像生成時に情報がデータベースにも書き込まれる

GUIの「画像を開いて情報を取得する」ボタンを押して画像を選択しますと、選択した画像のファイル名をキーにデータベースを検索し、該当画像情報をGUIに反映します。

したがって追加する機能は以下の4点です。

  1. GUIにパラメーター入力、反映の欄と「画像を開いて情報を取得する」ボタンを追加する

  2. 画像情報のデータベースを作成する

  3. 「画像生成」のボタンを押し、画像が生成された直後にデータベースに情報を書き込む

  4. 「画像を開いて情報を取得する」ボタンを押したときにデータベースを検索して選択した画像の情報をGUIに反映する

2-1. GUIにパラメーター入力、反映の欄と「画像を開いて情報を取得する」ボタンを追加する

項目を追加したことによってウィンドウサイズが大きくなります。その他はテキストボックスやボタンの追加になりますので、表示位置、変数名以外は前回のGUIと同じコードです。恐らく一番手間が掛かるのはレイアウト(rely、relheight、rewidth)の設定だと思います。

2-2. 画像情報を保持するデータベースを作成する

データベースの作成方法は以前の記事で書かせていただきましたので、下記の記事を参照いただければと思います。( 開発手順 - DB Browser for SQLiteでデータベースを設定する)

「DB Browser for SQlite」というアプリケーションでgenerate_history.dbというデータベースを作成し、historyテーブル内にfilename / prompt / negative_prompt / guidance_scale / Interface_steps / seedsフィールドを設定しています。テーブルとフィールドを設定したdbファイルをダウンロードできるようにしました。

2-3.「画像生成」のボタンを押し、画像が生成された直後にデータベースに情報を書き込む

画像生成ボタンを押したときに使用したプロンプト、ネガティブ・プロンプト、パラメーターをデータベースに追加します。生成された画像名は画像ファイルを保存するときのファイル名を参照し、残りはStableDiffusionに処理を渡す際に情報を保存した変数を参照します。

2-4.「画像を開いて情報を取得する」ボタンを押したときにデータベースを検索して選択した画像の情報をGUIに反映する

「画像を開いて情報を取得する」ボタンを押すとpngファイルのみ表示されるエクスプローラ画面が表示されるようにします。画像を選びますとデータベースから画像ファイル名をキーとして目的の情報を探します。ファイル名に画像が生成されたタイムスタンプを入れるようにしましたので同じ名前の画像ファイルが2個以上あることはデータベースを故意に書き換えない限りは起こりません。

3. Pythonコードの作成

作成したPythonコードは以下のとおりです。実行するときは、データベースファイル(generate_history.db)と学習モデル(BracingEvoMix_v1.safetensors)をPythonコードと同じ階層にコピーしてください。

import os
import customtkinter
from tkinter import filedialog
from tkinter import messagebox
import random
import math
import datetime
import sqlite3
import pandas as pd
import torch
from torch import autocast
from diffusers import StableDiffusionPipeline, EulerAncestralDiscreteScheduler

def btn_click_generate():
    model_id = "BracingEvoMix_v1.safetensors"
    pipe = StableDiffusionPipeline.from_ckpt(
        model_id,
        load_safety_checker = False,
        extract_ema = True,
        torch_dtype = torch.float16)
    pipe.scheduler = EulerAncestralDiscreteScheduler.from_config(pipe.scheduler.config)
    pipe.load_textual_inversion("sayakpaul/EasyNegative-test",weight_name="EasyNegative.safetensors", token="EasyNegative")

    pipe.to("cuda")

    prompt = prompt_data.get()
    negative_prompt = negative_prompt_data.get()
    if prompt == "":
        prompt = "((masterpiece:1.4, best quality)), ((masterpiece, best quality)),  (photo realistic:1.4), woman, female, Beautiful face, bright eyes,"
    if negative_prompt == "":
        negative_prompt = "Easy Negative (worst quality:2) (low quality:2) (normal quality:2) lowers normal quality ((monochrome)) ((grayscale)),skin spots,acnes,skin blemishes,age spot,ugly face,fat,missing fingers, extra fingers, extra arms,open chest,thick eyebrows, huge breasts, open chest"

    generate = generate_data.get()
    if generate == "":
        generate = 10
    else:
        generate = int(generate)

    conn = sqlite3.connect('generate_history.db')

    for i in range(generate):
        if guidance_data.get() == "":
            scale = math.floor((random.uniform(6, 15))*100)/100
        else:
            scale = float(guidance_data.get())
        if inference_data.get() == "":
            steps = random.randrange(18, 45, 1)
        else:
            steps = int(inference_data.get())
        if seeds_data.get() == "":
            seed = random.randrange(0, 4294967295, 1)
        else:
            seed = int(seeds_data.get())

        with autocast("cuda"):
            generator = torch.Generator("cuda").manual_seed(seed)
            image = pipe(
                prompt = prompt,
                negative_prompt = negative_prompt,
                generator = generator,
                num_inference_steps = steps,
                guidance_scale = scale,
                width = 768,height = 768,).images[0]

        # ファイル作成時間を求める
        t_delta = datetime.timedelta(hours=9)
        JST = datetime.timezone(t_delta, 'JST')
        now = datetime.datetime.now(JST)

        pict_name = f"{now.strftime('%Y%m%d%H%M%S')}_{scale}_{steps}_{seed}.png"
        image.save(pict_name)

        # 生成された画像情報をデータベースに書き込む
        cur = conn.cursor()
        sql = 'insert into history (filename, prompt, negative_prompt, guidance_scale, Inference_steps, seeds ) values (?,?,?,?,?,?)'
        data = (pict_name, prompt, negative_prompt, scale, steps, seed)
        cur.execute(sql,data)
        conn.commit()
    conn.close()

def btn_click_open():
    selfile = []

    # エクスプローラを開いて画像ファイルを指定する
    filename = filedialog.askopenfilename(title = "画像ファイルを開く",
    filetypes = [("Image file", ".png"), ("PNG", ".png")],
    initialdir = "./")
    selfile.append(os.path.basename(filename))

    # データベースから選択した画像情報を検索し、その情報をGUIに書き込む
    conn = sqlite3.connect('generate_history.db')
    pictinfo = pd.read_sql_query('SELECT * FROM history WHERE filename == ?', conn, params=selfile)
    if len(pictinfo) == 0:
        messagebox.showerror('DBエラー', '画像を選択していないか、選択した画像情報がデータベースに存在しません')
    else:
        prompt_data.delete(0, customtkinter.END)
        negative_prompt_data.delete(0, customtkinter.END)
        guidance_data.delete(0, customtkinter.END)
        inference_data.delete(0, customtkinter.END)
        seeds_data.delete(0, customtkinter.END)

        prompt_data.insert(customtkinter.END,pictinfo.prompt[0])
        negative_prompt_data.insert(customtkinter.END,pictinfo.negative_prompt[0])
        guidance_data.insert(customtkinter.END,pictinfo.guidance_scale[0])
        inference_data.insert(customtkinter.END,pictinfo.Inference_steps[0])
        seeds_data.insert(customtkinter.END,pictinfo.seeds[0])
    conn.close()

customtkinter.set_appearance_mode("System")  # Modes: system (default), light, dark
customtkinter.set_default_color_theme("blue")  # Themes: blue (default), dark-blue, green

root = customtkinter.CTk()
root.geometry('550x350')
root.title('Stable Diffusion GUI')

# プロンプトラベル
prompt_lbl = customtkinter.CTkLabel(root, text="プロンプト:(未入力時はデフォルト値で作成します)", width=60, justify="left", anchor="w")
prompt_lbl.place(relx=0.1, rely=0.05, relheight=0.05, relwidth=0.8)

# プロンプト入力テキスト
prompt_data = customtkinter.CTkEntry(root,placeholder_text="", width=20, height=25, border_width=2, corner_radius=6)
prompt_data.place(relx=0.1, rely=0.1, relheight=0.1, relwidth=0.8)

# ネガティブプロンプト・ラベル
negative_prompt_lbl = customtkinter.CTkLabel(root, text='ネガティブプロンプト:(未入力時はデフォルト値で作成します)', width=60, justify="left", anchor="w")
negative_prompt_lbl.place(relx=0.1, rely=0.22, relheight=0.05, relwidth=0.8)

# ネガティブ・プロンプト入力テキスト
negative_prompt_data = customtkinter.CTkEntry(root,placeholder_text="", width=25, height=25, border_width=2, corner_radius=6)
negative_prompt_data.place(relx=0.1, rely=0.27, relheight=0.1, relwidth=0.8)

# 生成枚数ラベル
generate_lbl = customtkinter.CTkLabel(root, text='生成枚数:(未入力時は10枚作成します)', width=60, justify="left", anchor="w")
generate_lbl.place(relx=0.1, rely=0.40, relheight=0.05, relwidth=0.8)

# 生成枚数入力テキスト
generate_data = customtkinter.CTkEntry(root,placeholder_text="", width=25, height=25, border_width=2, corner_radius=6)
generate_data.place(relx=0.1, rely=0.45, relheight=0.05, relwidth=0.2)

# Guidance scaleラベル
guidance_lbl = customtkinter.CTkLabel(root, text='プロンプトと出力画像の類似度:(未入力時はランダム)', width=60, justify="left", anchor="w")
guidance_lbl.place(relx=0.1, rely=0.50, relheight=0.05, relwidth=0.8)

# Guidance scale(プロンプトと出力画像の類似度)入力テキスト
guidance_data = customtkinter.CTkEntry(root,placeholder_text="", width=25, height=25, border_width=2, corner_radius=6)
guidance_data.place(relx=0.1, rely=0.55, relheight=0.05, relwidth=0.2)

# Inference stepsラベル
inference_lbl = customtkinter.CTkLabel(root, text='画像生成に費やすステップ数:(未入力時はランダム)', width=60, justify="left", anchor="w")
inference_lbl.place(relx=0.1, rely=0.60, relheight=0.05, relwidth=0.8)

# Inference steps(画像生成に費やすステップ数)入力テキスト
inference_data = customtkinter.CTkEntry(root,placeholder_text="", width=25, height=25, border_width=2, corner_radius=6)
inference_data.place(relx=0.1, rely=0.65, relheight=0.05, relwidth=0.2)

# Seeds値ラベル
seeds_lbl = customtkinter.CTkLabel(root, text='シード値(未入力時はランダム)', width=60, justify="left", anchor="w")
seeds_lbl.place(relx=0.1, rely=0.70, relheight=0.05, relwidth=0.8)

# Seeds値 入力テキスト
seeds_data = customtkinter.CTkEntry(root,placeholder_text="", width=25, height=25, border_width=2, corner_radius=6)
seeds_data.place(relx=0.1, rely=0.75, relheight=0.05, relwidth=0.2)

# 画像生成ボタン
btn_gen = customtkinter.CTkButton(root, text='画像生成', command=btn_click_generate)
btn_gen.place(relx=0.1, rely=0.85, relheight=0.1, relwidth=0.3)

# 画像選択ボタン
btn_open = customtkinter.CTkButton(root, text='画像を開いて情報を取得する', command=btn_click_open)
btn_open.place(relx=0.5, rely=0.85, relheight=0.1, relwidth=0.4)

root.mainloop()

3-1.使用例

例えばランダムで生成した画像に良いものがあれば、これを読み込んでシード値を残して空欄にすればその他の生成パラメータのみをランダムにして画像生成できます。

生成した画像を読み込んでシード値を取得
シード値以外をランダムで生成

この記事が気に入ったらサポートをしてみませんか?