見出し画像

ControlNet - CannyをLayered Diffusion Pipelineで使ってみる

最近、ControlNetをLayered Diffusion Pipelineでサポートしたので、使い方の紹介と、これを利用した実験を少しやってみました。また一緒に、Textual Inversionも利用可能になったので、その使い方も合わせて紹介します。

Layered Diffusion Pipelineとは、私が自分用に開発しているStable Diffusionを使って画像生成するための実行環境です。基本的な使い方は、次の記事を参照してください。

また、その他の関連記事は、こちらのマガジンにまとめてあります。

はじめに

ControlNetとは?

ControlNetとは、Lvmin Zhangが提案したStable DiffusionをFine Tuningする手法のことです。この手法は、画像生成の中心となるU-Net(ノイズ除去)モデルに対して、別の画像による追加の条件を加えることを可能にします。

ControlNetは4月14日現在で、安定版のv1.0と開発版のv1.1があります。本記事ではv1.0を用いています。モデルデータは次のHuggingFaceのリポジトリからダウンロードできます。

本記事では、この中でも、Cannyという画像のエッジを検出するアルゴリズムで生成された、エッジだけで描かれた線画のような画像を追加の条件にして、元の画像と同じような形状の画像を生成するControlNetを使います。

Textual Inversionとは?

Textual Inversionとは、Rinon Galらによって提案された画像生成モデルをFine Tuningする手法のことです。この手法は、画像生成のテキスト入力を処理するText Encoderに対して、新しい語彙を後から追加して、新しい概念を学習させることを可能にします。以下はHuggingFaceに掲載されているdiffusersのドキュメントです。

本記事では、この手法をネガティブプロンプトに応用したEasyNegativeというモデルを使用しています。

実験準備

実験用の画像ファイル

実験には、エッジの検出をするための元画像が必要です。今回は、Pixabayというフリーの画像素材サイトから、次の写真を選びました。

https://pixabay.com/images/id-1556411/

これを、実験しやすいように正方形に加工して、実験用の画像としました。

実験用の画像

実行環境の初期化

実験は、Google Colabで行っています。まず、次のように実行環境を初期化します。

!pip install diffusers transformers scipy accelerate xformers safetensors omegaconf pytorch_lightning opencv-python
!wget 'https://raw.githubusercontent.com/nanashi161382/unstable_diffusion/main/pipeline_layered_diffusion.py'
from pipeline_layered_diffusion import *
from diffusers import ControlNetModel

モデルをダウンロードします。画像生成モデルには、7th_anime_v3_Aを使いました。

!wget https://huggingface.co/syaimu/7th_Layer/resolve/main/7th_anime_v3/7th_anime_v3_A.safetensors
!mkdir sd-canny
!wget https://huggingface.co/lllyasviel/sd-controlnet-canny/resolve/main/config.json \
  -P sd-canny
!wget https://huggingface.co/lllyasviel/sd-controlnet-canny/resolve/main/diffusion_pytorch_model.safetensors \
  -P sd-canny
!wget https://huggingface.co/datasets/gsdf/EasyNegative/resolve/main/EasyNegative.safetensors

最後に、パイプラインを初期化します。

sd_canny = ControlNetModel.from_pretrained("sd-canny")
pipe = LayeredDiffusionPipeline().ConnectCkpt(
    "7th_anime_v3_A",
    checkpoint_path="7th_anime_v3_A.safetensors",
    embeddings=TextualInversion().Add("EasyNegative", "EasyNegative.safetensors"))

sd_cannyがControlNetのCannyモデルです。ローカルのsd-cannyディレクトリにダウンロードしたモデルデータを読み込んでいます。

embeddings引数で指定したものがTextual InversionのEasyNegativeです。追加する単語とダウンロードしたモデルデータを一緒に指定しています。

Cannyによるエッジの検出

OpenCV

Cannyによるエッジの検出には、OpenCVというライブラリを使用します。例えば、次のように使うことができます。

import cv2
img = cv2.imread("input.png")
canny_img = cv2.Canny(img, 100, 300)
cv2.imwrite("canny.png", canny_img)
Cannyによるエッジの検出

cv2.Canny()に与えた引数の数字を変えると、エッジの検出の感度を変えることができます。

GradationCanny

Layered Diffusion Pipelineには、エッジ検出の感度を変えながら、弱いエッジは暗い灰色に、強いエッジは明るい灰色になるように合成した画像を返す、GradationCanny()という関数が用意されています。次のように使います。

canny_img = GradationCanny(degree=1.0)("input.png")
canny_img.save("canny.png")

この関数には、degreeという引数があり、この値を変えることで、弱いエッジの明るさを変えることができます。値が小さいほど弱いエッジが明るくなり、大きいほど暗くなります。言い換えると、値が大きいほどエッジの強弱の差がより強調されるようになります。

以下に例を挙げます。degreeが1.0の画像と5.0の画像の比較です。

degree=1.0
degree=5.0

ControlNetなしの画像生成

まずは、ControlNetなしで画像生成をしてみます。

t2i(text-to-image)

次のスクリプトを使って、テキストのみから画像を生成してみました。

プロンプトには、実験用の画像を簡単な英語で説明したものを使用しました。訳は「ティーンエイジャーの女の子のソフトボール選手がグローブと赤いシャツを着ている」という意味になります。

ネガティブプロンプトには、Textual Inversionで導入した"EasyNegative"のみを使用しています。

seed = 545452271  # ランダムなシードを選んで固定する

