即戦力化　ディープラーニング実習（第七週）

# 【第７週】画像の生成と変換

講義目録：<a href="https://qiita.com/takenakayoichi/items/3b7435f4b155a181e39f">即戦力化　ディープラーニング実習 </a>

# はじめに  

これまでの実習では、主に **テキスト（txt2txt）** 系タスクを扱い、  
「生成 → 評価 → プロンプト改善」という一連の流れを体験してきました。  
第7週では舞台を **画像分野** に移し、  
1 行のコード変更で動かせる Stable Diffusion 系モデルを用いて

* **text-to-image：文章 → 画像をゼロから生成**  
* **image-to-image：既存画像を別スタイルに変換**  

を一気に体験します。  

体験する内容は、次のgoogle colabで確認できます。

**Google Colabへのリンク**
- [画像生成と変換のcolab](https://colab.research.google.com/drive/1a7SN5qPuPAEjoFVNKXTSFKdu2l-Z7lFG?usp=sharing)

## 1. 実習の目的
- **画像生成（text-to-image）の基本操作を習得する**  

- **画像変換（image-to-image / style-transfer）の代表的手法を比較する**  
  1. **Img2Img** : 画像を丸ごと別の画風へ変換する
  2. **Instruct-Pix2Pix** : 自然言語の指示文で部分編集を行う
  - Img2Imgと Pix2Pixの違いを理解する  
  3. **ControlNet (Canny)** : 線画に着色する

## 2. CNN とは  

今週から**畳み込みニューラルネットワーク（Convolutional Neural Network; CNN）** を用いた実習が始まります。

CNNは、画像のような “空間的に隣接するピクセル同士の関連” を捉えるのが得意なネットワークです。  
畳み込み層は **小さなフィルタ（カーネル）を画像全体に滑らせる** ことで、エッジ・テクスチャ・形状といった局所パターンを抽出します。

それを多層化（深層化）して、頑張って計算してくれるのがCNNです。

CNNの詳しい説明は、webでも書籍であちこちに存在しますので、ここでは詳しく説明しないことにします。

### RNN とのちがい  
先週まで扱った **RNN（再帰型ネットワーク）** は「時系列」や「文の単語列」のような **一次元の“順序依存”** をモデリングするのに特化しています。**RNN は「時間的文脈」**, **CNN は「空間的文脈」** を捉えるアーキテクチャだという点が最大の違いです。

### 3-1 共通の準備  

最初に、共通のライブラリをpipでinstallします。

google colab で動かそうとすると、いろいろと面倒でした。
まずは、深く考えず、お試し実行を優先することにしましょう。

```python
!pip uninstall -y diffusers transformers huggingface_hub peft sentence-transformers -q || true

# 衝突しない組み合わせをクリーンインストール
!pip install -q --no-cache-dir \
  "huggingface_hub==0.24.1" \
  "transformers==4.25.1" \
  "diffusers==0.26.3" \
  accelerate safetensors xformers
```

**Google Colab**上で実習する場合、上記セルを実行後、
**「ランタイム ▸ 再起動」** を行ってください。そうしないと、いろいろとエラーがでてきます。

Colab ノートブック例
セルの配置やランタイム再起動の手順が不安な場合は、
▶︎ [画像生成と変換の Colab サンプル](https://colab.research.google.com/drive/1a7SN5qPuPAEjoFVNKXTSFKdu2l-Z7lFG?usp=sharing) を使ってください。


### 3-2 画像生成（text-to-image）

Stable Diffusion v1-5 を使い、**テキストから 512×512 px の画像をゼロから生成**しましょう。 

---

#### 3-2-1 パイプラインのロード

```python
from diffusers import StableDiffusionPipeline
import torch, datetime

pipe = StableDiffusionPipeline.from_pretrained(
    "runwayml/stable-diffusion-v1-5",
    torch_dtype=torch.float16,
).to("cuda")

```

初回は 4.3 GB の重みをダウンロードするため 1〜2 分かかります。

#### 3-2-2 生成してみる


```
prompt = "A majestic tiger standing on a cliff at sunset, ultra-realistic, 8K"
#夕暮れの崖の上に立つ雄大な虎、超リアル、8K
image  = pipe(prompt, num_inference_steps=30, guidance_scale=7.5).images[0]
display(image)
```
![ダウンロード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526647/647473d5-1595-4663-9199-a3c696198bdc.png)

私の場合、きちんとトラが生成されました。みなさんは、いかがだったでしょうか。

なお、トラのimageは、後続の実習で再利用します。

では、prompt　を変更し、みなさんも試してみてください。

**えっ、日本語のプロンプトも試してみたい**ですって？

どうぞどうぞ。ぜひお試しあれ。

#### 3-2-2 日本語プロンプトのお試し。

では早速、試してみましょう。
日本語と英語、それぞれ同じ内容のプロンプトを準備しました。

| 言語 | プロンプト |
|------|-----------|
| 英語 | A black cat meows next to a hydrangea flower. |
| 日本語 | 黒猫がアジサイの花の横でにゃーんと鳴いている。 |


では実行してみましょう。

```
prompt_en = "A black cat meows next to a hydrangea flower."
prompt_jp = "黒猫がアジサイの花の横でにゃーんと鳴いている。"

image_en = pipe(prompt_en, num_inference_steps=30, guidance_scale=7.5).images[0]
image_en.save("cat_en.png")
display(image_en)

image_jp = pipe(prompt_jp, num_inference_steps=30, guidance_scale=7.5).images[0]
image_jp.save("cat_jp.png")
display(image_jp)
```
英語で生成：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526647/ee270752-ab19-46ec-aead-01be8aa3ffd4.png)

日本語で生成：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526647/83903461-3feb-4daf-b40d-7f4f23604709.png)

