diffusers（Stable Diffusion）による画像の改造／合成／変換／修正／拡大

# はじめに

この前の入門記事の続きの内容です。

https://qiita.com/phyblas/items/00f750b8277f66fb9b13

前回は主に言葉のプロンプトだけから画像を生成するというtxt2imgの話でしたが、今回はimg2imgをはじめとして画像を既存の画像を元に改造したり変換したり修正したり拡大させたりする機能の話となります。

ここではまず主に：
- img2img
- refiner
- upscale
- inpaint

の話になります。その他にもcontrolnetとipadapterなど関連の機能がありますが、これについて内容が多いので後で別の記事に書きます。

又、「潜在空間」という概念も関わってきて扱い方を知っておいたらいいと思うのでついでにここでちょっと触れてみます。

前回からの続きなのでまず必要なライブラリーがインストールされているという前提で進みます。
- diffusers
- pytorch
- transformer
- translate （一応プロンプトは全部日本語で入力します）


# img2img 〜画像から画像生成〜

img2imgはとある画像があってそれを別の何かの画像に改造や修正をしたい時に使う機能です。

img2imgのパイプラインクラスは`StableDiffusionImg2ImgPipeline`と`StableDiffusionXLImg2ImgPipeline`です。前回説明したtxt2imgで`StableDiffusionPipeline`と`StableDiffusionXLPipeline`を使うのと同じように、img2imgでもSDXLモデルかどうかで使い分けする必要があります。

使い方はtxt2imgと大体似ていますが、入力はただ言葉だけでなく、元画像も必要となります。

