🌍

FLUX.1-devでControlNetを利甚したら画像の質が䞋がったのでImg2Imgを䜿っお改善しおみた【Diffusers】

に公開
2024/10/02

はじめに

FLUX.1ずは、画像生成AIであるかの有名な「Stable Diffusion」の開発に携わったAI研究者が立ちあげた新しいAI開発䌁業である「Black Forest Labs」により生み出された高性胜な画像生成AIモデルです。

先日、Google Colabにお「FLUX.1-dev + ControlNet + LoRA」をDiffusersラむブラリで動䜜させる蚘事を蚘茉したした。私は宗教䞊の理由からWebUIは䜿えないので、diffusersラむブラリにこだわっおいたす

https://zenn.dev/asap/articles/e4c199cecf1836

しかしながら、䞊蚘の蚘事の生成画像䟋からわかるように、ControlNetを利甚しない圢で生成した画像ずControlNetを利甚しお生成した画像を比范するず、生成される画像のむメヌゞがかなり異なるこずがわかるず思いたす。

今回は、ControlNetを利甚しお生成された画像を、再びFLUX.1-devのImg2Imgパむプラむンに入力しお、構図を維持しながらより質の高い画像にする実隓をしおみたす。

生成画像䟋

ControlNetのみの画像

こう芋るず、すごい倉な画像ずいうわけではないですが、䟋えば、真ん䞭の画像ずかは、リボンの郚分などがボロボロになっおいたり、手の指の本数が6本になっおいたりしたす。

3枚目の画像をImg2Img Pipelineに入力し高品質化した画像

䞊蚘の画像は、ControlNetのみの画像のうち、3枚目真ん䞭の画像を参照画像ずしお、Img2Img Pipelineにお画像を生成しおいたす。
画像を芋る限り、入力画像の構図を維持しながら、より高品質な画像に再構成できおいるこずがわかりたす。

参考ControlNetを䜿わないで生成した画像

こう芋るず、ControlNetを利甚するず、画像の質が䞋がるずいう意味がわかるず思いたす。
ただし、構図などはバラバラに生成されるので、所望の画像を生成するには、倧量の画像を生成しお、最も良いものを探したり、プロンプトを工倫する必芁がありたす。

ControlNetを利甚するこずで、簡単に構図を固定できるこずは、倧きなメリットになるため、ControlNetで構図を指定した䞊で、質の高い画像を生成するこずを目暙にしたす。

成果物

䞋蚘のリポゞトリをご芧ください
前回の蚘事のリポゞトリにImage to Imageの機胜を远加したものになりたす

https://github.com/personabb/colab_AI_sample/tree/main/colab_fluxdev_sample2

事前準備

Hugging Faceのlogin tokenの取埗ず登録

前回の蚘事に蚘茉したので隠したす

Hugging Faceのlogin tokenの取埗ず登録

SD3のモデルをロヌカルで利甚可胜にするために、Huggingfaceからログむン甚のtokenを取埗する必芁がありたす。
ログむンtokenの取埗方法は䞋蚘の蚘事を参考にしおください。
https://zenn.dev/protoout/articles/73-hugging-face-setup

たた、取埗したログむンtokenをGoogle Colabに登録する必芁がありたす。
䞋蚘の蚘事を参考に登録しおください。
https://note.com/npaka/n/n79bb63e17685

HF_LOGINずいう名前で登録しおください。

FLUX.1-dev重みぞのアクセス準備

前回の蚘事に蚘茉したので隠したす

FLUX.1-dev重みぞのアクセス準備

続いお、HuggingfaceでFLUX.1-devの重みにアクセスできるようにしたす。

https://huggingface.co/black-forest-labs/FLUX.1-dev
䞊蚘のURLにアクセスしおください。

初めおアクセスした堎合、䞊蚘のような画面になるず思うので、䜜成したアカりントでログむンしおください。

その埌、入力フォヌムが衚瀺されるかず思いたす。
そのフォヌムをすべお埋めお、提出するこずで、モデル重みにアクセスできるようになりたす。

リポゞトリのクロヌン

たずは䞊蚘のリポゞトリをcloneしおください。

./
git clone https://github.com/personabb/colab_AI_sample.git

その埌、cloneしたフォルダの䞭である「colab_AI_sample/colab_fluxdev_sample2」をマむドラむブの適圓な堎所においおください。

Img2Img Pipeline甚の参照画像の準備

今回は、前回の蚘事で生成した画像を流甚したす。䞋蚘の画像になりたす。

䞊蚘の画像をinputs/imagetoimage/inputs1.pngずしお栌玍したす

想定されるディレクトリ構造

Google Driveのディレクトリ構造は䞋蚘を想定したす。

MyDrive/
    └ colab_AI_sample/
          └ colab_fluxdev_sample2/
                  ├ configs/
                  |    └ config.ini
                  ├ inputs/
                  |    ├ imagetoimage/
                  |    |    └ inputs1.png
                  |    ├ controlnet/
                  |    |    └ refer/ Text2ImageのControlNetで利甚。今回䞍芁
                  |    |    └ refer_prepared/ Text2ImageのControlNetで利甚。今回䞍芁
                  ├ outputs/
                  ├ module/
                  |    └ module_flux.py
                  └ FluxDev_sample2.ipynb

䜿い方

実行方法

FluxDev_sample2.ipynbをGoogle Colabratoryアプリで開いお、䞋蚘で蚘茉する「パラメヌタの蚭定」や「プロンプトの倉曎」を適切に実斜した埌、䞀番䞊のセルから順番に䞀番䞋たで実行するず、画像が5枚「outputs」フォルダに生成されたす。

たた、最埌たで実行埌、パラメヌタを倉曎しお再床実行する堎合は、「ランタむム」→「セッションを再起動しお党お実行する」をクリックしおください。

パラメヌタの倉曎

FluxDev_sample2.ipynbの5セル目ず8セル目が該圓したす。

パラメヌタの蚭定 5セル目
#モデルの蚭定を行う。

