色を保ったまま画像のスタイル変換を行う手法

# はじめに

Deep Learningを勉強されている方にはおなじみだと思いますが、画像のスタイル変換を行う[A Neural Algorithm of Artistic Style](A Neural Algorithm of Artistic Style)という論文があります。
日本語の解説は以下にあります。
スタイル変換って何？という方はまずこちらをご覧になることをお勧めします。
[Preferred Research 画風を変換するアルゴリズム](https://research.preferred.jp/2015/09/chainer-gogh/)

この手法でスタイル変換を行うと変換後の画像の色はスタイル画像に似たものになりますが、つい最近色を保ったままスタイル変換を行う[Preserving Color in Neural Artistic Style Transfer](http://arxiv.org/pdf/1606.05897v1.pdf)という論文が提出されました。
A Neural Algorithm of Artistic Styleと同じ筆者による論文です。

# 画像のスタイル変換とは？

コンテンツ画像(例えば猫の画像)とスタイル画像(例えばゴッホが描いた絵画)の2つを用意します。
この2つの画像から、内容はコンテンツ画像、スタイルはスタイル画像に近い画像(この場合ゴッホが描いたような猫の画像)を生成することをスタイル変換と呼びます。

# 概要

論文では2種類の手法を提案しています。

* Color histogram mathcing  
スタイル画像の色ヒストグラムをコンテンツ画像に近づける  
変換後のスタイル画像を使ってスタイル変換を行う
* Luminance-only transfer  
コンテンツ画像の輝度のみをスタイル変換する

# 手法その1 Color histogram matching

スタイル画像を変換し、RGB値の平均と共分散がコンテンツ画像のそれと同じになるようにします。
まず記号を定義します。

|記号|意味|次元|
|:---:|:---|:---|
|$\bf{x}_{C}$|コンテンツ画像|3 x (コンテンツ画像のピクセル数)、各行はR, G, B値のベクトル|
|$\bf{x}_{S}$|スタイル画像|3 x (スタイル画像のピクセル数)|
|$\bf{\mu}_{C}$|コンテンツ画像のRGBの平均値|3-vector|
|$\bf{\mu}_{S}$|スタイル画像のRGBの平均値|3-vector|
|$\bf{\Sigma}_{C}$|コンテンツ画像のRGBの共分散|3 x 3|
|$\bf{\Sigma}_{S}$|スタイル画像のRGBの共分散|3 x 3|

スタイル画像の変換

```math
\bf{x}_{S^{\prime}} \leftarrow \bf{A}\bf{x}_{S}+\bf{b}
```

を考えます。
変換後のスタイル変換の色ヒストグラムをコンテンツ画像の色ヒストグラムに近づけるために

```math
\bf{\mu}_{S^{\prime}} = \bf{\mu}_{C}
```
```math
\bf{\Sigma}_{S^{\prime}} = \bf{\Sigma}_{C}
```

を満たすような$\bf{A}$と$\bf{b}$を考えると、これらは

```math
\bf{b} = \bf{\mu}_{C}-\bf{A}\bf{\mu}_{S}
```
```math
\bf{A}\bf{\Sigma_{S}}\bf{A^{T}} = \bf{\Sigma_{C}}
```
を満たします。
この式を満たす$\bf{A}$として、論文では2つ挙げています。

## コレスキー分解

```math
\bf{A}_{\rm chol} = \bf{L}_{C}\bf{L}_{S}^{-1}
```
ただし$\bf{L}$は$\bf{\Sigma}$のコレスキー分解で$\bf{\Sigma}=\bf{L}\bf{L}^{T}$を満たします。

## 固有値分解

$\bf{\Sigma}$を固有値分解して$\bf{\Sigma}=\bf{U}\bf{\Lambda}\bf{U}^{T}$が得られたとします。
このとき$\bf{\Sigma}^{1/2}=\bf{U}\bf{\Lambda}^{1/2}\bf{U}^{T}$、$\bf{\Sigma}^{-1/2}=\bf{U}\bf{\Lambda}^{-1/2}\bf{U}^{T}$と定義します。
そして

```math
\bf{A}_{\rm IA} = \bf{\Sigma_{C}^{1/2}}\bf{\Sigma_{S}^{-1/2}}
```

を変換に用います。

得られた$\bf{A}$と$\bf{b}$を使ってスタイル画像の変換を行い、あとはコンテンツ画像と変換後のスタイル画像を使って通常のスタイル変換を行います。
論文では固有値分解の方がコレスキー分解よりもよい結果が得られたとしています。

固有値分解を使ったアルゴリズムを使ってスタイル画像の変換を行ってみました。
配色がコンテンツ画像に似ているのがわかります。

|コンテンツ画像|スタイル画像|色ヒストグラムを近づけた画像|
|:---:|:---:|:---:|:---:|
|<img src="https://qiita-image-store.s3.amazonaws.com/0/58026/80ba83f4-c0ba-ae25-f659-4cf9d3d54688.jpeg" width="160px">|<img src="https://qiita-image-store.s3.amazonaws.com/0/58026/6fce5fdb-5ef8-9042-802c-1f06c46037f2.png" width="160px">|<img src="https://qiita-image-store.s3.amazonaws.com/0/58026/27f8d0a7-9dae-5fe5-4f63-68e64fea44ef.png" width="160px">|

# 手法その2 Luminance-only transfer

コンテンツ画像とスタイル画像の色空間をYIQに変換し、輝度のみのコンテンツ画像とスタイル画像を取得します。
このとき輝度の平均と分散を揃えるために以下の変換を行います。
$L_{C}$はコンテンツ画像の輝度、$L_{S}$はスタイル画像の輝度、$\mu_{C}, \mu_{S}$はそれぞれコンテンツ画像とスタイル画像の輝度の平均値、$\sigma_{C}, \sigma_{S}$は標準偏差です。

```math
L_{S^{\prime}} = \frac{\sigma_{C}}{\sigma_{S}}(L_{S}-\mu_{S})+\mu_{C}
```

これらの画像を使ってスタイル変換を行ったあと、コンテンツ画像のIQチャンネルと結合することで変換後の画像を得ることができます。
RGBとYIQの変換方法は[Wikipedia](https://en.wikipedia.org/wiki/YIQ)に載っています。

# Pythonによる実装

## Color histogram matching

固有値分解を使ったColor histogram matchingのPython実装は以下のようになります。
`numpy.linalg.eig`で固有値分解を行えます。

```py
import numpy as np
import six

# xがスタイル画像
# yがスタイル画像
# x, yはnumpy配列でshapeは(batch_size, 3, height, width)
def match_color_histogram(x, y):
    z = np.zeros_like(x)
    shape = x[0].shape
    for i in six.moves.range(len(x)):
        a = x[i].reshape((3, -1))
        a_mean = np.mean(a, axis=1, keepdims=True)
        a_var = np.cov(a)
        d, v = np.linalg.eig(a_var)
        a_sigma_inv = v.dot(np.diag(d ** (-0.5))).dot(v.T)

        b = y[i].reshape((3, -1))
        b_mean = np.mean(b, axis=1, keepdims=True)
        b_var = np.cov(b)
        d, v = np.linalg.eig(b_var)
        b_sigma = v.dot(np.diag(d ** 0.5)).dot(v.T)

        transform = b_sigma.dot(a_sigma_inv)
        z[i,:] = (transform.dot(a - a_mean) + b_mean).reshape(shape)
    return z
```

## BGRからYIQへの変換

BGRとYIQの相互変換は以下のようになります。
RGBではなくBGRを使っているのは画像変換で使っているVGGモデルがBGRを入力とするからです。

```py
# xは画像を表すnumpy配列でshapeは(batch_size, 3, height, width)
def bgr_to_yiq(x):
    transform = np.asarray([[0.114, 0.587, 0.299], [-0.322, -0.274, 0.596], [0.312, -0.523, 0.211]], dtype=np.float32)
    n, c, h, w = x.shape
    x = x.transpose((1, 0, 2, 3)).reshape((c, -1))
    x = transform.dot(x)
    return x.reshape((c, n, h, w)).transpose((1, 0, 2, 3))

def yiq_to_bgr(x):
    transform = np.asarray([[1, -1.106, 1.703], [1, -0.272, -0.647], [1, 0.956, 0.621]], dtype=np.float32)
    n, c, h, w = x.shape
    x = x.transpose((1, 0, 2, 3)).reshape((c, -1))
    x = transform.dot(x)
    return x.reshape((c, n, h, w)).transpose((1, 0, 2, 3))
```

# 実行

実際にこの手法をためしてみました。
ソースコードは以下にあります。
https://github.com/dsanno/chainer-neural-style

## 実行方法

以下のようにして実行できます。

### Color histogram matchingの実行

```
$ python src/run.py -g 0 -c content.jpg -s style.jpg -w 384 -o out_dir_01 --iter 2000 --lr 10 --match_color_histogram --initial_image content
```

### Luminance-onlyの実行

```
$ python src/run.py -g 0 -c content.jpg -s style.jpg -w 384 -o out_dir_02 --iter 2000 --lr 10 --luminance_only --initial_image content
```

## 実行結果

実行結果を示します。
Color histogram matchingはコンテンツ画像と色ヒストグラムは似ていそうですが、壁が肌色っぽくなったりしていて適切な位置に適切な色が塗られていません。
論文の結果だともっときれいな配色になっていたのでどこか間違っているのかもしれませんが。
Luminance-onlyの方はスタイル画像に似た画像が出力されています。

|コンテンツ画像|スタイル画像|Color histogram matching|Luminance-only transfer|
|:---:|:---:|:---:|:---:|
|<img src="https://qiita-image-store.s3.amazonaws.com/0/58026/80ba83f4-c0ba-ae25-f659-4cf9d3d54688.jpeg" width="160px">|<img src="https://qiita-image-store.s3.amazonaws.com/0/58026/6fce5fdb-5ef8-9042-802c-1f06c46037f2.png" width="160px">|<img src="https://qiita-image-store.s3.amazonaws.com/0/58026/fbeed884-4235-4cc2-84f5-d6a1de1a6f2f.png" width="160px">|<img src="https://qiita-image-store.s3.amazonaws.com/0/58026/6030172b-366b-8f24-07f4-db45e124bc64.png" width="160px">|
|同上|<img src="https://qiita-image-store.s3.amazonaws.com/0/58026/ef40adc6-9a4a-1736-050f-f8e002a15d77.jpeg" width="160px">|<img src="https://qiita-image-store.s3.amazonaws.com/0/58026/0ceead06-aa83-e7cc-fe46-5201c75c3b62.png" width="160px">|<img src="https://qiita-image-store.s3.amazonaws.com/0/58026/12382de9-4c9d-e913-0175-f52c8c8dd060.png" width="160px">|
|同上|<img src="https://qiita-image-store.s3.amazonaws.com/0/58026/7a64f1ee-ee47-ec93-a1aa-bb965e9b5cd9.png" width="160px">|<img src="https://qiita-image-store.s3.amazonaws.com/0/58026/57c44a93-c110-0d25-3c53-7ff8210157c6.png" width="160px">|<img src="https://qiita-image-store.s3.amazonaws.com/0/58026/c9d5225b-9752-716d-18af-e2e363e78ee0.png" width="160px">|
|同上|<img src="https://qiita-image-store.s3.amazonaws.com/0/58026/85f6a7ac-e6f7-cb31-7425-20fdbaf6d244.png" width="160px">|<img src="https://qiita-image-store.s3.amazonaws.com/0/58026/33cf40a1-e997-3e4a-294c-0e1be01a8a99.png" width="160px">|<img src="https://qiita-image-store.s3.amazonaws.com/0/58026/28efe502-e9af-4961-1364-20e384f24f1a.png" width="160px">|

スタイル変換の最適化において初期画像はコンテンツ画像と同じにするのがよさそうです。
初期画像をランダムにすると、以下のように明暗がコンテンツ画像と乖離します。
(スタイル画像はゴッホのStarry night、手法はLuminance-only)
<img src="https://qiita-image-store.s3.amazonaws.com/0/58026/69ee3e40-6b9a-74c8-3afd-073300bfb8f7.png" width="160px">


# まとめ

色を保ったままの画像のスタイル変換を実装しました。

この手法は既存のスタイル変換にコンテンツ画像・スタイル画像の色変換を組み合わせたもので、手法としては易しいものといえます。
このように少し手法を修正するだけで違った効果が得られることがあるので、新しい手法を真似するだけではなく、自分で工夫できないか考えることが重要だと思いました。

# 参考文献

* Leon A. Gatys, Alexander S. Ecker, Matthias Bethge, 2015, ["A Neural Algorithm of Artistic Style"](http://arxiv.org/abs/1508.06576)
* Leon A. Gatys, Matthias Bethge, Aaron Hertzmann, Eli Shechtman, 2016, [Preserving Color in Neural Artistic Style Transfer](http://arxiv.org/abs/1606.05897)
* [Preferred Research 
画風を変換するアルゴリズム](https://research.preferred.jp/2015/09/chainer-gogh/)