3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

画像処理・画像解析Advent Calendar 2023

Day 15

instruct-pix2pixで画像を指示した通り変更したり

Last updated at Posted at 2024-07-27

この記事ではStable Diffusionの機能の人つであるinstruct-pix2pix及びその派生であるcontrolnet instruct-pix2pixについて説明します。

instruct-pix2pixとは? img2imgとどう違うの?

instruct-pix2pixは画像を指示した通り変更するStable Diffusion機能です。

ただそもそもStable Diffusionには似ているものとしてimg2imgがあります。以前の記事で説明してあります。

img2imgの方は知名度が高くて一般的に使われていますね。だから「どう違うか?img2imgで良くないか?」と疑問に思うでしょう。

確かにinstruct-pix2pixもimg2imgも、ある画像を元にして新しい画像に変更する機能です。それでも変換の仕様は違います。

img2imgはプロンプトに書いた内容の通り「元画像を参考にしてどうやって新しい画像を作る」というのに対し、instruct-pix2pixは「元画像をどうやって改造する」という感じです。

だからinstruct-pix2pixを使ったら全体的に結果は元画像のままでプロンプトに指示された内容との関係がある部分だけ変えられるが、img2imgは全体変えられます。プロンプトの内容と関係ない部分であっても勝手に決められます。

ということで原型を残したまま一部だけ変更したい場合はimg2imgよりinstruct-pix2pixの方がいいと思えます。

もう一つの違いはプロンプトの書き方です。img2imgでは欲しい結果画像の姿を描写するという形でプロンプトを唱えるのに対し、instruct-pix2pixを使う時に「これに変えたい」という書き方です。

例えば少女の画像があってその少女を笑わせたい場合、img2imgは「笑顔をしている少女」と書いたらいいのですが、instruct-pix2pixでは「少女を笑顔にする」みたいな書き方をするのです。

又、一部だけ変えたいというと、inpaintかcontrolnet inpaintという機能があります。これも前回の記事で説明してあります。

それを使うのは一番だと思いますが、inpaintを使うためには修正したい部分を塗ってマスク画像を作っておく必要があります。

それに対し、instruct-pix2pixはマスクなんて必要せず、どこを修正するか自動的に決められてしまいます。例えば顔の表情を変えたい場合顔の部分のマスクをする必要もなく、自動的にどこが顔かわかって適切に修正してくれるのです。凄い便利ですね!

とはいえ、やはり間違えることもありますし、望まない部分も修正される可能性があるので、細かく修正したい場合はまだあまり向いていないかもしれません。

尚、そもそも「pix2pix」というのは敵対的生成ネットワーク(GAN)によって作られた画像生成モデルの名前です。instruct-pix2pixも画像から別の画像を作るというところは同じですが、GANを使っているわけではなく、本来のpix2pixとは違う別のモデルであり、直接関係がないらしいです。

だからinstruct-pix2pixを単にpix2pixと呼んだら紛らわしい気がします。それはJavaScriptをJavaと呼ぶのと同じようなものかもしれません。

controlnet instruct-pix2pixとは

instruct-pix2pixはそもそも2023年1月にtimbrooks等によって発表された機能で、githubに公開してあります。

huggingfaceにもモデルが公開されて、diffusersから簡単に使えます。

しかしその後2023年4月に張呂敏チャン リュミン(lllyasviel)はcontrolnet 1.1を公開して、色々な機能の中でinstruct-pix2pixと同じ機能を持つものも含まれています。ここではtimbrooks氏の従来のinstruct-pix2pixと区別するために「controlnet instruct-pix2pix」と呼びます。

controlnetって、新鮮な機能を導入するだけでなく、以前からあった機能の改善版もしますね。inpaintもそうです。これについて前回のcontrolnet inpaintの記事にも書いてあります。

だからこれはcontrolnet版のinstruct-pix2pixとも言えます。従来のinpaintに対してcontrolnet inpaintがあるのと同じように、従来のinstruct-pix2pixに対してcontrolnet instruct-pix2pixがあります。