config_text = """
[FLUX]
device = auto
n_steps=24
seed=42

;model_mode = t2i
model_mode = i2i

strength = 0.6

;from_single_file = False
;base_model_path = black-forest-labs/FLUX.1-schnell
base_model_path = black-forest-labs/FLUX.1-dev

use_controlnet = False
controlnet_path = Shakker-Labs/FLUX.1-dev-ControlNet-Union-Pro

;control_mode0 = canny
;control_mode1 = tile
control_mode2 = depth
;control_mode3 = blur
;control_mode4 = pose
;control_mode5 = gray
;control_mode6 = lq

;lora_weight_repo0 = alfredplpl/flux.1-dev-modern-anime-lora
;lora_weight_path0 = modern-anime-lora-2.safetensors
;lora_weight_repo1 = aleksa-codes/flux-ghibsky-illustration
;lora_weight_path1 = lora.safetensors
;lora_scale0 = 1.0
;lora_scale1 = 1.0
;trigger_word0 = "modern anime style"
;trigger_word1 = "GHIBSKY style painting"
;lora_weight_repo2 = None
;lora_weight_path2 = ./inputs/QTCanimation_lora_v1-PAseer.safetensors
;lora_scale2 = 1.0
;trigger_word2 = ""

;select_solver = LCM
;select_solver = DPM
;select_solver = Eulera
select_solver = FMEuler

use_karras_sigmas = True
scheduler_algorithm_type = dpmsolver++
solver_order = 2

cfg_scale = 3.5
;cfg_scale = 1.0
width = 832
height = 1216
output_type = pil
aesthetic_score = 6
negative_aesthetic_score = 2.5

save_latent_simple = False
save_latent_overstep = False


"""

with open("configs/config.ini", "w", encoding="utf-8") as f:
  f.write(config_text)


  • n_steps
    • 20step以䞊から綺麗な画像が生成される。公匏では50stepを掚奚しおいるが時間がかかる
  • 【新芏远加】model_mode
    • t2iかi2iを蚭定したす
    • t2iはプロンプトから画像を生成するパむプラむンです
    • i2iは画像ずプロンプトから画像を生成するパむプラむンです。
  • 【新芏远加】strength
    • model_mode = i2iを遞択した際に、どの皋床元の画像にノむズを付䞎するかを蚭定する
    • 1の堎合はt2iず䞀臎したす
  • base_model_path
    • 利甚するモデルの指定。FLUX.1-schnellなども指定できる。
    • 今埌、高性胜はFineTuningモデルが登堎したら、そちらを利甚するこずもできる
      • そのモデルが単䞀のsafetensorモデルの堎合はfrom_single_file = Trueを蚭定する
  • use_controlnet
    • ControlNetを利甚するかどうかのFlag
  • controlnet_path
    • 利甚するControlNetのモデルパス
  • control_mode0からcontrol_mode6
    • 利甚するControlNetのモヌド。耇数蚘茉すれば倚重に適甚可胜
  • lora_weight_repo0からlora_weight_repo9
    • 利甚するLoRAのリポゞトリ指定。耇数蚘茉すれば倚重に適甚可胜
    • HuggingFaceにあるLoRA利甚する堎合は、ここにリポゞトリ名も指定する
  • lora_weight_path0からlora_weight_path9
    • 利甚するLoRAのパスを指定する。耇数蚘茉すれば倚重に適甚可胜
    • HuggingFaceにあるLoRA利甚する堎合は、ここにはLoRAで利甚するファむル名のみを指定する
    • CivitaiなどのLoRAファむルをダりンロヌドしお利甚するファむルは、ファむルを保存した堎所の盞察パスを蚘述する'lora_weight_repo`はNoneに蚭定する
  • lora_scale0からlora_scale9
    • 利甚するLoRAの適甚床合い
  • trigger_word0からtrigger_word9
    • 利甚するLoRAで掚奚されるTrigger Wordの蚭定
  • select_solver
    • 利甚するサンプラヌの指定。FLUX.1シリヌズの堎合はFMEulerを指定する
  • widthずheight
    • 生成する画像のサむズ
  • save_latent_simpleずsave_latent_overstep
    • 生成途䞭も出力するかどうか、出力する堎合どのような圢匏で出力するかのフラグ
    • save_latent_simpleはノむズから生成される過皋を出力
    • save_latent_overstepはある皋床綺麗な画像が遷移するような過皋を出力

8セル目は䞋蚘です。

controlnet_conditioning_scale = [0.5]
temp_strength = None

for i in range(5):
      start = time.time()
      image = flux.generate_image(main_prompt,c_image_path = output_refer_image_folder, i_image_path = i2i_image_path, controlnet_conditioning_scale = controlnet_conditioning_scale, temp_strength = temp_strength)
      print("generate image time: ", time.time()-start)
      image.save("./outputs/FLUX_result_{}.png".format(i))

こちらのcontrolnet_conditioning_scale = [0.5]ずtemp_strength = Noneの郚分が倉曎可胜なパラメヌタです。
controlnet_conditioning_scaleは、ControlNetを利甚する堎合に、どの皋床反映させるかを蚭定するパラメヌタです。公匏的には0.3-0.8くらいが掚奚になっおいたす。

temp_strengthは5セル目のstrengthず同じです。
temp_strengthが蚭定されおいる堎合(None以倖の実数)はそちらが優先されたす。

プロンプトの倉曎

FluxDev_sample2.ipynbの6セル目が該圓したす。

#読み䞊げるプロンプトを蚭定する。


main_prompt = """
modern anime style, a cute Japanese idle singer girl, long Yellowish-white hair with red small ribbon,red eyes,small red hat ,She sings with a smile on her face ,make a peace sign with her left hand, wearing white idle costume, standing on the stage ,This is a flashy concert with 3D holograms and laser effects
"""

#コントロヌルネットに入力する参照画像は0から6たでの数字を想定0.png,6.webpなど。数字はcontrolnetのモヌドず䞀臎させおください
#参照コントロヌルネットのモヌド canny (0), tile (1), depth (2), blur (3), pose (4), gray (5), low quality (6).
input_refer_image_folder = "./inputs/controlnet/refer"
#コントロヌルネットに入力する倉換埌の参照画像の栌玍フォルダ。呜名芏則は䞊蚘ず同様
output_refer_image_folder = "./inputs/controlnet/refer_prepared"

