OpenCV(Python3)で透過画像を回転して合成する

# はじめに
背景画像と透過画像を用意して、背景画像の上で透過画像を舞わせたい。
やりたいことの大半は下の記事と全く同じ↓

https://qiita.com/mo256man/items/a32ddf5ddbaaf767d319

しかしこの記事のやり方では透過がうまくいっていないようだ。
透過画像の合成について書かれた記事を見つけた↓

https://qiita.com/smatsumt/items/923aefb052f217f2f3c5

要はこの2記事の合わせ技である。ソースコードもほぼコピペなのだ…

# やったこと
BG.pngの上にchar.pngを重ねて、移動、回転、拡大縮小ができるようにした

完成品↓
![output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3719521/9a270f01-6871-ec2b-65d6-9984b2e2bb2a.gif)

操作方法↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3719521/90982721-529e-ffb8-a7c5-3255f59f1a47.png)

BG.png (アルファなし)↓
![BG.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3719521/3ccf8131-710e-5f1d-d43e-f6087b9754de.png)
char.png (アルファ付き)↓
![char.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3719521/6c5f3694-80e4-70bf-cd88-ffaf8b18ae3a.png)


```py:ソースコード
import cv2
import numpy as np

def main():
    filename_back = "BG.png"
    filename_front = "char.png"
    img_back = cv2.imread(filename_back)
    img_front = cv2.imread(filename_front, -1) #アルファチャンネルを読み込む

    pos=[50,500] #char.pngを表示する座標(左上)
    xc, yc = int(img_front.shape[0]/2),int(img_front.shape[1]/2) #前景画像の回転中心はchar.pngの中心
    angle=0
    size=1

    while True:
        back = img_back.copy() #直接上書きされないようにコピー
        img = putSprite_mask2(back, img_front, pos, angle, (xc,yc), size) #(x,y)からposに変更、sizeを追加

        cv2.putText(img, f"angle={angle}", (10,30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2)
        cv2.putText(img, f"pos={pos}", (10,60), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2)
        cv2.putText(img, f"size={size}", (10,90), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2)
        cv2.imshow("putSprite_mask2", img)

        key = cv2.waitKey(1) & 0xFF
        if key == 27: #escキー
            break
        elif key == ord("w"): #上へ
            pos[1]-=1
        elif key == ord("s"): #下へ
            pos[1]+=1
        elif key == ord("a"): #左へ
            pos[0]-=1
        elif key == ord("d"): #右へ
            pos[0]+=1
        elif key == ord("q"): #左回転
            angle+=1
        elif key == ord("e"): #右回転
            angle-=1
        elif key == ord("r"): #大きく
            size+=0.1
        elif key == ord("f"): #小さく
            size-=0.1

def putSprite_mask2(back, front4, pos, angle=0, center=(0,0), size=1):
    x, y = pos
    xc, yc = center
    fh, fw = front4.shape[:2]
    bh, bw = back.shape[:2]

    # 回転中心と四隅の距離の最大値を求める
    pts = np.array([(0,0), (fw,0), (fw,fh), (0,fh)])
    ctr = np.array([(xc,yc)])
    r = int(np.sqrt(max(np.sum((pts-ctr)**2, axis=1)))) #回転中心から最も遠い頂点の直線距離
    s=abs(size) #追記
    r=int(r*s) if s>1 else r #追記

    # 回転する
    M = cv2.getRotationMatrix2D((xc,yc), angle, size) #1からsizeに変更
    M[0][2] += r - xc
    M[1][2] += r - yc
    imgRot = cv2.warpAffine(front4, M, (2*r,2*r))  # 回転画像を含む外接四角形

    # 外接四角形の全体が背景画像外なら何もしない
    x0, y0 = x+xc-r, y+yc-r
    if not ((-2*r < x0 < bw) and (-2*r < y0 < bh)) :
        return back

    # 外接四角形のうち、背景画像内のみを取得する
    x1, y1 = max(x0,  0), max(y0,  0)
    x2, y2 = min(x0+2*r, bw), min(y0+2*r, bh)
    imgRot = imgRot[y1-y0:y2-y0, x1-x0:x2-x0]

    # マスク手法で外接四角形と背景を合成する
    #front_roi = imgRot[:, :, :3]
    #mask1 = imgRot[:, :, 3]
    #mask_roi = 255 - cv2.merge((mask1, mask1, mask1))
    #roi = back[y1:y2, x1:x2]
    #tmp = cv2.bitwise_and(roi, mask_roi)
    #tmp = cv2.bitwise_or(tmp, front_roi)
    #back[y1:y2, x1:x2] = tmp  #これでは透過できていなかった
    back[y1:y2, x1:x2] = back[y1:y2, x1:x2] * (1-imgRot[:,:,3:]/255) + \
                         imgRot[:,:,:3] * (imgRot[:,:,3:]/255)

    return back

if __name__=="__main__":
    main()
```
背景画像をBG.pngにしているが、もし背景画像をnp.zerosで生成するなら
```py
img_back=np.zeros((HEIGHT, WIDTH, 3),dtype=np.uint8)
img_back+=255 #(255,255,255)の色を生成して真っ白にする
```
とすること。引数の順番が (高さ,幅,ビット深さ) と、高さが先な点に注意。
または、RGB値を指定して任意色で塗りつぶすなら
```py
color=(B,G,R)
img_back = np.full((HEIGHT, WIDTH, 3), color, np.uint8)
```
とすることもできる(もちろん(255,255,255)なら真っ白にもできる。)
いずれにせよ、**dtype=np.uint8 はとてもとても大事なので注意すること**