openCVで画像を合成する

# はじめに
openCVを使って、正式には**アルファブレンド**と呼ばれる画像の合成のやり方を学んだのでメモ。

画像の合成は実用面で言うと、例えば超解像タスクでTTA（Test Time Augmentation)の一種で、何枚かの画像を合成してスコアを上げる**Geometric Self-ensemble**として論文でも紹介されていたりします。

詳しくは[コチラの論文](https://arxiv.org/pdf/1707.02921.pdf)を。

# サンプル画像
以下の4枚の画像を使います。
建物、森、氷河、海の画像です。

![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/d0f1b262-113b-4a09-b868-5456905185bb.png)


# cv2.addWeightedを使う
画像の合成自体はopenCVを使えば実装自体は非常に簡単でcv2.addWeightedを使用すれば良いです。

必要なものをインポートして

```python:インポート
import cv2
import matplotlib.pyplot as plt
```

いちいち画像パス書いて、読み込みしてみたいなのを書くのはめんどくさいので
画像をカラー画像として読み込むとこまで一連の関数にします

```python:RGB画像としてopencvで読み込み
def read_img_to_rgb(image_path):
    img = cv2.imread(image_path)
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    
    return img_rgb
```

PILの方がRGBの順番など気にしなくて良いですけどね、、、

同じくplt.imshowでいちいち表示したりするのは面倒なので、画像を表示する関数も設定します。

```python:画像を表示する関数
def show_img(img):
    plt.imshow(img)
    plt.axis('off')
```

4つの画像を読み込みます。

```python
building_img = read_img_to_rgb('building.jpg')
forest_img = read_img_to_rgb('forest.jpg')
sea_img = read_img_to_rgb('sea.jpg')
glacier_img = read_img_to_rgb('glacier.jpg')
```

下準備はできたので、画像の合成を行っていきます。
例えば海と森の画像を合成するコードは以下になります。

```python
sea_forest_img = cv2.addWeighted(src1=sea_img, alpha=0.5, src2=forest_img, beta=0.5, gamma=0)
```
引数の説明をします。
src1, src2はそれぞれ合成したい画像
alpha, betaは2枚の画像をどれだけの割合で合成するか（alpha : betaの割合で合成する）
gammaは全ての画素値に加えられる数。

以下の式に従ってそれぞれの画素値を計算している。

$$ dst = src1 × alpha + src2 × beta + gamma $$

上記コードを実行してできた画像が以下。

![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/3a12db8a-a201-17bd-64ea-cd0bab2fb16e.png)
今回画素値は0~255までの離散値を取るため、上記計算式での計算結果の小数点以下は丸められてます。
より正確に合成するならfloat32型にして、0~1の範囲で正規化して合成すると良いと思う。


ここでなんとなくalphaとbetaの合計は1じゃないといけない気もするがそれは間違い。
alpha=1/4, beta=1/4でやってみると以下。

![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/015dc52c-fbaf-07f7-97ed-2c3c28b7a35e.png)

ちょっと暗くなりましたね。要は上記計算式の計算結果が0以下の数値は0に設定され、255以上の数値は255になるだけです。

なのでalpha=10, beta=10とかってすると
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/0440ddfb-3849-d326-500b-a642ed53300c.png)
ほぼ全ての画素値が255を超えるため255に設定され真っ白に近くなります。

alpha=-1, beta=-1なら
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/1af3629b-1cf3-e288-72e2-21e43e1cfcfa.png)

全ての画素値がマイナスになるので全て0に設定されるため、真っ黒になりますね。

ただ画素値の範囲が0〜255なのか0~1なのかとかによって変わるのでそこだけ注意。

# 4枚の画像を均等に合成する
2枚の画像を均等に合成するならalphaとbetaを0.5に設定すれば良いですが、4枚の画像を均等に合成するにはどうすれば良いでしょうか。

正直自分もやり方が正しいか不安なところですが2枚ずつ1/4の割合で合成して、再度出来上がった2枚を1:1で合成するやり方でいけます。

```python
sea_forest_img = cv2.addWeighted(sea_img, 1/4, forest_img, 1/4, 0)

glacier_and_building = cv2.addWeighted(glacier_img, 1/4, building_img, 1/4, 0)

all_combined_img = cv2.addWeighted(sea_forest_img, 1, glacier_and_building, 1, 0)
```

##### 海と森の合成画像
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/85da67cd-ebc2-f7a1-cd19-bc02cfb81b47.png)

##### 氷河と建物の合成画像
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/bfd90e57-40a1-2bf1-4c4f-a8ac1c101572.png)

##### 4枚全て混ぜた画像
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/890361/1074048f-59e3-0c56-13d9-23c7843cf893.png)

これを応用すれば8枚を均等に合成したりとかもできたりすると思います。

# まとめ
cv2.addWeightedを使ったアルファブレンドの使い方について紹介しました。超解像の分野ではよく使う処理だと思うので、覚えておきたいです。