そして双方の相違点も同じようなところです。従来のinstruct-pix2pixはinstruct-pix2pix用のモデルだけで実行するのに対し、controlnet instruct-pix2pixは同時にベースとする別のチェックポイントモデルを必要とします。だからcontrolnet instruct-pix2pixはの方が色々調整できて使い勝手がいいです。

とはいえ、使ってみたところ従来のinstruct-pix2pixも随分いいものだと思います。従来のinpaintみたいにアニメ画像が苦手なわけではないらしい。

だからここでは従来のinstruct-pix2pixもcontrolnet instruct-pix2pixも紹介したいです。

instruct-pix2pixを使ってみる

まずは従来のinstruct-pix2pixです。これはStableDiffusionInstructPix2PixPipelineクラスでパイプラインを作成して使うことができます。使い方は大体img2imgと同じです。必要なのは元画像とプロンプトくらい。

今回は例としてこの元画像を使います。

nekomimi_hanasono.jpg

この画像は綺麗な花園ですが、instruct-pix2pixを使って雪の中にしてみます。

import torch
from PIL import Image
from diffusers import StableDiffusionInstructPix2PixPipeline
from translate import Translator

device = 'mps'
pipe = StableDiffusionInstructPix2PixPipeline.from_pretrained(
    'timbrooks/instruct-pix2pix',
    torch_dtype=torch.float16,
    safety_checker=None
).to(device)

seed = 21910
generator = torch.Generator(device=device).manual_seed(seed)
img0 = Image.open('nekomimi_hanasono.jpg')
honyaku = Translator('en','ja').translate
prompt = '雪の中の場面にする'
img = pipe(
    honyaku(prompt),
    image=img0,
    num_inference_steps=20,
    image_guidance_scale=1,
    generator=generator
).images[0]
img.save('nekomimi_hanasono_ip2p.jpg')

結果はこうなります。

nekomimi_hanasono_ip2p.jpg

木も花も殆どそのままで雪を被らせるというのは凄いですね。それに今回はシーンに関するプロンプトなので人物のところに殆ど変更がありません。逆に表情などに関するプロンプトなら背景は殆ど変わりません。

image_guidance_scale

instruct-pix2pixのパイプラインを実行する時にimage_guidance_scaleは大きく影響を与えるパラメータの一つです。これはどれくらい画像を変えるかを決める値です。1以上の値が入れられます。

既定値は1.5ですが、そのまま使ってもあまり元と変わらない画像ができるので普段は1を使うのは一般的らしいです。

違うimage_guidance_scaleの値の結果を比較してみましょう。

import torch
from PIL import Image
from diffusers import StableDiffusionInstructPix2PixPipeline
from translate import Translator
import matplotlib.pyplot as plt

def seisei(igs):
    pipe = StableDiffusionInstructPix2PixPipeline.from_pretrained(
        'timbrooks/instruct-pix2pix',
        torch_dtype=torch.float16,
        safety_checker=None
    ).to(device)
    generator = torch.Generator(device=device).manual_seed(seed)
    
    return pipe(
        honyaku(prompt),
        image=img0,
        num_inference_steps=20,
        image_guidance_scale=igs,
        generator=generator
    ).images[0]

device = 'mps'
img0 = Image.open('nekomimi_hanasono.jpg')
honyaku = Translator('en','ja').translate
prompt = '砂漠の場面にする'
seed = 55573

plt.figure(figsize=[6,9.5],dpi=100)
for i in range(6):
    igs = 1+0.1*i
    plt.subplot(3,2,i+1,title='image_guidance_scale = %.1f'%igs)
    plt.imshow(seisei(igs))
    plt.axis('off')
    torch.mps.empty_cache()

plt.tight_layout(pad=0.5)
plt.savefig('nekomimi_hanasono_igs.jpg')
plt.close()

nekomimi_hanasono_igs.jpg

今回砂漠に変えるというプロンプトだから花も消えますね。

その他にもguidance_scaleは大事なパラメータでもありますが、これはtxt2imgとimg2imgの場合と同じ概念なので割愛します。

一応SDXL版もありますが……

SDXL版と言われるinstruct-pix2pixも公開されています。これは主に768×768ピクセルの画像に使うためらしいです。

