Stability AIがベータリリースしたSearch and Replace APIで黒髪女性の画像を茶髪にしたり金髪にしたり表情や服を変えてみたりした

[Supership](https://supership.jp/)の名畑です。[キンプリが再始動](https://www.famitsu.com/news/202403/22337926.html)とのことで、プリズムの煌めきで満ちた夏になりそうです。

## はじめに

画像生成モデル[Stable Diffusion](https://ja.stability.ai/stable-diffusion)の展開元である[Stability AI](https://ja.stability.ai/)は、様々な[開発者向けAPI](https://platform.stability.ai/docs/api-reference)も同様に展開しています。

そのうちの一つとして、まだ**Beta**ではあるのですが**Search and Replace**というものが今月リリースされました。

> The Search and Replace service is a specific version of inpainting that does not require a mask. Instead, users can leverage a search_prompt to identify an object in simple language to be replaced. The service will automatically segment the object and replace it with the object requested in the prompt.
> 
> 参考：[Search and Replace](https://platform.stability.ai/docs/api-reference#tag/Edit/paths/~1v2beta~1stable-image~1edit~1search-and-replace/post)

要は画像に対して「**変換元**」と「**変換後**」を言葉(プロンプト)で指定すると、その通りに反映してくれるというものになります。

単純に同じ結果だけを求めるなら**Inpainting**を使うという手もあります。あるいは[Photoshop](https://www.adobe.com/jp/products/photoshop.html)で、[生成塗りつぶし](https://qiita.com/nabata/items/73c23b3bf7b393825b8f)を使うまでもなく、フィルタを活用してより精度高く行えます。ただ、**Web API**でしかも文字のみで完結するという気軽さが個人的にはかなり印象深い体験だったため、ここでコードを含め紹介させていただきます。

ちなみに**女性の髪色変化**をやろうと思ったのは、公式が「[Stability AI Developer Platform の画像サービス](https://ja.stability.ai/blog/image-services-on-stability-ai-developer-platform)」というページで挙げていたサンプルがこれだったからです。

## 注意

- 実際の解像度は元画像の**1024x1024**、生成画像は**2048x2048**なのですが、記事容量を抑えるために本記事では**512x512**に変換した上でアップロードしています
- 本APIは[有料](https://platform.stability.ai/pricing)です。現在は1回の呼び出しにつき4クレジットとなります。10ドルで1000クレジットなので、0.04ドルです。1ドルが150円とすると6円です。

## API Key

[Developer Platform](https://platform.stability.ai/)でAPI Keyを発行しておいてください。

ハードコーディングを避けるため、環境変数にでも保存しておきましょう。今回は**STABILITY\_API\_KEY**という名称で保存されていると仮定して進めます。

```
export STABILITY_API_KEY =ここに取得したAPI keyを書く
```

## コード

今回用いた**Python**のコードは以下です。見ての通りですが、**prompt**に「**どのように変換するか**」を、**search\_prompt**に「**何を変換するか**」を書きます。元画像は**original.png**というファイル名で保存してあるとします。今回はしていませんがシード値の指定も可能です。

```python
import os
import requests
import time

api_host = "https://api.stability.ai"
api_key = os.getenv("STABILITY_API_KEY")  # 環境変数にAPIキーを保存済み

# API Keyの取得確認
if api_key is None:
    raise Exception("Missing Stability API key.")

response = requests.post(
    f"{api_host}/v2beta/stable-image/edit/search-and-replace",
    headers={
        "Authorization": f"Bearer {api_key}",
        "accept": "image/*"
    },
    files={
        "image": open("./original.png", "rb")  # 元画像
    },
    data={
        "prompt": "変換後プロンプト",
        "search_prompt": "変換元プロンプト",
        "output_format": "png",
    }
)

# 画像の保存
if response.status_code == 200:
    with open(f"./{int(time.time())}.png", "wb") as file:
        file.write(response.content)
else:
    raise Exception(str(response.json()))
```

## 黒髪の女性を茶髪にする

元画像は以下です。**Dall-E 3**で生成したものです。

![sar_8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/9259a74a-e953-8831-3955-cbb69c49bc34.png)

プロンプトは以下にしました。**black hair**(黒髪)を**brown hair**(茶髪)に変えてみます。実際はあえて**black**と書かなくても他に髪がないので同じような結果になるとは思いますが今回は明示的に書きました。

```python
"prompt": "brown hair",
"search_prompt": "black hair",
```

生成された画像は以下です。

![sar_9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/0cfc1e33-f82b-b262-c9ce-00612d30ae14.png)

確かに、茶髪になっている。

並べてみます。

| 変換前 | 変換後 |
| :---: | :---: |
| ![sar_8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/9259a74a-e953-8831-3955-cbb69c49bc34.png) | ![sar_9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/0cfc1e33-f82b-b262-c9ce-00612d30ae14.png) |

よく見ると髪との距離が近い目や眉も変わってしまっていますが、かなり綺麗な変換です。鈍感な私なら、言われなければ髪色以外の変化に気づかない気もする。いや、流石に気づくか。
なんにせよ、ここまで綺麗にやってくれるものなのだなと驚きです。

## 黒髪の女性を金髪にする

さらに以下のプロンプトで金髪にもしてみました。

```python
"prompt": "blonde hair",
"search_prompt": "black hair",
```

![sar_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/6d7e789c-be80-8f72-da31-97c9b7c65c30.png)

| 変換前 | 変換後 |
| :---: | :---: |
| ![sar_8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/9259a74a-e953-8831-3955-cbb69c49bc34.png) | ![sar_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/6d7e789c-be80-8f72-da31-97c9b7c65c30.png) |

これも右の目や眉の変化により顔自体の印象も異なりますが、ちゃんと元画像を生かした金髪です。

## ウェーブをかけてみる

```python
"prompt": "black wavy hair",
"search_prompt": "black hair",
```

「**black wavy hair**」つまり「**ウェーブのかかった黒い髪**」を指定してみました。

![sar_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/cf9bb946-0e14-9900-2c73-ce93e5dec68c.png)

| 変換前 | 変換後 |
| :---: | :---: |
| ![sar_8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/9259a74a-e953-8831-3955-cbb69c49bc34.png) | ![sar_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/cf9bb946-0e14-9900-2c73-ce93e5dec68c.png)|

指示に忠実に従ってはいます。
ただ、やっぱり髪に近い部分の変化があり、髪型以上の印象変化を受けます。

目が顔の印象に与える影響って大きいものですね。

## 目を閉じてみる

表情を変えてみましょう。

以下のプロンプトで**eyes**(目)を**closed eyes**(閉じた目)にしてみます。

```python
"prompt": "closed eyes",
"search_prompt": "eyes",
```

![sar_14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/c6d6cf5f-1bd6-aa0e-97ab-a7102cf01974.png)

| 変換前 | 変換後 |
| :---: | :---: |
| ![sar_8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/9259a74a-e953-8831-3955-cbb69c49bc34.png) | ![sar_14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/c6d6cf5f-1bd6-aa0e-97ab-a7102cf01974.png) |

ここまでで最も自然な気がします。変更箇所が狭いからでしょうか。

## 口を開けてみる

さらに試してみます。

**mouth**(口)を**open mouth**(開いた口)にしてみます。

```python
"prompt": "open mouth",
"search_prompt": "mouth",
```

![sar_13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/29878e89-c504-e466-e252-f5c5e4cba66b.png)

| 変換前 | 変換後 |
| :---: | :---: |
| ![sar_8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/9259a74a-e953-8831-3955-cbb69c49bc34.png) | ![sar_13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/29878e89-c504-e466-e252-f5c5e4cba66b.png) |

閉じた唇と開いた唇を並べると色味など違いもあるとは思いますが、まあ、自然ではあるかな。
不自然だろうか。
見すぎててよくわからなくなってきました。

## 服を着物にしてみる

顔以外も変えてみましょう。

**clothes**(服)を**kimono**(着物)にしてみました。

```python
"prompt": "kimono",
"search_prompt": "clothes",
```

![sar_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/8852da24-176e-f18f-8696-b2fc5ff3695e.png)

| 変換前 | 変換後 |
| :---: | :---: |
| ![sar_8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/9259a74a-e953-8831-3955-cbb69c49bc34.png) | ![sar_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/8852da24-176e-f18f-8696-b2fc5ff3695e.png)|

磯丸水産の制服みたいになってしまいましたが、きちんと服だけが変わっています。インナーも変えてほしかったですが、別物扱いですかね。

## 変わりすぎてしまうこともある

プロンプトが雑なせいか、あるいは画像の質によるものか、意図通りにいかないケースもやはりありました。

ということで、同じく若い女性ではあるものの、別画像で試した結果も紹介します。

元画像は以下です。[過去記事](https://qiita.com/nabata/items/d7cd5995595d4f9717d7)で生成したものの流用です。

![sar_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/8aeb4b78-c987-f2a9-01ae-3755f688f7e2.png)

この画像に対して同様に「**茶髪**」「**金髪**」「**ウェーブのかかった黒い髪**」への変換を行った結果が以下です。

![sar_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/0b66a3e8-fb9b-a135-65ce-5acd29a7e493.png)

![sar_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/68de42ba-42a5-f4eb-fcc7-fc858aa6aac3.png)

![sar_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/16646445-5a5d-8666-b613-a2ed3fc41d1c.png)

髪のかかった範囲が先ほどよりも広いためか、顔の変化が大きいです。耳にピアスがつくという変化も生じました。
それでもかなり元画像に近くはありますが。

[別の過去記事](https://qiita.com/nabata/items/3db85197404d2ee55f36)から持ってきた以下の画像でも試してみました。

![sar_6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/a136f661-565a-5f4f-cec0-a74757eb4591.png)

**black hair**(黒髪)を**brown hair**(茶髪)に変えてみます。

![sar_7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/0054b500-34fb-9e6a-2575-709d4bd9bf5f.png)

目もかなり変わってしまっています。
別人に見える。

ちなみに同じ画像で**eyes**(目)を**closed eyes**(閉じた目)にしてみた結果は以下です。

![sar_15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/a33c61e6-5663-27a2-1693-aa54af770eaa.png)

対象範囲が狭いからか、かなり自然でした。

ただしそれは単体での話で、やはり元画像と並べてよく見るとまつ毛を中心に違和感があります。

| 変換前 | 変換後 |
| :---: | :---: |
| ![sar_6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/a136f661-565a-5f4f-cec0-a74757eb4591.png) | ![sar_15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/a33c61e6-5663-27a2-1693-aa54af770eaa.png) |

この違いも、鈍感な私なら気づかないですね。

## 最後に

使う上でのハードルがどんどんと下がっていることを実感します。

あと、人間の顔って少しの変化が大きな印象の差につながるのだと当たり前のことを学びました。

## 宣伝

[SupershipのQiita Organization](https://qiita.com/organizations/supership-inc)を合わせてご覧いただけますと嬉しいです。他のメンバーの記事も多数あります。

Supershipではプロダクト開発やサービス開発に関わる方を絶賛募集しております。
興味がある方は[Supership株式会社 採用サイト](https://supership.jp/recruit/)よりご確認ください。