色違いのポケモンを生成する【ControlNet】

# やること
StableDiffusionControlNetPipelineを用いて、色違いのポケモン画像を生成します。
実際のポケモン画像を入力とし、エッジ検出により線画化した後、その線画で条件付けた画像生成を行います。
なお、以下のコードはGoogle Colaboratory上(ランタイムのタイプ：T4 GPU)での実行を想定しています。

## ライブラリのインストール、インポート
```
!pip install -q diffusers==0.14.0 transformers xformers git+https://github.com/huggingface/accelerate.git
!pip install -q opencv-contrib-python
!pip install -q controlnet_aux
```

```Python
import cv2
from PIL import Image
import numpy as np
import torch
from diffusers import StableDiffusionControlNetPipeline, ControlNetModel
from diffusers import UniPCMultistepScheduler
from diffusers import StableDiffusionControlNetPipeline
from diffusers.utils import load_image
from google.colab import files
```

## パイプラインの作成
エッジ画像で条件づけられるControlNetを用います。
```Python
# モデルのロード
controlnet = ControlNetModel.from_pretrained("lllyasviel/sd-controlnet-canny", torch_dtype=torch.float16)

pipe = StableDiffusionControlNetPipeline.from_pretrained(
    "lambdalabs/sd-pokemon-diffusers", controlnet=controlnet, torch_dtype=torch.float16
)

pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
pipe = pipe.to(device)

pipe.enable_xformers_memory_efficient_attention()

# NSFWチェッカーの無効化
def null_safety(images, **kwargs):
    return images, [False for _ in range(images.shape[0])]
pipe.safety_checker = null_safety
```

なお、今回はポケモン風の絵柄にするために[ポケモン画像でファインチューンされたモデル](https://huggingface.co/lambdalabs/sd-pokemon-diffusers)を使わせていただきましたが、通常のSD1.5("runwayml/stable-diffusion-v1-5")でも構いません。

## ファイルのアップロード
ローカルから画像ファイルを選択します。余白の少ない画像であることが望ましいです。
```Python
# ファイルのアップロード
uploaded_file = files.upload()
uploaded_file = next(iter(uploaded_file))
image = load_image(uploaded_file)
image
```
このセルを繰り返し実行する際、ブラウザがSafariだとエラーが出るようなので、Google Chromeの使用を推奨します。

## エッジ検出
cv2.Cannyを用いて画像からエッジ検出をします。
```Python
image = np.array(image)

# エッジ検出の閾値
threshold1 = 200
threshold2 = 300

canny_image = cv2.Canny(image, threshold1, threshold2)
canny_image = canny_image[:, :, None]
canny_image = np.concatenate([canny_image, canny_image, canny_image], axis=2)
canny_image = Image.fromarray(canny_image)
canny_image
```
二つのthresholdはどちらもエッジかどうかの判断の閾値を表していて、値が大きいほどエッジが検出されにくく、小さいほどエッジが検出されやすくなります。主にthreshold2(エッジかどうかの判断の閾値そのもの)を調整し、適切なエッジ検出画像を作りましょう。

## 画像生成
配列colorsの各テキストにより、生成するモンスターの色を指定します。下の例では、同時に三通りの色パターンを生成しています。
追加プロンプトやネガティブプロンプトにはまだ試行錯誤の余地があります。
```Python
# 生成するモンスターの色
colors = ["red and green",
          "blue and white",
          "colorful"]

# 推論ステップ数
num_inference_steps = 50
# 追加プロンプト
additional_prompt =  "flat white background, solo, masterpiece, best quality" 
# ネガティブプロンプト
negative_prompt = "lowres, worst quality, low quality, mutiple"

prompt = [color + " body, " + additional_prompt for color in colors]

generator = [torch.Generator(device=device).manual_seed(0) for i in range(len(prompt))]

# 推論
output = pipe(
    prompt,
    canny_image,
    negative_prompt=[negative_prompt for _ in range(len(prompt))],
    generator=generator,
    num_inference_steps=num_inference_steps,
)

# 結果の表示
w, h = output.images[0].size
grid = Image.new("RGB", size=(len(prompt)*w, h))
for i, img in enumerate(output.images):
  grid.paste(img, box=(i*w, 0))
grid
```

# 結果
## 例1
### 元画像
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1940763/af2f7d60-63d3-90a6-56c7-ce53ffe6c646.png)

### エッジ抽出画像
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1940763/a1e1b022-74e2-1c88-e4ad-9b34021610a6.png)

### プロンプトと生成画像
```Python
colors = ["red and green",
          "blue and white",
          "colorful"]
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1940763/e9c5c12a-99c5-fd56-d156-bb481d9d997d.png)

## 例2
### 元画像
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1940763/22993739-572a-71c9-fcfe-df9116b489ec.png)
### エッジ抽出画像
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1940763/42f2c9e8-493b-7d48-c826-530bb03884f9.png)
### プロンプトと生成画像
```Python
colors = ["red/green/blue",
          "black spots, yellow",
          "pikachu",
          "black and white stripe"]
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1940763/055af34e-8215-b534-e2e0-3f61fcef7b34.png)

## 例3
### 元画像
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1940763/14dd0bac-b090-43b2-521e-01ff4ce1c314.png)
### エッジ抽出画像
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1940763/17e55584-cc3b-21b4-4a98-06debe4ad517.png)
### プロンプトと生成画像
```Python
colors = ["red fire",
          "blue water",
          "yellow electricity",
          "green leaf"]
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1940763/97489e29-8550-6b74-fa74-58749621bef2.png)


# おわりに
- 背景色を真っ白にすることが難しかった。プロンプトの影響を受けがちで、余計なものが出てくることが多い。
- 色以外のプロンプトにおいて、模様（斑点、縞）は精度良く指定できたが、より高次の概念（季節、タイプ）はうまく反映されなかった。

# 参考サイト
[cv2.Canny(): Canny法によるエッジ検出の調整をいい感じにする](https://qiita.com/Takarasawa_/items/1556bf8e0513dca34a19)
[Stable Diffusion 呪文・プロンプトまとめ](https://harusdia.hatenablog.com/entry/2023/02/20/105410#background-%E8%83%8C%E6%99%AF)
[【ControlNet】Colaboratory上でコントローラブルな画像生成AIを5分で構築するレシピ](https://axross-recipe.com/recipes/1050)