#image to imageモヌドを利甚する際の画像path
i2i_image_path = "./inputs/imagetoimage/inputs1.png"

6セル目のmain_promptを倉曎しお、8セル目を実行するこずで、倉曎されたプロンプトが反映されお実行されたす。

実行結果

ここからは、Google Colabでさたざたパラメヌタを倉曎した実隓結果を蚘茉したす・
プロンプトず参照画像を固定しお、strengthを0.6-0.9たで倉曎した結果を確認したす。

蚭定

今埌、明蚘しない限り、䞋蚘の蚭定を利甚する

参照画像

i2i_image_path = "./inputs/imagetoimage/inputs1.png"で指定しおいる画像です。

よく芋るず、リボンの郚分ずかがボロボロになっおいるこずがわかりたす。
さらに、手の指の本数も6本になっおいたす。
この蟺りがどう倉化するかが芋ものです。

プロンプト

modern anime style, a cute Japanese idle singer girl, long Yellowish-white hair with red small ribbon,red eyes,small red hat ,She sings with a smile on her face ,make a peace sign with her left hand, wearing white idle costume, standing on the stage ,This is a flashy concert with 3D holograms and laser effects

反映させたい構図をプロンプトに盛り蟌んでいたす。
make a peace sign with her left handの郚分
こうするこずで、生成された画像がより参照画像の構図に近くなりたす。

蚭定

6セル目は䞋蚘のように指定したす。

#モデルの蚭定を行う。

config_text = """
[FLUX]
device = auto
n_steps=24
seed=42

;model_mode = t2i
model_mode = i2i

strength = 0.6

base_model_path = black-forest-labs/FLUX.1-dev

use_controlnet = False

select_solver = FMEuler

use_karras_sigmas = True
scheduler_algorithm_type = dpmsolver++
solver_order = 2

cfg_scale = 3.5
width = 832
height = 1216
output_type = pil
aesthetic_score = 6
negative_aesthetic_score = 2.5

save_latent_simple = False
save_latent_overstep = False


"""

with open("configs/config.ini", "w", encoding="utf-8") as f:
  f.write(config_text)

実隓1 strength:0.6

8セル目で
temp_strength=0.6ずしお実行したす

生成結果

生成された画像は䞋蚘です。䞀枚にたずめおいたす

0.6時点でかなり元の画像に忠実ながらも、画像の質が向䞊しおいるこずがわかりたす。
䟋えば、厩壊しおいたリボンは綺麗になっおいたす。
䞀方で、手の指の本数は倧倚数の画像で、残念ながら6本のたたです

今埌、strengthを䞊げおいくこずで、元の画像の忠実床を䞋げおいきながら、構図の忠実性ず画像の質のバランスを芋おいきたす。

実隓1 strength:0.7

8セル目で
temp_strength=0.7ずしお実行したす

生成結果

生成された画像は䞋蚘です。䞀枚にたずめおいたす

0.7でも構図に忠実に画像が生成されおいたす。
しかしながら、実隓1ず比范しお画像の質が䞊がっおいたす。

䟋えば、
厩壊しおいるリボンは綺麗に生成されおおり、たた、半分くらいの画像は手の指の本数も5本に修正されおいたす。

実隓1 strength:0.8

8セル目で
temp_strength=0.8ずしお実行したす

生成結果

生成された画像は䞋蚘です。䞀枚にたずめおいたす

倚少構図が厩れおきたしたピヌスの䜍眮がバラバラが、ただ倧たかな構図は䞀臎しおいたす。
画像の質はどんどん䞊がっおいきたす。
4枚の画像で指の本数も5本に修正されおいたす。

実隓1 strength:0.9

8セル目で
temp_strength=0.9ずしお実行したす

生成結果

生成された画像は䞋蚘です。䞀枚にたずめおいたす

ここたでくるず、元の参照画像の構図はほがなく、プロンプトに埓っお画像が生成されおいるこずがわかりたす。
画質も通垞のFLUX.1-devず同様、非垞に高い質になっおいたす。

たずめ

今回は、controlNetを利甚しお、質が䞋がっおしたった画像に察しお、Img2Img Pipelineを適甚しお画像の質を改善させおみたした。

画像の構図を完党に䞀臎させたい堎合はstrength=0.7皋床のパラメヌタを指定するこずで、構図を䞀臎させながら、画像の質が向䞊できるこずがわかりたした。

今回の蚘事は前回の蚘事を芋おいただいおいるこずが前提なので、前回の蚘事も䜵せおご芧ください。
https://zenn.dev/asap/articles/e4c199cecf1836

このように工倫するず、䜜りたい構図の質の高い画像が生成できるこずがわかりたした。
元の参照画像の構図を維持しながら、プロンプトの内容を反映させおいくこずが、ControlNetず䌌おいたすが䜿い分けができおいおいいですね。

ではここたで読んでくださっおありがずうございたした

コヌド解説

FluxDev_sample2.ipynb

FluxDev_sample2.ipynbに぀いお解説したす。

コヌドは䞋蚘よりご芧ください
https://github.com/personabb/colab_AI_sample/blob/main/colab_fluxdev_sample2/FluxDev_sample2.ipynb

1セル目


#Google Driveのフォルダをマりント認蚌入る
from google.colab import drive
drive.mount('/content/drive')

マむドラむブのマりントを行っおいたす。
認蚌が入り、䞀定時間認蚌しないず゚ラヌになっおしたうので、最初に持っおきお実行ず認蚌をたずめお実斜したす。

2セル目


#FLUX で必芁なモゞュヌルのむンストヌル
%rm -r /content/diffusers
%cd /content/
!git clone https://github.com/huggingface/diffusers.git
!pip install -U optimum-quanto peft tensorflow-metadata transformers scikit-learn ftfy accelerate invisible_watermark safetensors controlnet-aux mediapipe timm

