最適輸送距離を使って，簡単にスタイル変換みたいなことをしてみた

2023/01/22:最終更新
# はじめに
最適輸送距離を用いて，スタイル変換みたいなことをする方法を紹介します．
この手法自体は，以下の最適輸送距離の本に書いてあったものです．
![61ClkGlinpL._SX598_BO1,204,203,200_.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2885323/f6cefaab-e70f-b264-7359-7eb10e8f957c.jpeg)

https://www.amazon.co.jp/%E6%9C%80%E9%81%A9%E8%BC%B8%E9%80%81%E3%81%AE%E7%90%86%E8%AB%96%E3%81%A8%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0-%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92%E3%83%97%E3%83%AD%E3%83%95%E3%82%A7%E3%83%83%E3%82%B7%E3%83%A7%E3%83%8A%E3%83%AB%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-%E4%BD%90%E8%97%A4-%E7%AB%9C%E9%A6%AC/dp/4065305144

実際にしたいことは以下の，左の2つの画像から，右の画像を生成することです．
ここでは，右の2つの画像サイズは同じであり，小さいサイズであるときのみ動くコードを紹介します．
最後のコードを実行したら動くようにしています．
![connected_img.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2885323/49e8b60e-5d1c-68ac-bc95-4934cc77a799.png)


# 開発環境
Mac OS Montery v.12.6
Python 3.9.11
numpy = 1.23.3
POT = 0.8.2
Pillow = 9.4.0
# 目次
<!-- タイトルとアンカー名を編集 -->
1. 2つの画像のサイズを合わせる方法
2. スタイル変換のようなことをする方法
3. 画像を結合して保存する方法
4. 上の3つをまとめたコード

<!-- 各チャプター -->
<a id="#Chapter1"></a>
# 2つの画像のサイズを合わせる方法


```python:resize.py
from PIL import Image


def resize_img(content_img_file, style_img_file, resize=100):
    # 画像の読み込み
    content_img = Image.open(content_img_file)
    style_img = Image.open(style_img_file)

    # 画像のリサイズ
    content_img_resized = content_img.resize((resize, resize))
    style_img_resized = style_img.resize((resize, resize))

    # ファイルを保存
    content_img_resized.save('content_resized.png', quality=90)
    style_img_resized.save('style_resized.png', quality=90)

    return content_img_resized, style_img_resized


content_img_resized , style_img_resized = resize_img(content_img_file, style_img_file)

```
<a id="#Chapter2"></a>
# スタイル変換のようなことをする方法

```python:transfer.py
from PIL import Image
import numpy as np
import ot


def transfer_img(content_img, style_img):
    # 画像の形式の変更
    xs = np.array(content_img, dtype='float64').reshape(-1, 3)
    xt = np.array(style_img, dtype='float64').reshape(-1, 3)

    # 各分布のサンプル数
    n = xs.shape[0]
    # 各点の重さ。今回は全て1/nとしている
    a, b = np.ones((n,)) / n, np.ones((n,)) / n
    # 距離の定義
    C = ot.dist(xs, xt)
    C /= C.max()

    # 最適な輸送方法の計算
    P = ot.emd(a, b, C)

    # Pを用いて実際に輸送してみる
    transferred_img = np.einsum('ij, ki->kj',xt, P)
    transferred_img = transferred_img*n

    # 輸送後の画像の保存
    transferred_img = np.array(transferred_img, dtype='uint8')
    transferred_img = transferred_img.reshape(int(n**(1/2)), int(n**(1/2)), 3)

    pil_img = Image.fromarray(transferred_img)
    pil_img.save('transferred_img.png')
    return pil_img


transferred_img = transfer_img(content_img_resized, style_img_resized)

```

<a id="#Chapter3"></a>
# 画像を結合して保存する方法

```python:connect.py
def save_img(images):
    connected_img = Image.new('RGB', (images[0].width*len(images), images[0].height))
    for i in range(len(images)):
        connected_img.paste(images[i], (images[i].width*i, 0))
    connected_img.save('connected_img.png')


save_img([content_img_resized, style_img_resized, transferred_img])

```
<a id="#Chapter3"></a>
# 上の3つをまとめたコード


```python:otd.py
from PIL import Image
import numpy as np
import ot


def resize_img(content_img_file, style_img_file, resize=100):
    # 画像の読み込み
    content_img = Image.open(content_img_file)
    style_img = Image.open(style_img_file)

    # 画像のリサイズ
    content_img_resized = content_img.resize((resize, resize))
    style_img_resized = style_img.resize((resize, resize))

    # ファイルを保存
    content_img_resized.save('content_resized.png', quality=90)
    style_img_resized.save('style_resized.png', quality=90)

    return content_img_resized, style_img_resized

def transfer_img(content_img, style_img):
    # 画像の形式の変更
    xs = np.array(content_img, dtype='float64').reshape(-1, 3)
    xt = np.array(style_img, dtype='float64').reshape(-1, 3)

    # 各分布のサンプル数
    n = xs.shape[0]
    # 各点の重さ。今回は全て1/nとしている
    a, b = np.ones((n,)) / n, np.ones((n,)) / n
    # 距離の定義
    C = ot.dist(xs, xt)
    C /= C.max()

    # 最適な輸送方法の計算
    P = ot.emd(a, b, C)

    # Pを用いて実際に輸送してみる
    transferred_img = np.einsum('ij, ki->kj',xt, P)
    transferred_img = transferred_img*n

    # 輸送後の画像の保存
    transferred_img = np.array(transferred_img, dtype='uint8')
    transferred_img = transferred_img.reshape(int(n**(1/2)), int(n**(1/2)), 3)

    pil_img = Image.fromarray(transferred_img)
    pil_img.save('transferred_img.png')
    return pil_img

def save_img(images):
    connected_img = Image.new('RGB', (images[0].width*len(images), images[0].height))
    for i in range(len(images)):
        connected_img.paste(images[i], (images[i].width*i, 0))
    connected_img.save('connectefdfdd_img.png')


if __name__ == '__main__':
    content_img_file = 'content.png'
    style_img_file = 'style.png'
    content_img_resized , style_img_resized = resize_img(content_img_file, style_img_file)
    transferred_img = transfer_img(content_img_resized, style_img_resized)
    save_img([content_img_resized, style_img_resized, transferred_img])

```