公開日:2023年1月4日
最終更新日:2023年1月22日
はじめに
「waifu-diffusion-v1-4」をいろいろ触ってみたのでその過程を記事にします。左の元画像から右の画像を生成しました。なるべく元画像に忠実にというのが今回のテーマです。
元画像はぱくたそから使わせて頂きました。
こちらの画像です。
動作環境
こちらのPCを使ってローカル環境でdiffusersからwaifu-diffusion-v1-4を実行しています。Windows 11 CUDA 11.6.2 Python 3.10.9 Git for Windows 2.39.0
環境構築はpipのみで可能です。
waifu-diffusion以外も使用するためにこのようになっています。
waifu-diffusionには不要なものも入っているかもしれません。
pip install torch==1.13.1+cu116 torchvision==0.14.1+cu116 --extra-index-url https://download.pytorch.org/whl/cu116 pip install git+https://github.com/huggingface/diffusers.git pip install git+https://github.com/huggingface/transformers.git pip install accelerate==0.15.0 scipy==1.10.0 pip install xformers==0.0.16rc425 pip install safetensors==0.2.8
念のため動作確認できたバージョンをpinしておきます。
pip install -e git+https://github.com/huggingface/diffusers.git@8d326e61cfbe5d76e25deca6093ecb22967d634e#egg=diffusers pip install -e git+https://github.com/huggingface/transformers.git@4e730b387364c9f46b6b1b0c79fdaf0903c42257#egg=transformers
モデルはgit lfsで一括ダウンロードしました。
git lfs install git clone https://huggingface.co/hakurei/waifu-diffusion
方法
手順0
効率よく画像を生成するためにPythonスクリプトを書きました。ブログの最後にのせておきます。以降の手順はそれを使用しています。
手順1(「prompt」について)
prompt(呪文)を決める必要があります。promptをいろいろ工夫して生成画像を調整する方法もありますが今回は以下のpromptに固定します。prompt
masterpiece best quality high quality absurdres kawaii princess white glowing skin kawaii face with blush blue eyes with eyelashes sweater turtleneck
negative prompt
worst quality low quality medium quality deleted lowres comic bad anatomy bad hands text error missing fingers extra digit fewer digits cropped jpeg artifacts signature watermark username blurry
手順2(「strength」について)
パラメーター「strength」についてみてみます。元画像をどの程度変換するかを示すパラメーターです。 値が大きいほどノイズが追加され元画像から離れていきます。
0 から 1 の間で指定し、デフォルトは0.8に設定されているようです。
いろいろ「strength」の値を変更して画像を出力させてみた結果がこのようになります。
python img2img.py --image lady.jpg --negative_prompt --strength 0.2 0.4 0.6 0.8
左から0.2→0.4→0.6→0.8と変えています。
デフォルトの0.8では全然違うポーズ、服装になってしまいました。
元画像を再現するという今回の目的では0.4あたりが妥当と考えました。
以降0.4に固定します。
手順3(「guidance_scale」について)
パラメーター「guidance_scale」についてみてみます。promptをどの程度反映させるかを示すパラメーターです。 値が大きいほどpromptに密接に関連する画像が生成されますが画質は低下してしまうようです。
1 以上を指定し、デフォルトは7.5に設定されているようです。
いろいろ「guidance_scale」の値を変更して画像を出力させてみた結果がこのようになります。
python img2img.py --image lady.jpg --negative_prompt --strength 0.4 --scale 3.5 5.5 7.5 9.5 11.5 13.5
左から 3.5→5.5→7.5→9.5→11.5→13.5と変えています。
高くすればするほど画像が乱れていきます。
3.5が妥当と考えました。
以降3.5に固定します。
手順4(「seed」をいろいろ変えてみる)
パラメーター「seed」を変えて良さそうなものを選択します。今回は500から順に1ずつ増やして30枚の画像を作成して良さそうなものを選びました。
python img2img.py --image lady.jpg --negative_prompt --strength 0.4 --scale 3.5 --seed 500 --n_samples 30
501が一番良さそうだったので以降501に固定します。
手順5(「num_inference_steps」)
パラメーター「num_inference_steps」についてみてみます。ノイズ除去を行うステップ数を示すパラメーターです。 値が大きいほど画像の品質が高くなるようです。
デフォルトは50に設定されています。
パラメーター「strength」に影響を受けるようで実際のステップ数は
「num_inference_steps」×「strength」
で計算されます。
「strength」に小さい値を設定すると「num_inference_steps」は大きくする必要があります。
いろいろ「num_inference_steps」の値を変更して画像を出力させてみた結果がこのようになります。
python img2img.py --image lady.jpg --negative_prompt --strength 0.4 --scale 3.5 --seed 501 --steps 50 100 200 400
左から 50→100→200→400と変えています。
400stepsのものを最終生成画像としました。
その他
その他の設定としてスケジューラの変更などもあります。python img2img.py --image lady.jpg --negative_prompt --strength 0.4 --scale 3.5 --seed 501 --steps 400 --scheduler multistepdpm python img2img.py --image lady.jpg --negative_prompt --strength 0.4 --scale 3.5 --seed 501 --steps 400 --scheduler eulera
Pythonスクリプト
import os import sys import glob import argparse import datetime import torch from PIL import Image from diffusers import StableDiffusionImg2ImgPipeline parser = argparse.ArgumentParser() parser.add_argument( '--seed', type=int, default=200, help='the seed (for reproducible sampling)', ) parser.add_argument( '--n_samples', type=int, default=1, help='how many samples to produce for each given prompt', ) parser.add_argument( '--scale', nargs='*', default=[7.5], type=float, help='guidance_scale', ) parser.add_argument( '--strength', nargs='*', default=[0.8], type=float, help='strength', ) parser.add_argument( '--steps', nargs='*', default=[50], type=int, help='num_inference_steps', ) parser.add_argument( '--negative_prompt', action="store_true", help='if enabled, use negative prompt', ) parser.add_argument( '--image', type=str, help='original image' ) parser.add_argument( '--scheduler', type=str, default='pndm', choices=['pndm', 'multistepdpm', 'eulera'] ) opt = parser.parse_args() globresult = glob.glob('*') dirlist =[] for file_or_dir in globresult: if os.path.isdir(file_or_dir) and file_or_dir != 'results': dirlist.append(file_or_dir) if len(dirlist) == 1: model_id = dirlist[0] print(f'model id: {model_id}') else: print('Unable to identify model') sys.exit() original_image = opt.image init_image = Image.open(original_image).convert("RGB").resize((512, 512)) if os.path.isfile('prompt.txt'): print('reading prompts from prompt.txt') with open('prompt.txt', 'r') as f: #prompt = f.read().splitlines() prompt = f.readlines() prompt = [x.strip() for x in prompt] prompt = ','.join(prompt) else: print('Unable to find prompt.txt') sys.exit() if opt.negative_prompt and os.path.isfile('negative_prompt.txt'): print('reading negative prompts from negative_prompt.txt') with open('negative_prompt.txt', 'r') as f: #negative_prompt = f.read().splitlines() negative_prompt = f.readlines() negative_prompt = [x.strip() for x in negative_prompt] negative_prompt = ','.join(negative_prompt) else: negative_prompt = None print(f'prompt: {prompt}') print(f'negative prompt: {negative_prompt}') pipe = StableDiffusionImg2ImgPipeline.from_pretrained(model_id, torch_dtype=torch.float32) scheduler = opt.scheduler match scheduler: case 'pmdn': from diffusers import PNDMScheduler pipe.scheduler = PNDMScheduler.from_config(pipe.scheduler.config) case 'multistepdpm': from diffusers import DPMSolverMultistepScheduler pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config) case 'eulera': from diffusers import EulerAncestralDiscreteScheduler pipe.scheduler = EulerAncestralDiscreteScheduler.from_config(pipe.scheduler.config) case _: None pipe.to("cuda") def null_safety(images, **kwargs): return images, False pipe.safety_checker = null_safety os.makedirs('results', exist_ok=True) now = datetime.datetime.today() now_str = now.strftime('%m%d_%H%M') scale_list = opt.scale strength_list = opt.strength steps_list = opt.steps for i in range(opt.n_samples): seed = opt.seed + i for scale in scale_list: for strength in strength_list: for steps in steps_list: generator = torch.Generator(device="cuda").manual_seed(seed) image = pipe( prompt = prompt, negative_prompt = negative_prompt, image = init_image, generator = generator, guidance_scale = scale, strength = strength, num_inference_steps = steps, num_images_per_prompt = 1).images[0] image.save(os.path.join('results', f'{now_str}_{scheduler}_seed{seed}_scale{scale}_strength{strength}_steps{steps}.png'))