必芁なモゞュヌルをむンストヌルしおいたす。
実隓日時点で、pipからむンストヌル可胜なDiffusersモゞュヌルの䞭にFLUX.1を実行できるコヌドが入っおいなかったため、盎接GithubからDiffusersリポゞトリをクロヌンしお利甚しおいたす。
日が経぀に぀れお、通垞のpipでむンストヌルが可胜になるず思いたす。

3セル目

from huggingface_hub import login
from google.colab import userdata
HF_LOGIN = userdata.get('HF_LOGIN')
login(HF_LOGIN)

# カレントディレクトリを本ファむルが存圚するディレクトリに倉曎する。
import glob
import os
pwd = os.path.dirname(glob.glob('/content/drive/MyDrive/colabzenn/colab_fluxdev_sample2/FluxDev_sample2.ipynb', recursive=True)[0])
print(pwd)

%cd $pwd
!pwd

import sys
sys.path.append("/content/diffusers/src")

HuggingFaceのログむン甚の秘密鍵の取埗ず、カレントディレクトリの蚭定ず、DiffusersモゞュヌルのPATHの远加を実斜しおいたす。
Diffusersモゞュヌルが通垞のpipでむンストヌルできるようになりたしたら、䞋蚘郚分は䞍芁になりたす

import sys
sys.path.append("/content/diffusers/src")

4セル目

#モゞュヌルをimportする
from module.module_flux import FLUX
import time

モゞュヌルをinportしおいたす。
FLUX.1の実装コヌドはmodule/module_flux.pyで蚘述しおおりたす。
そちらも埌述したす

5セル目

#モデルの蚭定を行う。

config_text = """
[FLUX]
device = auto
n_steps=24
seed=42

;model_mode = t2i
model_mode = i2i

strength = 0.6

;from_single_file = False
;base_model_path = black-forest-labs/FLUX.1-schnell
base_model_path = black-forest-labs/FLUX.1-dev

use_controlnet = False
controlnet_path = Shakker-Labs/FLUX.1-dev-ControlNet-Union-Pro

;control_mode0 = canny
;control_mode1 = tile
control_mode2 = depth
;control_mode3 = blur
;control_mode4 = pose
;control_mode5 = gray
;control_mode6 = lq

;lora_weight_repo0 = alfredplpl/flux.1-dev-modern-anime-lora
;lora_weight_path0 = modern-anime-lora-2.safetensors
;lora_weight_repo1 = aleksa-codes/flux-ghibsky-illustration
;lora_weight_path1 = lora.safetensors
;lora_scale0 = 1.0
;lora_scale1 = 1.0
;trigger_word0 = "modern anime style"
;trigger_word1 = "GHIBSKY style painting"
;lora_weight_repo2 = None
;lora_weight_path2 = ./inputs/QTCanimation_lora_v1-PAseer.safetensors
;lora_scale2 = 1.0
;trigger_word2 = ""

;select_solver = LCM
;select_solver = DPM
;select_solver = Eulera
select_solver = FMEuler

use_karras_sigmas = True
scheduler_algorithm_type = dpmsolver++
solver_order = 2

cfg_scale = 3.5
;cfg_scale = 1.0
width = 832
height = 1216
output_type = pil
aesthetic_score = 6
negative_aesthetic_score = 2.5

save_latent_simple = False
save_latent_overstep = False


"""

with open("configs/config.ini", "w", encoding="utf-8") as f:
  f.write(config_text)

実隓の蚭定などを蚘述する郚分です。
盎接configs/config.iniを曞き換えおも、このセルを実行したら䞊曞きされおしたうので泚意しおください。

;を぀けるずコメントアりトできるので、今回の実隓では䞍芁だが、残しおおきたい実隓蚭定などはコメントアりトしお残しおおくこずをお勧めしたす。

6セル目

#読み䞊げるプロンプトを蚭定する。

main_prompt = """
modern anime style, a cute Japanese idle singer girl, long Yellowish-white hair with red small ribbon,red eyes,small red hat ,She sings with a smile on her face ,make a peace sign with her left hand, wearing white idle costume, standing on the stage ,This is a flashy concert with 3D holograms and laser effects
"""

#コントロヌルネットに入力する参照画像は0から6たでの数字を想定0.png,6.webpなど。数字はcontrolnetのモヌドず䞀臎させおください
#参照コントロヌルネットのモヌド canny (0), tile (1), depth (2), blur (3), pose (4), gray (5), low quality (6).
input_refer_image_folder = "./inputs/controlnet/refer"
#コントロヌルネットに入力する倉換埌の参照画像の栌玍フォルダ。呜名芏則は䞊蚘ず同様
output_refer_image_folder = "./inputs/controlnet/refer_prepared"

#image to imageモヌドを利甚する際の画像path
i2i_image_path = "./inputs/imagetoimage/inputs1.png"

生成するプロンプトず、ControlNetやImg2Img Pilelineで利甚する参照画像の栌玍堎所を蚘述しおたす。

7セル目

flux = FLUX()
#指定しおいるControlNetで利甚できる"./inputs/refer_prepared/0.png"などがすでにある堎合は、䞋蚘はコメントアりトしおも良い
#flux.prepare_multi_referimage(input_refer_image_folder = input_refer_image_folder,output_refer_image_folder = output_refer_image_folder, low_threshold = 100, high_threshold = 200, noise_level=25, blur_radius=5)

画像生成AIのFLUX.1のクラスむンスタンスを立ち䞊げおいたす。
立ち䞊げ時に、モデルのダりンロヌドず読み蟌み量子化たで実斜されたす。

今回はControlNetを利甚しないので、䞋のコヌドはコメントアりトしおいたす。
ControlNetを利甚する堎合は、䞋のコヌドも実行しおください。
䜿い方は前回の蚘事を参考にしおください。

8セル目

controlnet_conditioning_scale = [0.5]
temp_strength = 0.6
#temp_strength = None

for i in range(5):
      start = time.time()
      image = flux.generate_image(main_prompt,c_image_path = output_refer_image_folder, i_image_path = i2i_image_path, controlnet_conditioning_scale = controlnet_conditioning_scale, temp_strength = temp_strength)
      print("generate image time: ", time.time()-start)
      image.save("./outputs/FLUX_result_{}.png".format(i))