おお？猫に羽が生えている！！　

どうやらこのモデルは日本語が苦手なようですね。

私が試したところ、指示を日本語で与えたら、背中に羽の生えた猫？鳥？みたいなへんてこな画像が生成されました。

みなさんも、同じような状況だったのではないでしょうか。

その理由ですが、今回用いたモデルが日本語を苦手としているからなのです。ではどうすればいい？

日本語のプロンプトを英語に翻訳してあげればいいのです。

ではその翻訳は誰がするのかって？

そりゃもちろん、深層学習のモデルに行ってもらえばいいのです。

即戦力化　ディープラーニング実習の[「第3週　翻訳と要約」](https://qiita.com/takenakayoichi/items/096fcb649e0881e36831)で学んだ成果をここで生かす時が来ましたよ。


### 3-3 画像変換（image-to-image / style-transfer）

ここからは、先ほど生成した **虎の画像** を `init_image` として使い、  
3 種類の変換手法（Img2Img / Instruct-Pix2Pix / ControlNet）を比較します。  

---

#### 3-3-1 共通コード

では最初に、３種類の変換手法に共通する部分です。

```python
# 先ほど生成したトラの image を実習用のイメージに転用します
init_image = image.convert("RGB").resize((512, 512))

# 共通セットアップ
from diffusers import (
    StableDiffusionImg2ImgPipeline,
    StableDiffusionInstructPix2PixPipeline,
    ControlNetModel,
    StableDiffusionControlNetPipeline,
)
from diffusers.utils import load_image
import torch, requests, io, PIL.Image as Image

device = "cuda"          # GPU 
dtype  = torch.float16   # VRAM 節約
```

> **ポイント**  
> - `init_image` は **512×512 px・RGB** に統一すると Stable Diffusion 1.5 系で最も安定します。  
> - 以降の各パイプラインはすべて `device` と `dtype` を共通化し、メモリ効率を最大化します。  
>
> この準備ができたら、次節以降で  
> 1. **Img2Img**（画風変換）  
> 2. **Instruct-Pix2Pix**（指示文による部分編集）  
> 3. **ControlNet (Canny)**（線画固定＋着彩）  
>   
> を順に実行していきます。

#### 3-3-2 Img2Img ─ 画風を「水彩画」へ変換

では、img2Img　から始めましょう。先ほどのトラの絵を水彩画風に変換します。

```
pipe_img2img = StableDiffusionImg2ImgPipeline.from_pretrained(
    "runwayml/stable-diffusion-v1-5",
    torch_dtype=dtype,
).to(device)

prompt      = "A watercolor painting of the same scene, A majestic tiger standing on a cliff at sunset, ultra-realistic, 8K" 
#同じシーンを描いた水彩画, そのあとに、生成に利用したプロンプトをコピペしています

strength    = 0.75   # 0=original維持, 1=完全生成
guidance    = 7.5

image_out = pipe_img2img(
    prompt           = prompt,
    image            = init_image,
    strength         = strength,
    guidance_scale   = guidance,
    num_inference_steps = 30,
).images[0]

image_out
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526647/647c0608-8905-469f-a6c7-a1320503121e.png)

背景が良くなりましたが、しっぽが２本になりました。

まぁ、これなら良いほうでしょうか。

運が良ければなんとか原型をとどめているかと思います。


#### 3-3-3 Instruct-Pix2Pix ─ 指示文で「夜景＋星空」に編集

次は違う仕組みを使って、背景を編集してみます。

```pytyon
pipe_p2p = StableDiffusionInstructPix2PixPipeline.from_pretrained(
    "timbrooks/instruct-pix2pix",
    torch_dtype=dtype,
    safety_checker=None,    # NSFW フィルタ不要なら外しても
).to(device)
pipe_p2p.enable_attention_slicing()

instruction = "Make it a night scene with a starry sky"
#星空のある夜景にする
image_edit  = pipe_p2p(
    prompt            = instruction,
    image             = init_image,
    num_inference_steps = 30,
    guidance_scale      = 7.5,
    image_guidance_scale= 1.5,   # 入力画像をどれだけ信頼するか
).images[0]

image_edit
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526647/4e29e74e-49ca-477c-aed1-f0126469b040.png)

