OpenCVで透過画像を任意の画像に張り付ける

OpenCVの公式ドキュメントに、[透過画像を張り付けるチュートリアル](http://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_core/py_image_arithmetics/py_image_arithmetics.html#bitwise-operations)が存在するが、縁取りとか回転とかの方法までは書かれていなかったので記事を作成した。

# 結果

## チュートリアルそのまま

![test1.jpg](https://qiita-image-store.s3.amazonaws.com/0/227399/0c2287a1-e955-8a58-b242-88400bf01f7d.jpeg)


## 縁取り

どうしても透過画像をそのまま張り付けると画像の縁がジャギジャギになってしまうので、以下の画像では透過情報を膨張・縮小して縁を調整している。

![test2.jpg](https://qiita-image-store.s3.amazonaws.com/0/227399/163ee854-5c83-0078-5e73-ade56c7b2e24.jpeg)


## ランダム回転とランダム移動

以下の画像のように、ランダムに回転・移動ができると画像のバリエーションが簡単に増やせる。Deep Learningなどでデータを水増ししたい場合に利用することが多い。

![test3.jpg](https://qiita-image-store.s3.amazonaws.com/0/227399/f56e480d-224a-8b70-c18b-57209a8949c9.jpeg)

# 解説

以下がメイン文。二枚の画像（前景`hoge.png`と背景`huga.png`）を読み込み、縁取りや回転・移動のフラグを設定すると合成された画像が表示される。`cv2.imread()`の第二引数にはアルファチャンネルまで読み込むように`-1`を設定する。縁取りしたい場合は一つ目の`False`を`True`に、ランダム回転・移動をしたい場合は二つ目の`False`を`True`にする。

```python3:main.py
import cv2
from paste import paste


if __name__ == '__main__':
    img = paste(
        cv2.imread('hoge.png', -1),# 前景
        cv2.imread('huga.png', -1),# 背景
        False, False # 縁フラグ、回転・移動フラグ
    )

    cv2.imshow('test', img) # 画像の表示
    cv2.waitKey(0)
```

## 前景と背景の合成

基本的には[チュートリアル](http://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_core/py_image_arithmetics/py_image_arithmetics.html#bitwise-operations)と同じ。

```python3:paste.py
import cv2
import numpy as np
from rotate import rotateR

def paste(fg, bg, mask_flg=True, random_flg=True):
    """
    背景に前景を重ね合せる
    [in]  fg:         重ね合せる背景
    [in]  bg:         重ね合せる前景
    [in]  mask_flg:   マスク処理を大きめにするフラグ
    [in]  random_flg: 前景をランダムに配置するフラグ
    [out] 重ね合せた画像
    """

    # Load two images
    img1 = bg.copy()
    if random_flg:# ランダム回転
        img2, _ = rotateR(fg, [-90, 90], 1.0)
    else:
        img2 = fg.copy()

    # I want to put logo on top-left corner, So I create a ROI
    w1, h1 = img1.shape[:2]
    w2, h2 = img2.shape[:2]
    if random_flg:# ランダム移動
        x = np.random.randint(0, w1 - w2 + 1)
        y = np.random.randint(0, w1 - w2 + 1)
    else:
        x = 0
        y = 0

    roi = img1[x:x + w2, y:y + h2]

    # Now create a mask of logo and create its inverse mask also
    mask = img2[:, :, 3]
    ret, mask_inv = cv2.threshold(
        cv2.bitwise_not(mask),
        200, 255, cv2.THRESH_BINARY
    )

    if mask_flg:# 縁を膨張・収縮で作成（膨張大きめ）
        kernel1 = np.ones((5, 5), np.uint8)
        kernel2 = np.ones((3, 3), np.uint8)
        mask_inv = cv2.dilate(mask_inv, kernel1, iterations=1)
        mask_inv = cv2.erode(mask_inv, kernel2, iterations=1)

    # Now black-out the area of logo in ROI
    img1_bg = cv2.bitwise_and(roi, roi, mask=mask_inv)

    # Take only region of logo from logo image.
    img2_fg = cv2.bitwise_and(img2, img2, mask=mask)

    # Put logo in ROI and modify the main image
    dst = cv2.add(img1_bg, img2_fg)
    img1[x:x + w2, y:y + h2] = dst
    return img1
```

## ランダム回転

ランダム回転に使用している自作関数は、`cv2.warpAffine()`を利用している。

```python3:rotate.py
import cv2
import numpy as np

def rotate(img, angle, scale):
    """
    画像を回転（反転）させる
    [in]  img:   回転させる画像
    [in]  angle: 回転させる角度
    [in]  scale: 拡大率
    [out] 回転させた画像
    """

    size = img.shape[:2]
    mat = cv2.getRotationMatrix2D((size[0] // 2, size[1] // 2), angle, scale)
    return cv2.warpAffine(img, mat, size, flags=cv2.INTER_CUBIC)


def rotateR(img, level=[-10, 10], scale=1.2):
    """
    ランダムに画像を回転させる
    [in]  img:   回転させる画像
    [in]  level: 回転させる角度の範囲
    [out] 回転させた画像
    [out] 回転させた角度
    """

    angle = np.random.randint(level[0], level[1])
    return rotate(img, angle, scale), angle
```

以上。