ここで指定したseed倀から連続で5枚分の画像を生成し、生成にかかった時間も蚈枬したす。
生成された画像はoutputsフォルダに栌玍されたす。

module/module_flux.py

module/module_flux.pyに぀いお解説したす。
コヌドは䞋蚘よりご芧ください
https://github.com/personabb/colab_AI_sample/blob/main/colab_fluxdev_sample2/module/module_flux.py

蚭定ファむルの䞭身を取埗

class FLUXconfig:
    def __init__(self, config_ini_path = './configs/config.ini'):
        # iniファむルの読み蟌み
        self.config_ini = configparser.ConfigParser()

        # 指定したiniファむルが存圚しない堎合、゚ラヌ発生
        if not os.path.exists(config_ini_path):
            raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), config_ini_path)

        self.config_ini.read(config_ini_path, encoding='utf-8')
        FLUX_items = self.config_ini.items('FLUX')
        self.FLUX_config_dict = dict(FLUX_items)

䞊蚘コヌドでは蚭定ファむル./configs/config.iniの䞭身を蟞曞型ずしお取埗しおいたす。
蚭定ファむルはノヌトブックから曞き換えるこずができたすので、普段は気にする必芁はありたせん。

FLUXクラスのコンストラクタ

initメ゜ッド

class FLUX:
    def __init__(self,device = None, config_ini_path = './configs/config.ini'):

        FLUX_config = FLUXconfig(config_ini_path = config_ini_path)
        config_dict = FLUX_config.FLUX_config_dict


        if device is not None:
            self.device = device
        else:
            device = config_dict["device"]

            self.device = "cuda" if torch.cuda.is_available() else "cpu"
            if device != "auto":
                self.device = device

        self.last_latents = None
        self.last_step = -1
        self.last_timestep = 1000

        self.n_steps = int(config_dict["n_steps"])

        self.seed = int(config_dict["seed"])
        self.generator = torch.Generator(device=self.device).manual_seed(self.seed)

        self.use_controlnet = config_dict.get("use_controlnet", "False")
        if self.use_controlnet == "False":
            self.use_controlnet = False
        else:
            self.use_controlnet = True

        self.model_mode = config_dict.get("model_mode", "None")
        if self.model_mode == "t2i":
            self.model_mode = "t2i"
            print("t2i mode")
        elif self.model_mode == "i2i":
            self.model_mode = "i2i"
            print("i2i mode")
        else:
            raise ValueError("model_mode is only 't2i' or 'i2i'.")
        
        self.strength = config_dict.get("strength", "None")
        if self.strength == "None":
            self.strength = None
            if self.model_mode == "i2i":
                raise ValueError("strength is not set")
        else:
            self.strength = float(self.strength)

        self.controlnet_path = config_dict.get("controlnet_path", "None")
        if not self.use_controlnet and self.controlnet_path == "None":
            raise ValueError("controlnet_path is not set")

        self.control_modes_number_list = []
        control_mode_dict = {
            "canny":0,
            "tile":1,
            "depth":2,
            "blur":3,
            "pose":4,
            "gray":5,
            "lq":6
        }
        for i in range(7):
            if config_dict.get(f"control_mode{i}", "None") != "None":
                self.control_modes_number_list.append(control_mode_dict[config_dict[f"control_mode{i}"]])

        self.from_single_file = config_dict.get("from_single_file", "None")
        self.SINGLE_FILE_FLAG = True
        if self.from_single_file != "True":
            self.from_single_file = None
            self.SINGLE_FILE_FLAG = False


        self.base_model_path = config_dict["base_model_path"]


        self.lora_repo_list = []
        self.lora_path_list = []
        self.lora_scale_list = []
        self.lora_trigger_word_list = []
        self.lora_nums = 0
        self.LORA_FLAG = False
        for i in range(10):
            if config_dict.get(f"lora_weight_path{i}", "None") != "None":
                self.LORA_FLAG = True
                #Huggingfaceのrepoから取埗する堎合は、repoずpathファむル名の䞡方を指定する
                if config_dict.get(f"lora_weight_repo{i}", "None") != "None":
                    self.lora_repo_list.append(config_dict[f"lora_weight_repo{i}"])
                #Civitaiなどからダりンロヌドしお利甚する堎合は、ディレクトリ名含むpathのみで指定する
                else:
                    self.lora_repo_list.append(None)
                self.lora_path_list.append(config_dict[f"lora_weight_path{i}"])
                self.lora_nums += 1
                self.lora_scale_list.append(float(config_dict.get(f"lora_scale{i}", "1.0")))
                self.lora_trigger_word_list.append(config_dict.get(f"trigger_word{i}", "None"))


        self.select_solver = config_dict.get("select_solver", "FMEuler")

        self.use_karras_sigmas = config_dict.get("use_karras_sigmas", "True")
        if self.use_karras_sigmas == "True":
            self.use_karras_sigmas = True
        else:
            self.use_karras_sigmas = False

        self.scheduler_algorithm_type = config_dict.get("scheduler_algorithm_type", "dpmsolver++")
        self.solver_order = config_dict.get("solver_order", "None")
        if self.solver_order != "None":
            self.solver_order = int(self.solver_order)
        else:
            self.solver_order = None


        self.cfg_scale = float(config_dict["cfg_scale"])
        self.width = int(config_dict["width"])
        self.height = int(config_dict["height"])
        self.output_type = config_dict["output_type"]
        self.aesthetic_score = float(config_dict["aesthetic_score"])
        self.negative_aesthetic_score = float(config_dict["negative_aesthetic_score"])

        self.save_latent_simple = config_dict["save_latent_simple"]
        if self.save_latent_simple == "True":
            self.save_latent_simple = True
            print("use callback save_latent_simple")
        else:
            self.save_latent_simple = False

        self.save_latent_overstep = config_dict["save_latent_overstep"]
        if self.save_latent_overstep == "True":
            self.save_latent_overstep = True
            print("use callback save_latent_overstep")
        else:
            self.save_latent_overstep = False

        self.use_callback = False
        if self.save_latent_simple or self.save_latent_overstep:
            self.use_callback = True

        if (self.save_latent_simple and self.save_latent_overstep):
            raise ValueError("save_latent_simple and save_latent_overstep cannot be set at the same time")

        if self.model_mode == "t2i":
            if self.LORA_FLAG:
                self.base = self.preprepare_model_withLoRAandQuantize()
            else:
                self.base = self.preprepare_model()
        elif self.model_mode == "i2i":
            if self.LORA_FLAG:
                self.base = self.preprepare_img2img_model_withLoRAandQuantize()
            else:
                self.base = self.preprepare_img2img_model()
        else:
            raise ValueError("model_mode is only 't2i' or 'i2i'.")