しかし使ってみたところあまりいい結果が出なくてがっかりしました。だからここには紹介しません。

使い方はほぼ同じで、パイプラインオブジェクトを作る時にStableDiffusionInstructPix2PixPipelineからStableDiffusionXLInstructPix2PixPipelineに変えるくらいです。

上の例と同じ入力画像(ただしサイズは768×768)と同じプロンプトで試した結果です。望んだ結果とはかけ離れていなますが、これもこれで面白いので一応載せておきます。

yukinoip2pxl21907_g1.5.jpg

controlnet instruct-pix2pixを使ってみる

次はcontrolnet instruct-pix2pixの方を使ってみます。比較するために最初の例と同じ入力画像で同じプロンプトを使います。

controlnet instruct-pix2pixを使う時にベースのチェックポイントモデルが必要なので、今回HimawariMixを使います。

import torch
from PIL import Image
from diffusers import StableDiffusionControlNetPipeline,ControlNetModel,EulerAncestralDiscreteScheduler
from translate import Translator

device = 'mps'
controlnet = ControlNetModel.from_pretrained(
    'lllyasviel/control_v11e_sd15_cip2p',
    torch_dtype=torch.float16
).to(device)
pipe = StableDiffusionControlNetPipeline.from_single_file(
    'stadifmodel/himawarimix_v11.safetensors',
    controlnet=controlnet,
    torch_dtype=torch.float16
).to(device)
pipe.scheduler = EulerAncestralDiscreteScheduler.from_config(pipe.scheduler.config)

seed = 77205
generator = torch.Generator(device=device).manual_seed(seed)
img0 = Image.open('nekomimi_hanasono.jpg')
honyaku = Translator('en','ja').translate
prompt = '雪の中の場面にする'
img = pipe(
    honyaku(prompt),
    image=img0,
    num_inference_steps=25,
    generator=generator
).images[0]
img.save('nekomimi_hanasono_cp2p.jpg')

nekomimi_hanasono_cip2p.jpg

従来のinstruct-pix2pixと比べて変化が大きいです。服も背景も雪の場面に合わせていますね。それでも元の画像の構造は大体そのままです。

ただしランダム性も従来のinstruct-pix2pixより高くて、何度試して色々違う結果ができます。このように花園そのまま雪だけ入れられるという結果もあります。

nekomimi_hanasono_cip2p77218.jpg

色んなプロンプトで試そう

その他にも例えば「炎の中の場面にする」と書いたらこうなります。

honoononaka21901-.jpg

本当に花園が燃えているみたいですね。

「森の中の場面にする」

morinonaka21901.jpg

「山中の場面にする」

sanchuu21901.jpg

「砂漠の場面にする」

nekomimi_hanasono_cp2p砂漠の場面にする.jpg

場所の指定はここまでにしましょう。では試しに「着物姿に着替える」としたら場所は関係なくただ服が変えられるだけになって凄いです。

kimono21903.jpg

controlnet_conditioning_scale

controlnet instruct-pix2pixだけの話ではないのですが、controlnetパイプラインを実行する時にcontrolnet_conditioning_scaleというパラメータがあります。これはコントロール画像の影響の重みを決める大事なパラメータです。既定値は最大値の1です。1より小さい値にしたら入力画像の影響が薄くなります。

controlnet instruct-pix2pixの場合0.6になる時点でもはや元画像の原型が残らずただプロンプトに書いたままの画像になります。

では違うcontrolnet_conditioning_scaleの結果を比較してみましょう。入力は今までと同じ画像で、今回は浜辺にします。

import torch
from PIL import Image
from diffusers import StableDiffusionControlNetPipeline,ControlNetModel,EulerAncestralDiscreteScheduler
from translate import Translator
import matplotlib.pyplot as plt