プロンプト通り、夜の景色になりましたね。

皆さんもいろいろ試してみてください。
#### 3-3-4 Img2Img2 とpix2pix の違い

子の実習では、最初に**img2img** で水彩画に、続いて**Instruct-Pix2Pix**(pix2pix)で夜景に変換しました。

似たような変換ですが、手法としては何が違うのでしょうか？


>**Img2Img** は「元画像にノイズを混ぜてから *prompt* に従って描き直す」方式で、絵全体を新しいスタイルにリライトするのが得意です。
>
>一方、 **Instruct-Pix2Pix** は「元画像・編集後画像・自然言語指示」のペアで追加学習されており、*instruction* を受けて **必要な部分だけを改変** するよう振る舞います。

#### 3-3-4 ControlNet (Canny) ─ 線画を固定してアニメ調に着彩

続いて３番目の画風変換です。

線画に色を塗ってみましょう。

とはいえ最初の画像は普通のカラー画像のトラさんです。
そこで、
1. エッジ抽出により線画を生成し、
2. 続いて色を塗る
という二段階の操作を行います。

```Python
# 1. ControlNet 重み（Canny）ロード
controlnet = ControlNetModel.from_pretrained(
    "lllyasviel/sd-controlnet-canny",
    torch_dtype=dtype,
)

pipe_canny = StableDiffusionControlNetPipeline.from_pretrained(
    "runwayml/stable-diffusion-v1-5",
    controlnet=controlnet,
    torch_dtype=dtype,
).to(device)
pipe_canny.enable_xformers_memory_efficient_attention()

# 2. Canny エッジを抽出
import cv2, numpy as np
np_img = np.array(init_image)
edges  = cv2.Canny(np_img, 100, 200)
edge_img = Image.fromarray(edges).convert("RGB").resize((512, 512))
display(edge_img)

# 3. 生成
prompt = "A vibrant anime style illustration"
#躍動感あふれるアニメ風イラスト
image_cn = pipe_canny(
    prompt           = prompt,
    image            = edge_img,
    num_inference_steps = 30,
    guidance_scale   = 9.0,
    controlnet_conditioning_scale = 1.0,
).images[0]

display(image_cn)
```

#### 線画
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526647/70d3b71e-c837-4e09-b5f2-75340565b8ce.png)

おおって、なんか線画ってかっこよさげですね。
それなのに...

#### 着色

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526647/3cf023a0-12da-4e53-9b80-fe257182e63f.png)

色を塗ると、なんか幻滅。でも、色が塗れたのでよしとしましょう。
最初なら、こんなもんです。

こちらもプロンプトを変更して、どう変わるかを試してみてくださいね。

これで **Img2Img → Pix2Pix → ControlNet** の 3 手法を一通り体験できました。  

## 🔚 第7週のまとめ

今週は、**画像生成（text-to-image）と画像変換（image-to-image / style-transfer）**　を実習しました。

特に、image-to-image では３手法にチャレンジしました。  
- Img2Img
- Instruct-Pix2Pix
- ControlNet  

### ✅ 今週のポイント

- **CNN とは何か**、畳み込み層が “空間的文脈” を捉える原理と、RNN の “時間的文脈” との対比を学びました。  
- **Stable Diffusion v1-5** で 512×512px の画像を生成し、`prompt / guidance_scale / seed` の３パラメータ調整を体験しました。  
- **Img2Img** で `strength` を変えながら「画風（水彩画）変換」の自由度を検証しました。  
- **Instruct-Pix2Pix** で “Make it a night scene with a starry sky” のような自然言語指示により、局所編集を実行しました。  
- **ControlNet (Canny)** で線画に色を塗ってみました。
---


## 🔭 次回予告：「第8週　画像の特徴抽出」

次回は **「画像の特徴抽出 ― 画像からキーワードや特徴ベクトルを得る」** をテーマに進めます 

- *Vision Transformer / CLIP* などの **事前学習モデルを転用**し、  
- 画像を入力すると **埋め込みベクトル（feature vector）** と **自動キーワード（タグ）** を取得。  
- 得られた特徴を使って **画像検索・クラスタリング・類似度計算** を体験します。  
- さらに、今週生成した画像をベンチマークに用い、  
  **「生成画像は検索システムでどのように表現されるか」** を実験的に確認します。

「生成→特徴抽出→検索」というワークフローは、  
実サービスの“画像検索エンジン”や“レコメンドシステム”にも直結する重要ステップです。  
どうぞお楽しみに！