蚭定ファむルを読み蟌んで、クラスアトリビュヌトに蚭定しおいたす。たたLoRAやControlNetなど、堎合によっおは耇数読み蟌たれる可胜性のある蚭定などが、幟぀読み蟌む必芁があるかなどを取埗しお、別メ゜ットで必芁はフラグなどを立おおいたす。

最埌に、preprepare_modelにお、モデルのダりンロヌド、読み蟌み、量子化などを実斜しおいたす。
前回の蚘事で蚘茉したしたが、2024幎9月11日珟圚、Quantoによる量子化ずLoRAを䜵甚するず゚ラヌが出おしたう問題があるようです。
https://github.com/huggingface/diffusers/issues/9270

埓っお、LoRAを利甚するかどうかで、量子化をするかしないかのモデルロヌドを倉えおいたす。
たた、Text to ImageずImage to Imageでは利甚するPipelineが違うため、それ次第でもモデルロヌドが違いたす。

FLUX.1モデルの読み蟌みImage to Image without LoRA

Text to Imageに関しおは前回の蚘事をご芧ください。

preprepare_modelメ゜ッド
class FLUX:
・・・
    def preprepare_img2img_model(self):
        if not self.SINGLE_FILE_FLAG:
            transformer = FluxTransformer2DModel.from_pretrained(
                self.base_model_path,
                subfolder="transformer",
                torch_dtype=torch.float16
            )
            print("transformer quantizing")
            quantize(transformer, weights=qfloat8)
            freeze(transformer)
            print("loaded transformer")

            text_encoder_2 = T5EncoderModel.from_pretrained(
                self.base_model_path,
                subfolder="text_encoder_2",
                torch_dtype=torch.float16
            )
            print("text_encoder_2 quantizing")
            quantize(text_encoder_2, weights=qfloat8)
            freeze(text_encoder_2)
            print("loaded text_encoder_2")

            base = FluxImg2ImgPipeline.from_pretrained(
                self.base_model_path, 
                transformer=transformer, 
                text_encoder_2=text_encoder_2,
                torch_dtype=torch.float16
                )

            print("loaded base model")
        else:
            base = FluxImg2ImgPipeline.from_pretrained(self.base_model_path, torch_dtype=torch.float16)
            print("transformer quantizing")
            quantize(base.transformer, weights=qfloat8)
            freeze(base.transformer)
            print("text_encoder_2 quantizing")
            quantize(base.text_encoder_2, weights=qfloat8)
            freeze(base.text_encoder_2)

            print("loaded base model")

        print("cpu offloading")
        base.enable_model_cpu_offload()

        lora_adapter_name_list = []
        lora_adapter_weights_list = []
        if self.LORA_FLAG:
            for i in range(self.lora_nums):
                if self.lora_repo_list[i] is not None:
                    base.load_lora_weights(
                        pretrained_model_name_or_path_or_dict = self.lora_repo_list[i], 
                        weight_name=self.lora_path_list[i],
                        adapter_name=f"lora{i}")
                else:
                    base.load_lora_weights(self.lora_path_list[i], adapter_name=f"lora{i}")
                lora_adapter_name_list.append(f"lora{i}")
                lora_adapter_weights_list.append(self.lora_scale_list[i])
            if self.lora_nums > 1:
                base.set_adapters(lora_adapter_name_list, adapter_weights=lora_adapter_weights_list)

            print("finish lora settings")

        if self.select_solver == "DPM":
            if self.solver_order is not None:
                base.scheduler = DPMSolverMultistepScheduler.from_config(
                        base.scheduler.config,
                        use_karras_sigmas=self.use_karras_sigmas,
                        Algorithm_type =self.scheduler_algorithm_type,
                        solver_order=self.solver_order,
                        )

            else:
                base.scheduler = DPMSolverMultistepScheduler.from_config(
                        base.scheduler.config,
                        use_karras_sigmas=self.use_karras_sigmas,
                        Algorithm_type =self.scheduler_algorithm_type,
                        )
        elif self.select_solver == "LCM":
            base.scheduler = LCMScheduler.from_config(base.scheduler.config)
        elif self.select_solver == "Eulera":
            base.scheduler = EulerAncestralDiscreteScheduler.from_config(base.scheduler.config)
        elif self.select_solver == "FMEuler":
            base.scheduler = FlowMatchEulerDiscreteScheduler.from_config(base.scheduler.config)
        else:
            raise ValueError("select_solver is only 'DPM' or 'LCM' or 'Eulera' or 'FMEuler'.")


        return base

基本的には前回の蚘事の内容ず同じです。

ただし利甚するPipeLineがFluxPipelineではなく、FluxImg2ImgPipelineになりたす。
LoRAを利甚するか、もしくは単䞀のsafetensorファむルからモデルを読み蟌むのか、どのスケゞュヌラを利甚するのかなど、蚭定ファむルに則っお䞊から順番にモデルを構築しおいきたす。

FLUX.1による画像生成