image = pipe(
    num_steps=30,
    size=(512, 512),
    rand_seed=seed,
    iterate=Layer(
        prompt = "teenage girl softball player with a glove and a red shirt",
        negative_prompt = "EasyNegative",
        cfg_scale=9,
    ),
)
t2iで生成

i2i(image-to-image)

次に、i2i(image-to-image)で、実験用の画像を下絵にして画像生成してみます。そのためには、上のスクリプトのpipeに次の引数を追加します。

    initialize=ByImage("input.png", strength=0.7),
i2iで生成

ControlNetなしでも、i2iだけでかなり近い画像が生成されています。

strength値を小さくすれば、もっと元画像に近い画像を生成することができますが、同時にイラストとしての自然さを失うことになります。

元画像と生成画像の違いはいろいろと挙げられますが、ここでは特に次の3点を挙げておきたいと思います。

  • 歩いているか、止まっているか

  • 視線が下を見ているか、前を見ているか

  • 髪が上に跳ねているか

これらの違いは、画素としては小さな違いですが、人に与える印象は比較的大きなものとなるという点で共通しています。

ControlNetありのt2i(text-to-image)

ControlNetのパラメータ

下絵を使わずに、テキストとControlNetのみを使って画像生成します。ControlNetのモデルは前述のようにCannyを使い、エッジ検出にはGradationCannyを用います。スクリプトは次のようになります。

seed = 545452271  # ランダムなシードを選んで固定する

degree = 1.0  # エッジ検出の強弱
pre_scale = 1.0  # ControlNetの入力に対するの倍率
post_scale = 1.0  # ControlNetの出力に対するの倍率

image = pipe(
    num_steps=30,
    size=(512, 512),
    rand_seed=seed,
    iterate=Layer(
        prompt = "teenage girl softball player with a glove and a red shirt",
        negative_prompt = "EasyNegative",
        cfg_scale=9,
    ),
    controlnet=ControlNet().Add(
        sd_canny, "input.png", pre_scale=pre_scale, post_scale=post_scale,
        detector=GradationCanny(degree)
    ),
)

controlnet引数にControlNetの設定を追加します。ここでは3つのパラメータが使用されています。1つ目は、GradationCannyに対するdegreeです。これについては前述しまた。

2つ目と3つ目は、pre_scaleとpost_scaleです。この2つは、ControlNetの共通のパラメータです。それぞれのパラメータの図示すると次のようになります。

ControlNet - Canny のパラメーター

degreeのみを変化させる

まずは、pre_scaleとpost_scaleを1.0に固定にして、degreeのみを変えて比較してみます。degreeは1, 3, 5の3通りを試します。左はGradationCannyによって検出されたエッジ画像で、右は生成された画像です。

また、最上段には比較用に元画像とi2iの画像を掲載します。

degreeのみを変化させる

degree=1.0が最も元の画像の形状をよく維持しています。特に、上で指摘した3点がはっきりと再現しているのが分かります。しかし、画像がシルエットになってしまい、イラストとしての質が犠牲になっています。

逆にdegree=5.0はイラストとして最も破綻なく仕上がっていると思います。形状も一定程度は維持していますが、上で指摘した3点の内、視線と髪型の2点は再現されていません。また、シャツのロゴが消えて代わりにしわになってしまっています。

興味深いことに、太ももの数字は21から22に変化してしまいました。

pre_scaleとpost_scaleを変化させる

次に、pre_scaleとpost_scaleを変化させます。今回は、degree=1.0と5.0の2つだけを使います。上段から順に、パラメータの組み合わせは次のようにします。

  • pre_scale, post_scale = 1.0, 1.0

  • pre_scale, post_scale = 0.5, 1.0

  • pre_scale, post_scale = 1.0, 0.5

また前と同様に、最上段には比較用に元画像とi2iの画像を掲載します。

pre_scaleとpost_scaleを変化させる

scaleを小さくすることで、pre_scaleもpost_scaleもControlNetの影響を小さくすることができましたが、影響の出方には差があります。特に、GradationCannyのdegree=1.0の影響がより顕著です。

pre_scaleを0.5に減らしたもの(3段目)は、生成画像がControlNetの影響で大きく変化しています。これは、左と右の画像の差が大きいことから推測されます。しかし、エッジ画像の影響は少なくなっており、上で指摘した3点の特徴が維持されなくなっています。

post_scaleを0.5に減らしたもの(4段目)は、pre_scaleの時とは異なり、エッジ画像の特徴を比較的維持していることが分かります。特に、degree=1.0の画像は、歩行が維持されているほか、ロゴも残っています。しかし、左右の画像の差は小さくなっています。

ControlNetありのi2i(image-to-image)

degreeのみを変化させる

t2iと同様に、まずはdegreeのみを変化させます。今回はエッジ画像ではなく、t2iとi2iを並べて表示します。

i2iでdegreeを変化させる

下絵が存在することで、生成画像がより元画像に近づいたことが見て取れます。特に顕著なのが背景ですが、上に挙げた3点の特徴(歩行、視線、髪跳ね)や色合いの再現度も高くなっています。

pre_scaleとpost_scaleを変化させる

ここでは、degree=1.0のみに絞って、t2iとi2iを並べて比較してみます。並び順は上記t2iの時と同じです。

i2iでpre_scaleとpost_scaleを変化させる

ここでも、これまで観察された事柄が同様に見て取れます。

まとめ

  • ControlNetとTextual Inversionの使い方を紹介しました。

  • ControlNet - Cannyのパラメータを説明しました。

  • パラメータを変化させて画像生成したサンプル画像を紹介しました。

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