def seisei(ccs):
    controlnet = ControlNetModel.from_pretrained(
        'lllyasviel/control_v11e_sd15_ip2p',
        torch_dtype=torch.float16
    ).to(device)
    pipe = StableDiffusionControlNetPipeline.from_single_file(
        'stadifmodel/himawarimix_v11.safetensors',
        controlnet=controlnet,
        torch_dtype=torch.float16
    ).to(device)
    pipe.scheduler = EulerAncestralDiscreteScheduler.from_config(pipe.scheduler.config)

    return pipe(
        honyaku(prompt),
        image=img0,
        num_inference_steps=25,
        controlnet_conditioning_scale=ccs,
        generator=torch.Generator(device=device).manual_seed(seed)
    ).images[0]

device = 'mps'
seed = 77799
img0 = Image.open('nekomimi_hanasono.jpg')
honyaku = Translator('en','ja').translate
prompt = '浜辺の場面にする'

plt.figure(figsize=[6,9.5],dpi=100)
for i in range(6):
    ccs = 0.6+0.08*i
    plt.subplot(3,2,i+1)
    plt.imshow(seisei(ccs))
    plt.title('controlnet_conditioning_scale = %.2f'%ccs,size=11)
    plt.axis('off')
    torch.mps.empty_cache()

plt.tight_layout(pad=0.5)
plt.savefig('nekomimi_hanasono_ccs.jpg')
plt.close()

nekomimi_hanasono_ccs.jpg

controlnet_conditioning_scaleが少ないと、少女の姿が消えてただ書いた通りの浜辺の場面になりますね。

感情を変えてみる

controlnet inpaintみたいにcontrolnet instruct-pix2pixも表情を変えるのに使うことができます。結果はcontrolnet inpaintほどよくないが、マスク画像を準備する必要なくすぐ使える分はメリットですね。

今回はこれを元画像に使います。

nekomimi_hanami.jpg

プロンプトは「泣き顔にする」。コードは大体同じでただ画像ファイルの名前とプロンプトを変えるだけなので省略します。

結果はこうなります。

nekomimi_hanami_cip2p.jpg

物凄く苦しそうな泣き方で可哀想です……。

でもやはりcontrolnet inpaintの方がもっと綺麗にできますね。

文字をお洒落にする

controlnet instruct-pix2pixで文字の画像を弄ったら看板やロゴみたいにお洒落に変えることもできそうなので試してみました。

例えばこのように簡単なコードで作成されたただ文字が地味に書かれているだけの画像です。

from PIL import Image, ImageDraw, ImageFont

img = Image.new('RGB',(512,512),(200,200,240))
draw = ImageDraw.Draw(img)
font = ImageFont.truetype('MEIRYOB.TTC',120)
draw.text((15,0),'おいしい',fill=(120,80,90),font=font)
font = ImageFont.truetype('MEIRYOB.TTC',180)
draw.text((85,140),'博多',fill=(220,110,80),font=font)
font = ImageFont.truetype('MEIRYOB.TTC',120)
draw.text((15,350),'ラーメン',fill=(150,80,10),font=font)
img.save('ohara.png')

ohara.png

生成コードはこの記事を参考に。

この画像を入力として、落書きに変えてみます。

import torch
from PIL import Image
from diffusers import StableDiffusionControlNetPipeline,ControlNetModel,EulerAncestralDiscreteScheduler
from translate import Translator

device = 'mps'
controlnet = ControlNetModel.from_pretrained(
    'lllyasviel/control_v11e_sd15_ip2p',
    torch_dtype=torch.float16
).to(device)
pipe = StableDiffusionControlNetPipeline.from_single_file(
    'stadifmodel/himawarimix_v11.safetensors',
    controlnet=controlnet,
    torch_dtype=torch.float16
).to(device)
pipe.scheduler = EulerAncestralDiscreteScheduler.from_config(pipe.scheduler.config)

seed = 21899
generator = torch.Generator(device=device).manual_seed(seed)
img0 = Image.open('ohara.png')
honyaku = Translator('en','ja').translate
prompt = '落書きにさせる'
img = pipe(
    honyaku(prompt),
    image=img0,
    num_inference_steps=20,
    generator=generator
).images[0]
img.save('ohara_rakugaki.jpg')

ohara_rakugaki.jpg

壁に書いたような落書きっぽい文字になりましたね。もっと工夫したら使えそうです。

参考

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?