generate_imageメ゜ッド
class FLUX:
・・・

    def generate_image(self, prompt, neg_prompt = None, c_image_path = None, i_image_path = None, seed = None, controlnet_conditioning_scale = [1.0], temp_strength = None):
        def decode_tensors(pipe, step, timestep, callback_kwargs):
            if self.save_latent_simple:
                callback_kwargs = decode_tensors_simple(pipe, step, timestep, callback_kwargs)
            elif self.save_latent_overstep:
                callback_kwargs = decode_tensors_residual(pipe, step, timestep, callback_kwargs)
            else:
                raise ValueError("save_latent_simple or save_latent_overstep must be set")
            return callback_kwargs


        def decode_tensors_simple(pipe, step, timestep, callback_kwargs):
            latents = callback_kwargs["latents"]
            imege = None
            if self.save_latent_simple:
                image = latents_to_rgb_vae(latents,pipe)
            else:
                raise ValueError("save_latent_simple is not set")
            gettime = time.time()
            formatted_time_human_readable = time.strftime("%Y%m%d_%H%M%S", time.localtime(gettime))
            image.save(f"./outputs/latent_{formatted_time_human_readable}_{step}_{timestep}.png")

            return callback_kwargs

        def decode_tensors_residual(pipe, step, timestep, callback_kwargs):
            latents = callback_kwargs["latents"]
            if step > 0:
                residual = latents - self.last_latents
                goal = self.last_latents + residual * ((self.last_timestep) / (self.last_timestep - timestep))
                #print( ((self.last_timestep) / (self.last_timestep - timestep)))
            else:
                goal = latents

            if self.save_latent_overstep:
                image = latents_to_rgb_vae(goal,pipe)
            else:
                raise ValueError("save_latent_overstep is not set")

            gettime = time.time()
            formatted_time_human_readable = time.strftime("%Y%m%d_%H%M%S", time.localtime(gettime))
            image.save(f"./outputs/latent_{formatted_time_human_readable}_{step}_{timestep}.png")

            self.last_latents = latents
            self.last_step = step
            self.last_timestep = timestep

            if timestep == 0:
                self.last_latents = None
                self.last_step = -1
                self.last_timestep = 100

            return callback_kwargs

        def latents_to_rgb_vae(latents,pipe):

            latents = pipe._unpack_latents(latents, self.height, self.width, pipe.vae_scale_factor)
            latents = (latents / pipe.vae.config.scaling_factor) + pipe.vae.config.shift_factor

            image = pipe.vae.decode(latents, return_dict=False)[0]
            image = pipe.image_processor.postprocess(image, output_type=self.output_type)

            return FluxPipelineOutput(images=image).images[0]


        def load_image_path(image_path):
            # もし image_path が str ならファむルパスずしお画像を読み蟌む
            if isinstance(image_path, str):
                image = load_image(image_path)
                print("Image loaded from file path.")
            # もし image_path が PIL むメヌゞならそのたた䜿甚
            elif isinstance(image_path, Image.Image):
                image = image_path
                print("PIL Image object provided.")
            # もし image_path が Torch テン゜ルならそのたた䜿甚
            elif isinstance(image_path, torch.Tensor):
                image = image_path.unsqueeze(0)
                image = image.permute(0, 3, 1, 2)
                image = image/255.0
                print("Torch Tensor object provided.")
            else:
                raise TypeError("Unsupported type. Provide a file path, PIL Image, or Torch Tensor.")

            return image

        def find_file_with_extension(image_path, i):
            # パタヌンに䞀臎するファむルを怜玢
            file_pattern = f"{image_path}/{i}.*"
            matching_files = glob.glob(file_pattern)

            # マッチするファむルが存圚する堎合、そのファむルのパスを返す
            if matching_files:
                # 䟋: ./image_path/0.png のような完党なファむルパスが取埗される
                return matching_files[0]
            else:
                # マッチするファむルがない堎合
                raise FileNotFoundError(f"No file found matching pattern: {file_pattern}")
                return None

        if seed is not None:
            self.generator = torch.Generator(device=self.device).manual_seed(seed)
        if temp_strength is not None:
            self.strength = temp_strength

        control_image_list = []
        if self.use_controlnet and self.model_mode == "t2i":
            print("use controlnet mode: ",self.control_modes_number_list)
            for i in self.control_modes_number_list:
                if c_image_path is None:
                    raise ValueError("when use controlnet ,control_image_path must be set")
                control_image_name = load_image_path(find_file_with_extension(c_image_path, i))
                control_image = load_image_path(control_image_name)
                control_image_list.append(control_image)
        
        init_image = None
        if self.model_mode == "i2i":
            print("use i2i mode")
            if i_image_path is None:
                raise ValueError("when use i2i mode, init_image_path must be set")
            init_image = load_image_path(i_image_path)


        lora_weight_average = 0
        if self.LORA_FLAG:
            print("use LoRA")
            lora_weight_average = sum(self.lora_scale_list) / len(self.lora_scale_list)
            for word in self.lora_trigger_word_list:
                if (word is not None) and (word != "None"):
                    prompt = prompt + ", " + word

        image = None
        if self.model_mode == "i2i":
            if self.use_callback:
                if self.LORA_FLAG:
                    image = self.base(
                        prompt=prompt,
                        image=init_image, 
                        strength= self.strength,
                        guidance_scale=self.cfg_scale,
                        num_inference_steps=self.n_steps,
                        output_type=self.output_type,
                        width = self.width,
                        height = self.height,
                        generator=self.generator,
                        callback_on_step_end=decode_tensors,
                        callback_on_step_end_tensor_inputs=["latents"],
                        joint_attention_kwargs={"scale": lora_weight_average},
                        ).images[0]
                #LORAを利甚しない堎合
                else:
                    image = self.base(
                        prompt=prompt,
                        image=init_image, 
                        strength= self.strength,
                        guidance_scale=self.cfg_scale,
                        num_inference_steps=self.n_steps,
                        output_type=self.output_type,
                        width = self.width,
                        height = self.height,
                        generator=self.generator,
                        callback_on_step_end=decode_tensors,
                        callback_on_step_end_tensor_inputs=["latents"],
                        ).images[0]
            #latentを保存しない堎合
            else:
                if self.LORA_FLAG:
                    image = self.base(
                        prompt=prompt,
                        image=init_image, 
                        strength= self.strength,
                        guidance_scale=self.cfg_scale,
                        num_inference_steps=self.n_steps,
                        output_type=self.output_type,
                        width = self.width,
                        height = self.height,
                        generator=self.generator,
                        joint_attention_kwargs={"scale": lora_weight_average},
                        ).images[0]
                # LORAを利甚しない堎合
                else:
                    image = self.base(
                        prompt=prompt,
                        image=init_image, 
                        strength= self.strength,
                        guidance_scale=self.cfg_scale,
                        num_inference_steps=self.n_steps,
                        output_type=self.output_type,
                        width = self.width,
                        height = self.height,
                        generator=self.generator
                        ).images[0]

        #t2iモヌドの堎合
        elif self.model_mode == "t2i":
            if self.use_callback:
                if self.LORA_FLAG:
                    if self.use_controlnet:
                        image = self.base(
                            prompt=prompt,
                            control_image=control_image_list,
                            control_mode=self.control_modes_number_list,
                            guidance_scale=self.cfg_scale,
                            controlnet_conditioning_scale=controlnet_conditioning_scale,
                            num_inference_steps=self.n_steps,
                            output_type=self.output_type,
                            width = self.width,
                            height = self.height,
                            generator=self.generator,
                            callback_on_step_end=decode_tensors,
                            callback_on_step_end_tensor_inputs=["latents"],
                            joint_attention_kwargs={"scale": lora_weight_average},
                            ).images[0]
                    else:
                        image = self.base(
                            prompt=prompt,
                            guidance_scale=self.cfg_scale,
                            num_inference_steps=self.n_steps,
                            output_type=self.output_type,
                            width = self.width,
                            height = self.height,
                            generator=self.generator,
                            callback_on_step_end=decode_tensors,
                            callback_on_step_end_tensor_inputs=["latents"],
                            joint_attention_kwargs={"scale": lora_weight_average},
                            ).images[0]
                #LORAを利甚しない堎合
                else:
                    if self.use_controlnet:
                        image = self.base(
                            prompt=prompt,
                            control_image=control_image_list,
                            control_mode=self.control_modes_number_list,
                            guidance_scale=self.cfg_scale,
                            controlnet_conditioning_scale=controlnet_conditioning_scale,
                            num_inference_steps=self.n_steps,
                            output_type=self.output_type,
                            width = self.width,
                            height = self.height,
                            callback_on_step_end=decode_tensors,
                            callback_on_step_end_tensor_inputs=["latents"],
                            generator=self.generator
                            ).images[0]
                    else:
                        image = self.base(
                            prompt=prompt,
                            guidance_scale=self.cfg_scale,
                            num_inference_steps=self.n_steps,
                            output_type=self.output_type,
                            width = self.width,
                            height = self.height,
                            callback_on_step_end=decode_tensors,
                            callback_on_step_end_tensor_inputs=["latents"],
                            generator=self.generator
                            ).images[0]
            #latentを保存しない堎合
            else:
                if self.LORA_FLAG:
                    if self.use_controlnet:
                        image = self.base(
                            prompt=prompt,
                            control_image=control_image_list,
                            control_mode=self.control_modes_number_list,
                            guidance_scale=self.cfg_scale,
                            controlnet_conditioning_scale=controlnet_conditioning_scale,
                            num_inference_steps=self.n_steps,
                            output_type=self.output_type,
                            width = self.width,
                            height = self.height,
                            generator=self.generator,
                            joint_attention_kwargs={"scale": lora_weight_average},
                            ).images[0]
                    else:
                        image = self.base(
                            prompt=prompt,
                            guidance_scale=self.cfg_scale,
                            num_inference_steps=self.n_steps,
                            output_type=self.output_type,
                            width = self.width,
                            height = self.height,
                            generator=self.generator,
                            joint_attention_kwargs={"scale": lora_weight_average},
                            ).images[0]

                # LORAを利甚しない堎合
                else:
                    if self.use_controlnet:
                        image = self.base(
                            prompt=prompt,
                            control_image=control_image_list,
                            control_mode=self.control_modes_number_list,
                            guidance_scale=self.cfg_scale,
                            controlnet_conditioning_scale=controlnet_conditioning_scale,
                            num_inference_steps=self.n_steps,
                            output_type=self.output_type,
                            width = self.width,
                            height = self.height,
                            generator=self.generator
                            ).images[0]
                    else:
                        image = self.base(
                            prompt=prompt,
                            guidance_scale=self.cfg_scale,
                            num_inference_steps=self.n_steps,
                            output_type=self.output_type,
                            width = self.width,
                            height = self.height,
                            generator=self.generator
                            ).images[0]
        else:
            raise ValueError("model_mode is only 't2i' or 'i2i'.")


        return image

