LoginSignup
0
0

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

Last updated at Posted at 2024-04-04

はじめに

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

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

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

やったこと

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

完成品↓
output.gif

操作方法↓
image.png

BG.png (アルファなし)↓
BG.png
char.png (アルファ付き)↓
char.png

ソースコード
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で生成するなら

img_back=np.zeros((HEIGHT, WIDTH, 3),dtype=np.uint8)
img_back+=255 #(255,255,255)の色を生成して真っ白にする

とすること。引数の順番が (高さ,幅,ビット深さ) と、高さが先な点に注意。
または、RGB値を指定して任意色で塗りつぶすなら

color=(B,G,R)
img_back = np.full((HEIGHT, WIDTH, 3), color, np.uint8)

とすることもできる(もちろん(255,255,255)なら真っ白にもできる。)
いずれにせよ、dtype=np.uint8 はとてもとても大事なので注意すること

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0