例えばアニメ風画像をリアル風にしたい場合です。例としてこの画像を入力として使います。これもStable Diffusionによって生成されたものです。（具体的に言うと[以前の記事で説明したComfyUIでblue_pencil-XLモデル](https://qiita.com/phyblas/items/0c69ac370daa0aa01d15)）

![ginpatsukitsunemimi.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/16463062-0609-afdb-126c-50b299ae8b59.jpeg)

そしてこのリアル風のチェックポイントモデルを使います。

https://civitai.com/models/25494/beautiful-realistic-asians

実装のコードはこんな感じ。
```python
import torch
from PIL import Image
from diffusers import StableDiffusionImg2ImgPipeline,DPMSolverMultistepScheduler
from translate import Translator

device = 'mps'
pipe = StableDiffusionImg2ImgPipeline.from_single_file(
    'stadifmodel/beautifulRealistic_v7.safetensors',
    torch_dtype=torch.float16
).to(device)
pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config)
generator = torch.Generator(device).manual_seed(174)
honyaku = Translator('en','ja').translate
prompt = honyaku('黒髪ツインテイル少女')
img0 = Image.open('ginpatsukitsunemimi.jpg')
img = pipe(
    prompt,
    image=img0,
    guidance_scale=7,
    strength=0.6,
    num_inference_steps=30,
    generator=generator
).images[0]  
img.save("realkurokamitwintail.jpg")    
```

そしてできたのはこのように元画像の構造を保ちながらプロンプトに書いた内容も従うリアルな画像です。

![realkurokamitwintail.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/e1d2444f-03aa-4ada-f532-fdc6c26260aa.jpeg)

まだアニメっぽいって感じですが、これもパラメータ調整やプロンプトやチェックポイントモデル次第です。

ここで一番重要なパラメータは`strength`です。これを変えるだけで結果は全然違います。その他のパラメータは殆どtxt2imgと同じですが、`strength`だけはimg2imgしか使わないので、ここで`strength`の影響について説明します。


## strength 〜変化の強さ〜

`strength`は変化の強さを表すパラメータです。値の範囲は0から1です。0だと元の画像のままで、1だとほぼ完全に元画像を無視するということになります。

又、生成時のステップ数は`num_inference_steps`ではなく、「`num_inference_steps` × `strength`」となります。だから`strength`が小さいと速いです。変化が少ない分、生成時間も短いです。

では違う`strength`の結果を比較してみます。例として今回はこの画像を元画像にします。これは元々[私がpixivに投稿した画像](https://www.pixiv.net/artworks/82494727)です。

![isogashiiteto.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/ff682440-8661-9efb-8a71-b1110c51a6a8.jpeg)

そしてこのアニメ風チェックポイントモデルを使います。

https://civitai.com/models/131611/himawarimix

```python
import torch
from PIL import Image
from diffusers import StableDiffusionImg2ImgPipeline,EulerAncestralDiscreteScheduler
from translate import Translator
import matplotlib.pyplot as plt

def seisei(strength):
    pipe = StableDiffusionImg2ImgPipeline.from_single_file(
        'stadifmodel/himawarimix_v11.safetensors',
        torch_dtype=torch.float16
    ).to(device)
    pipe.scheduler = EulerAncestralDiscreteScheduler.from_config(pipe.scheduler.config)
    generator = torch.Generator(device).manual_seed(seed)
    img = pipe(
        prompt,
        image=img0,
        guidance_scale=8,
        strength=strength,
        num_inference_steps=30,
        generator=generator
    ).images[0]
    return img

device = 'mps'
seed = 11190
honyaku = Translator('en','ja').translate
prompt = honyaku('ずんだ餅を食べている緑髪少女')
img0 = Image.open('isogashiiteto.jpg')

plt.figure(figsize=[6,15.5],dpi=100)
for i in range(10):
    strength = 0.1+i*0.1
    img = seisei(strength)
    plt.subplot(5,2,i+1,title='strength = %.1f'%strength)
    plt.imshow(img)
    plt.axis('off')
    torch.mps.empty_cache()

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

![isogashiiteto_janai.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/e92522d5-831e-1f0c-396c-fa91e9136490.jpeg)


髪の色がどんどん変わっていくのがいいですね。コーヒーも[ずんだ餅](https://ja.wikipedia.org/wiki/ずんだ餅)になっていくのは面白いです。


## guidance_scale 〜プロンプトの重さ〜

`guidance_scale`（CFG scaleとも）はtxt2imgの場合と同じようにプロンプトの重要性を表すパラメータです。`strength`ほどではないが、`guidance_scale`も重要なのでここでも`guidance_scale`の違いの比較もしてみます。

今回の例はリアル写真とアニメ風のチェックポイントモデルを使ってみます。これは博多駅の近くの店の肉ごぼう天うどんです。これをうなぎ丼に改造してみます。

![hktudon.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/01360d8e-cfa7-4fdf-a8a6-d7528d00e5e2.jpeg)

```python
import torch
from PIL import Image
from diffusers import StableDiffusionImg2ImgPipeline,EulerAncestralDiscreteScheduler
from translate import Translator
import matplotlib.pyplot as plt

def seisei(gs):
    pipe = StableDiffusionImg2ImgPipeline.from_single_file(
        'stadifmodel/himawarimix_v11.safetensors',
        torch_dtype=torch.float16
    ).to(device)
    pipe.scheduler = EulerAncestralDiscreteScheduler.from_config(pipe.scheduler.config)
    generator = torch.Generator(device).manual_seed(seed)
    
    img = pipe(
        prompt,
        image=img0,
        guidance_scale=gs,
        strength=0.5,
        num_inference_steps=30,
        generator=generator
    ).images[0]
    return img

device = 'mps'
seed = 111147
honyaku = Translator('en','ja').translate
prompt = honyaku('うなぎ丼')
img0 = Image.open('hktudon.jpg')

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

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

![hktunagidon.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/6a0a9949-4be0-b06d-4774-31bdbf5a1498.jpeg)

うどんからどんぶりに変わりますね。guidance_scaleが大きくなるとうなぎ丼っぽくなっていく気がします。

guidance_scaleが0だとプロンプトを入れないのと同じですが、それでも画像はそれなりに変えられます。ただしどんな画像にするかイメージがはっきりしないせいかぼやっとした感じです。


# refiner 〜モデル合成〜

リファイナー（refiner）というのは元々SDXLモデルと同時に公開された機能です。最初はSDXLモデルと一緒に使うことで画質が上がるという主張でしたが、違いが微妙で必要がないと評価した人も多くて結局その目的で使う人は少ないと思います。

ただしリファイナーはもう一つの用途があります。それは2つのSDXLモデルを合成することです。具体的にはまず「ベースモデル」となる一つのモデルで生成を始めて、途中で「リファイナーモデル」となるもう一つのモデルに渡して生成を続けるのです。

リファイナーとして使うモデルは本来[SD-XL 1.0-refiner](https://huggingface.co/stabilityai/stable-diffusion-xl-refiner-1.0)が推奨されましたが、結局普段使っているSDXLモデルどれもリファイナーとして使うことができます。

リファイナーモデルはSDXL版のimg2imgパイプラインである`StableDiffusionXLPipeline`が使われますが、使い方は普段のimg2imgの時とはちょっと違います。

例として今回はアニメ風モデルをベースモデルとして、リアル風モデルをリファイナーモデルとして使ってみます。

アニメ風モデルの方はこれを使います。

https://civitai.com/models/9409/or-anything-xl

リアル風モデルはこれを使います。

https://civitai.com/models/415107?modelVersionId=462564

```python
import torch
from diffusers import StableDiffusionXLPipeline,StableDiffusionXLImg2ImgPipeline,EulerAncestralDiscreteScheduler
from translate import Translator

device = 'mps'
# ベースモデルのパイプライン
pipe_bs = StableDiffusionXLPipeline.from_single_file(
    '/stadifmodel/AnythingXL_xl.safetensors',
    torch_dtype=torch.float16
).to(device)
pipe_bs.scheduler = EulerAncestralDiscreteScheduler.from_config(pipe_bs.scheduler.config)
# リファイナーモデルのパイプライン
pipe_rf = StableDiffusionXLImg2ImgPipeline.from_single_file(
    'stadifmodel/crystalClearOneVs1_v10.safetensors',
    torch_dtype=torch.float16,
    scheduler=pipe_bs.scheduler # スケジューラーも統一
).to(device)

seed = 111162
generator = torch.Generator(device).manual_seed(seed)
honyaku = Translator('en','ja').translate
prompt = honyaku('猫を抱いている高校制服姿の黒髪少女')
# ベースモデルでまず生成開始
img0 = pipe_bs(
    prompt,
    num_inference_steps=20,
    generator=generator,
    denoising_end=0.4, # ここで途中で生成をやめると指定
    output_type='latent' # 出力を潜在空間と指定
).images
# リファイナーモデルで
img = pipe_rf(
    prompt,
    image=img0,
    num_inference_steps=20,
    generator=generator,
    denoising_start=0.4, # ここで生成を途中から続けると指定
).images[0]
img.save('nekodaku.jpg')
```

![nekodaku111162.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/9626d305-9584-ece7-bef8-5d5cede86a80.jpeg)

このようにリアルとアニメの間の画風ができます。

ここで重要なポイントはベースパイプラインの方の`output_type='latent'`と指定することです。これは出力を「潜在空間」にするという意味です。これについて詳しくは後で説明しますが、まずもう一つ重要なポイントである`denoising_end`を説明します。これは移り変わりポイントを示すパラメータです。そしてリファイナーパイプラインの`denoising_start`もその数値に合わせるのです。値は0から1まで。

例えば0.4を入れると40%までベースモデルで生成して、その後はリファイナーモデルで生成を続けるという過程になります。ここでステップ数（`num_inference_steps`）が20にしているので8と12に分けられます。

数値が小さいとリファイナーモデルの影響が大きくて、逆に数値が1に近づいたら殆どベースモデルだけ使うのと変わらないことになります。

尚`denoising_end`と`denoising_start`はSDXLではない従来のモデルに使っても効果がないので、**この方法はSDXLモデル限定**です。

違う数値の比較をしてみます。
```python
import torch
from diffusers import StableDiffusionXLPipeline,StableDiffusionXLImg2ImgPipeline,EulerAncestralDiscreteScheduler
from translate import Translator
import matplotlib.pyplot as plt

def seisei(se):
    pipe_bs = StableDiffusionXLPipeline.from_single_file(
        'stadifmodel/AnythingXL_xl.safetensors',
        torch_dtype=torch.float16
    ).to(device)
    pipe_bs.scheduler = EulerAncestralDiscreteScheduler.from_config(pipe_bs.scheduler.config)
    
    pipe_rf = StableDiffusionXLImg2ImgPipeline.from_single_file(
        'stadifmodel/crystalClearOneVs1_v10.safetensors',
        torch_dtype=torch.float16,
        scheduler=pipe_bs.scheduler
    ).to(device)
    
    generator = torch.Generator(device).manual_seed(seed)
    img0 = pipe_bs(
        prompt,
        num_inference_steps=20,
        generator=generator,
        denoising_end=se,
        output_type='latent'
    ).images
    
    img = pipe_rf(
        prompt,
        image=img0,
        num_inference_steps=20,
        generator=generator,
        denoising_start=se,
    ).images[0]
    return img

device = 'mps'
seed = 100532
honyaku = Translator('en','ja').translate
prompt = honyaku('花園で兎と遊んでいる高校制服姿の日本人少女')

plt.figure(figsize=[6,12.5],dpi=100)
for i in range(8):
    se = 0.1+i*0.1
    img = seisei(se)
    plt.subplot(4,2,i+1,title='%.1f'%se)
    plt.imshow(img)
    plt.axis('off')
    torch.mps.empty_cache()

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

![usagiasobu.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/df68f564-3568-9849-95ac-b7fc5411b3d6.jpeg)

数値が大きくなるにつれ、リアルからアニメに変わっていっていい感じですね。


# latent 〜潜在空間の変換〜

リファイナーを使う時に`output_type='latent'`というのがあったからついでにこれについてここで説明しておきます。

「latent」というのは潜在空間のことです。実は普段Stable Diffusionが画像を生成する時にまず潜在空間という形で生成してその後「ピクセル空間」つまり我々の馴染んでいる画像に変換するのです。

そんな仕組みになっている理由はその方が効果的だからです。潜在空間の画像はピクセル空間よりずっと小さいので計算量も小さいです。そのため潜在空間で生成してその後変換するという手段を取ることで生成が高速化できるわけです。

だからdiffusersでは生成している途中の画像は潜在空間で、終わった後自動的にピクセル空間に変換されるという仕組みになっています。

しかし`output_type='latent'`を指定することで生成が終わった後ピクセル空間に変換せずに潜在空間のままで出力することになります。

リファイナーを使う時にベースパイプラインの出力はまだ生成の途中だから変換する必要がなく潜在空間のままを使うのです。

では具体的に潜在空間というのはどんなものなのかわかるためにとりあえずtxt2imgで普通に画像を生成するが、出力は潜在空間にしてみましょう。
```python
import torch
from diffusers import StableDiffusionPipeline,EulerAncestralDiscreteScheduler
from translate import Translator

model_file = 'stadifmodel/himawarimix_v11.safetensors'
device = 'mps'
seed = 24179
pipe = StableDiffusionPipeline.from_single_file(
    model_file,
    torch_dtype=torch.float16
).to(device)
pipe.scheduler = EulerAncestralDiscreteScheduler.from_config(pipe.scheduler.config)
generator = torch.Generator(device).manual_seed(seed)
honyaku = Translator('en','ja').translate
prompt = '夜の商店街で油を売っている水色髪メイド'
img_latent = pipe(
    honyaku(prompt),
    num_inference_steps=20,
    generator=generator,
    output_type='latent'
    ).images

print(img_latent.shape) # torch.Size([1, 4, 64, 64])
```

このように潜在空間は4×64×64ピクセルの画像です。本来出力される画像のサイズは512×512だから潜在空間では1/8のサイズだとわかりますね。だから生成する時に`width`と`height`の指定は8に割り切れる数値でないといけないのです。

潜在空間の画像はまだそのまま使える画像ではないが、試しに無理矢理画像として表示することも一応できてしまいます。
```python
import numpy as np
import matplotlib.pyplot as plt

imgl = np.float32(img_latent[0].cpu()).transpose(1,2,0)
plt.figure(figsize=[6,6],dpi=100)
plt.imshow((imgl-imgl.min())/(imgl.max()-imgl.min()))
plt.tight_layout()
plt.savefig('aburamaid.png')
plt.close()
```

![aburamaid.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/782db2d2-7a48-55ea-e560-6ee692f947d4.png)

とりあえずこのように何かぼやっと形が見えますね。

こんなものをピクセル空間の完成品に変換するのは**VAEデコーダー**です。入門の記事でもちょっとVAEのことを説明しましたが、このVAEはStable Diffusionモデルの重要な一部です。普段チェックポイントモデルの中でVAEが埋め込まれています。diffusersで生成する時にパイプラインの中でVAEが使われて最後の画像に変換するのです。

今回VAEを使わずに潜在空間のまま出力してしまったが、自分でVAEを呼び出して変換させることもできます。このようにします。
```python
from PIL import Image

img1 = pipe.vae.decode(img_latent/pipe.vae.config.scaling_factor)
img1 = img1.sample[0].detach().cpu().numpy().transpose(1,2,0)
img1 = Image.fromarray(np.uint8(np.clip(img1*0.5+0.5,0,1)*255))
img1.save('aburamaid.jpg')
```

そしてこのような完成した画像ができます。もう普段生成した画像と同じですね。

![aburamaid.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/822fab36-9c56-15c5-1ae3-846f0b3db1c0.jpeg)

基本的に`decode`を呼び出すのはここのポイントですが、それだけでなく他の計算も必要なので自分で変換のコードを書くのはちょっと面倒ですね。

VAEは**エンコーダー**と**デコーダー**に分けられています。潜在空間からピクセル空間に変換するのはVAEのデコーダー部分ですが、逆にピクセル空間から潜在空間に変換する時にエンコーダー部分が使われます。

実際にimg2imgパイプラインは入力した画像を自動的にVAEエンコーダーによって潜在空間に変換するので、潜在空間で入れてもピクセル空間で入れてもいいようにされています。

普通img2imgとして使う時に実は中でまず入力した画像が潜在空間に変換されるから自分で変換する必要は普段ないと思いますが、あえて自分で変換したいならこのように書きます。
```python
imgl2 = torch.HalfTensor(np.array(img1).transpose(2,0,1)[None,:]/255).to(device)
imgl2 = pipe.vae.encode(imgl2).latent_dist.sample()*pipe.vae.config.scaling_factor
```


# upscale 〜綺麗に拡大する〜

小さな画像を大きな画像にアップスケールするのもStable Diffusionのよく使われる機能の一つです。普段の画像編集ソフトウェアで画像を大きくしたらできた画像は破綻しますが、機械学習モデルで変換することで綺麗な拡大画像ができます。Stable Diffusionもそんなことができるモデルの一つです。

Stable Diffusionでは主にアップスケールに使うモデルが2つあります。
- x4 upscaler
- x2 latent upscaler

それぞれの使い方を紹介します。


## x4 upscaler 〜元画像から4倍拡大〜

x4 upscalerは画像を4倍サイズにするアップスケーラーのモデルです。

https://huggingface.co/stabilityai/stable-diffusion-x4-upscaler

このモデルはhuggingfaceに公開されているので、このリンクで直接ダウンロードすることもできますが、diffusersで使う場合直接ダウンロードせずに`.from_pretrained`で自動ダウンロードすることもできます。

このモデルを使うために`StableDiffusionUpscalePipeline`というクラスでパイプラインを作ります。

今回は例としてこの画像を使います。サイズは240×240ピクセルで、アップスケールしたら960×960となります。このアップスケーラーは物凄くメモリーを消耗するのでこんな小さい画像でないときついです。

![s240.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/152f4dc9-a2be-1e2c-8330-828bc53cd148.jpeg)


```python
import torch
from PIL import Image
from diffusers import StableDiffusionUpscalePipeline
from translate import Translator

honyaku = Translator('en','ja').translate
device = 'mps'
pipe = StableDiffusionUpscalePipeline.from_pretrained(
    'stabilityai/stable-diffusion-x4-upscaler',
    torch_dtype=torch.float16,
    variant='fp16',
).to(device)

img0 = Image.open('s240.jpg')

seed = 10000
generator = torch.Generator(device).manual_seed(seed)
prompt = '赤目銀髪少女'
img = pipe(
    honyaku(prompt),
    img0,
    num_inference_steps=20,
    generator=generator,
).images[0]
img.save('s960.jpg')
```

![s960.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/9b6cfb93-c425-494b-a32a-c7b5554af50f.jpeg)

ただのアップスケーラーだからプロンプトなんて関係ない……と、最初はそう思ったのですが、実際にプロンプトが必要です。試しにプロンプトを空っぽにしたら結果はこんなによくないのです。関係ないプロンプトを入れても駄目です。Stable Diffusionはプロンプトを解釈してどのように補足するか的確な判断をした上でアップスケールするらしいです。

試しに顔の部分だけ切り取って比較してみたらこんな感じ。

![upscale_hikaku.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/dcbadd87-106a-512c-ad7a-cfa768aaf15b.jpeg)

違いは微妙ですが、確かに適切なプロンプトを入れることで拡大した画像が明瞭になりますね。


## x2 latent upscaler 〜潜在空間で2倍拡大〜

もう一つのモデルは潜在空間の中で画像を2倍サイズにするx2 latent upscalerモデルです。

https://huggingface.co/stabilityai/sd-x2-latent-upscaler

既存の画像に使うx4 upscalerと違って、このモデルは主に生成したばかりでまだピクセル空間に変換していない潜在空間の画像に使うのです。とはいっても実際に既存の画像を入力しても問題ないらしいです。

ただしこのモデルを使う時にプロンプトを入れると悪い結果になるので基本的に**プロンプトは空っぽにする**のです。なぜかわからないが、プロンプトを入れると画像は目茶苦茶になります。これはx4 upscalerの場合と逆なので注意です。

SDXLではない従来のモデルは512×512が一番得意なサイズであり、1024×1024を直接生成しようとしてもいい結果が出ない場合も多いので、512×512で生成してから1024×1024にアップスケールするのも手らしいです。そんな時はこのアップスケーラーモデルの出番ですね。

では画像を生成してアップスケールしてみます。
```python
import torch
from PIL import Image
from diffusers import StableDiffusionPipeline,StableDiffusionLatentUpscalePipeline,EulerAncestralDiscreteScheduler
from translate import Translator

device = 'mps'
pipe = StableDiffusionPipeline.from_single_file(
    'stadifmodel/himawarimix_v11.safetensors',
    torch_dtype=torch.float16
).to(device)
pipe.scheduler = EulerAncestralDiscreteScheduler.from_config(pipe.scheduler.config)

pipe_x2 = StableDiffusionLatentUpscalePipeline.from_pretrained(
    'stabilityai/sd-x2-latent-upscaler',
    torch_dtype=torch.float16,
).to(device)

seed = 10114
generator = torch.Generator(device).manual_seed(seed)
honyaku = Translator('en','ja').translate
prompt = honyaku('窓際で泣いている紫髪吸血鬼少女')
img0 = pipe(
    prompt,
    num_inference_steps=20,
    generator=generator,
    output_type='latent'
).images

img = pipe_x2(
    '',
    img0,
    num_inference_steps=20,
    generator=generator,
).images[0]
img.save('madogiwa1024.jpg')
```

![madogiwa1024.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/a27e04b2-daa0-d4c7-4c18-96b9ebcc92a0.jpeg)

一応元の512×512の画像を保存して比較してみます。（潜在空間で出力したのでちょっと変換が必要）
```python
import numpy as np

img1 = pipe.vae.decode(img0/pipe.vae.config.scaling_factor)
img1 = img1.sample[0].detach().cpu().numpy().transpose(1,2,0)
img1 = np.uint8(np.clip(img1*0.5+0.5,0,1)*255)
Image.fromarray(img1).save('madogiwa512.jpg')
```

![madogiwa512.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/1bf99ae2-2d23-f713-79b0-ba9403942d12.jpeg)




# inpaint 〜特定の部分だけ修正〜

最後に、画像をある特定の部分だけ修正を行うためのインペイント（inpaint）機能を紹介します。

インペイント用のモデルはhuggingfaceに公開されています。これもdiffusersで`from_pretrained`を通じて自動ダウンロードして使うことができます。

https://huggingface.co/runwayml/stable-diffusion-inpainting

インペイントをする時に準備する必要があるのは、修正したい元画像と、修正する部分を指定するマスク画像です。

例えば今回この画像を使います。

![jinja_akagami.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/c91e4c6e-481c-0adb-9416-d6a384f96d66.jpeg)

修正してみたい部分は女の子が立っているところなのでこのマスク画像を使います。

![jinja_masuku.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/67328f9a-ad13-2472-b9b3-21bfaf3710ba.png)

マスク画像はRGBでもグレースケールでもいいです。修正したい部分は白で、その他は黒で塗ります。ComfyUIやAUTOMATIC1111 Stable Diffusion web UIだったらこのマスクを作るためのGUIがありますが、diffusersはのそのような機能がないので個別でPhotoshopなどで塗るしかないですね。

ではインペイントを実行してみます。
```python
import torch
from PIL import Image
from diffusers import StableDiffusionInpaintPipeline
from translate import Translator

device = 'mps'
pipe = StableDiffusionInpaintPipeline.from_pretrained(
    'runwayml/stable-diffusion-inpainting',
    torch_dtype=torch.float16,
    variant='fp16',
    safety_checker=None # これがないと真っ暗な画像ばっかりになる
).to(device)

seed = 21359
generator = torch.Generator(device=device).manual_seed(seed)
img0 = Image.open('jinja_akagami.jpg') # 元画像
img_mask = Image.open('jinja_masuku.png') # マスク画像
honyaku = Translator('en','ja').translate
prompt = 'こっちを見て笑っている黒髪和服少女、アニメ風'
img = pipe(
    prompt=honyaku(prompt),
    image=img0,
    mask_image=img_mask,
    num_inference_steps=20,
    generator=generator
).images[0]
img.save('jinja_kurokami.jpg')
```

![jinja_kurokami.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/12d01ab0-d80c-c4dd-6aea-07c7b2267f52.jpeg)

結果は意外と上手くいかないものです。実は試してみたところあまり思い通りにならなかくてがっかりしました。

それにこのインペイントモデルはアニメ画像が得意ではないらしいです。プロンプトの中で一応「アニメ風」を入れてよくなってきたが、それでもまだ微妙です。

ただし実際にcontrolnetという機能でインペイントすることもできます。その方が色々調整できていい結果は期待できそうなので、インペイントするならこのインペイントパイプラインではなく、controlnetを使った方がいいと思います。


# controlnet inpaint 〜インペイントの改善版〜

controlnetでインペイントを行う方法については次の記事に書いてあります。

https://qiita.com/phyblas/items/7cacb9297650afd63d34


# instruct-pix2pix 〜指示通り画像変更〜

img2imgと似ているものとしてinstruct-pix2pixという機能があります。これについて個別の記事に書いてあります。

https://qiita.com/phyblas/items/28c342740c2ed00250b8


# paint by example 〜指定の部分へ画像を挿入する〜

[paint by example](https://github.com/Fantasy-Studio/Paint-by-Example)はinpaintと同じように、画像の一部を修正する機能です。ただしinpaintでは修正したい内容をプロンプトで決めるのに対し、paint by exampleではプロンプトを必要とせず画像で決めるのです。つまり、ある画像のあるところにもう一つの画像を挿入するということです。

使う時はこのモデルを使って[`PaintByExamplePipeline`](https://huggingface.co/docs/diffusers/api/pipelines/paint_by_example)クラスでパイプラインを作ります。

https://huggingface.co/Fantasy-Studio/Paint-by-Example

paint by exampleを使う時に3枚画像を準備する必要があります。
- 元画像
- 修正したい部分マスク画像
- 挿入したいものが入っている画像

例としてここで元画像をこれにします。

![tenohiraneko.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/2270c7e9-5317-57bc-c4d6-47b81e357d7d.jpeg)

子猫の代わりにこの象を入れてみたいです。

![kawaiizousan.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/dfc5863a-5151-c8ca-cdd5-a6e8c9704cc3.jpeg)

マスク画像。

![tenohiraneko_mask.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/72496420-6e6a-2c8d-87b4-782971cf2e3c.png)

では試してみましょう。書き方は簡単です。
```python
import torch
from PIL import Image
from diffusers import PaintByExamplePipeline

device = 'mps'
pipe = PaintByExamplePipeline.from_pretrained(
    'Fantasy-Studio/Paint-by-Example',
    torch_dtype=torch.float16,
).to(device)
img_ex = Image.open('kawaiizousan.jpg')
img0 = Image.open('tenohiraneko.jpg')
img_mask = Image.open('tenohiraneko_mask.png')
img = pipe(
    example_image=img_ex,
    image=img0,
    mask_image=img_mask,
    num_inference_steps=30
).images[0]
img.save('tenohirazou.jpg')
```

![tenohirazou.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/3d5be3c2-eedd-e5e7-c3f0-745797df6fa6.jpeg)

なぜかあまり目標の象と似ていなくて期待外れです。

試してみたところ結果は微妙です。実際にこの方法は知名度が低くてググってもあまり情報が多くないみたいです。だからここではこんな方法もあるね、という程度で紹介しておきましたが、恐らくまだ実用的ではないですね。


# 参考

### img2img
- [【StableDiffusionのImg2Imgで桜を紅葉に変換してみた】](https://qiita.com/kitsune_yk/items/d7738aee752af2c3d24c)
- [Stable Diffusion2で顔写真を少し盛る](https://qiita.com/ahinore@github/items/feec453524a862f16ebe)
- [Stable Diffusion で画像をテキストで修正する方法（img2img）](https://gammasoft.jp/blog/image-to-image-with-stable-diffusion)
- [diffusers (Google Colab) で Image-to-Image を使ってみる](https://zako-lab929.hatenablog.com/entry/20240201/1706797257)
- [【Stable Diffusion】Google Colabでimg2imgを使う方法を解説](https://runrunsketch.net/diffusers-img2img/)
- [【Stable Diffusion】M1 MBAでimg2imgを動かす](https://qiita.com/1_arua/items/8671f2092d8c06f1b6ce)
- [Google Colaboratory 環境の Stable Diffusion で なるべく簡単に 下絵を使って画像を生成（img2img）したい！](https://qiita.com/seaweeds/items/5e718c0016c54cc5e46d)
- [子供と生成AIによるお絵かき共同作業](https://qiita.com/koji-yamauchi/items/0e8560e05b458b0a6595)
- [画像生成AIを使った自分だけのおしゃれなQRコードの作成手法](https://qiita.com/S4nTo/items/10819d3dc10f4c0807f2)
- [AIを使って2Dアニメーション作ってみた（やったことまとめ）](https://qiita.com/takaaki_inada/items/11880eaac9d9fee954a9)

### refiner
- [Refinerでモデル合成](https://gihan-page.ltt.jp/blog/archives/83)
- [Stable Diffusion web UIでSDXLのRefinerを利用する](https://taziku.co.jp/blog/sdxl-refiner)
- [Stable Diffusionの『Refiner』とは？使い方や設定方法について解説！](https://romptn.com/article/23259)
- [Stable DiffusionでRefinerの使い方について解説](https://ai-illust-kouryaku.com/?p=8972)
- [SDXL 1.0 (Stable Diffusion XL 1.0) が公開されたので Diffusers から使ってみる](https://touch-sp.hatenablog.com/entry/2023/07/27/192120)
- [最新の画像生成AI「SDXL 1.0」実写系イラストのクオリティがすごい！！](https://ascii.jp/elem/000/004/147/4147298/)

### upscale
- [画像からアップスケーラー、そして超解像へ](https://zako-lab929.hatenablog.com/entry/20240202/1706832728)
- [【Stable Diffusion】Diffusersのupscalerで高解像度の画像を作ってみた](https://technoxs-stacker.hatenablog.com/entry/2023/08/23/000000)
- [Diffusersで高解像度の画像を生成する](https://hodalog.com/generating-high-resolution-images-using-diffusers/)

### inpaint
- [Inpaint を使ってみる](https://zako-lab929.hatenablog.com/entry/20240209/1707476932)
- [【Diffusers】【Inpaint】StableDiffusionInpaintPipeline と MaskedStableDiffusionImg2ImgPipeline の違いを比較してみました。](https://touch-sp.hatenablog.com/entry/2024/01/13/130445)
- [Stable Diffusion2で顔写真を少し盛る](https://qiita.com/ahinore@github/items/feec453524a862f16ebe)
- [diffusers で ControNet の inpaint を試す](https://note.com/npaka/n/n31d6c98dcf02)
- [Stable Diffusionで顔や手だけ変える機能『Inpaint』の使い方！](https://romptn.com/article/5123)
- [Stable Diffusionで部分修正できるinpaintの使い方について解説](https://ai-illust-kouryaku.com/?p=3883)

### paint by example
- [【inpainting】従来のGAN（AOT-GAN）とPaintByExampleを比較してみる](https://touch-sp.hatenablog.com/entry/2023/01/07/170744)
- [[CV勉強会@関東 CVPR2023] Paint by Example : exemplar-based Image Editing with Diffusion Models](https://speakerdeck.com/cheerfularge/cvmian-qiang-hui-at-guan-dong-cvpr2023-paint-by-example-exemplar-based-image-editing-with-diffusion-models)
- [【PaintByExample】写真に写った犬を猫に変えてみる](https://touch-sp.hatenablog.com/entry/2023/01/02/170635)
- [Paint-by-Example で写っているものを差し替えてみる](https://zako-lab929.hatenablog.com/entry/20240311/1710164996)