ここでは、FLUXによる画像生成を行っおいたす。
こちらも基本的に前回の蚘事ず同じですが、Image to Imageの堎合は、若干__call__メ゜ッドの匕数が倉わりたす。

LoRAを導入し、さらに生成途䞭の画像を出力する堎合は、䞋蚘が実行されたす。
䞋蚘では、蚭定ファむルにお蚘茉されおいる蚭定ずプロンプト、そしおLoRAの蚭定ず、参照画像の画像デヌタ、ノむズの付䞎率、そしお、生成途䞭の画像を出力するためのコヌルバック関数を蚭定しおいたす。

if self.model_mode == "i2i":
    if self.use_callback:
        if self.LORA_FLAG:
            image = self.base(
                prompt=prompt,
                image=init_image, 
                strength= self.strength,
                guidance_scale=self.cfg_scale,
                num_inference_steps=self.n_steps,
                output_type=self.output_type,
                width = self.width,
                height = self.height,
                generator=self.generator,
                callback_on_step_end=decode_tensors,
                callback_on_step_end_tensor_inputs=["latents"],
                joint_attention_kwargs={"scale": lora_weight_average},
                ).images[0]

たずめ2回目

ここたで、実装コヌドの解説をしおいきたした。
基本的にはFLUXは前回の蚘事で説明した通りですが、今回はそこにImage to ImageのPipelineを導入したした。

ここたで読んでくださっお、本圓にありがずうございたした

Discussion