ソースに絡まるエスカルゴ

貧弱プログラマの外部記憶装置です。

【python/OpenCV】背景画像にアルファ画像を合成する方法

 今回はpythonOpenCVを使って背景画像にアルファ画像を合成する方法の備忘録になります。

 では始めます。


1:背景画像とアルファ画像を用意する
 背景画像と合成用のアルファ画像を用意します。

 今回は以下の画像を使った前提で進めていきますので、必要な方は右クリックから画像を保存してください。

・背景画像(bg.jpg)
f:id:rikoubou:20190514142824j:plain

・合成用アルファ画像(fg.png)
f:id:rikoubou:20190514142856p:plain


2:背景画像にアルファ画像を合成する関数
 結論を先に書くと以下の関数でできます。

import cv2

# 画像を合成する関数(s_xは画像を貼り付けるx座標、s_yは画像を貼り付けるy座標)
def merge_images(bg, fg_alpha, s_x, s_y):
    alpha = fg_alpha[:,:,3]  # ①アルファチャンネルだけ抜き出す(要は2値のマスク画像)
    alpha = cv2.cvtColor(alpha, cv2.COLOR_GRAY2BGR) # grayをBGRに
    alpha = alpha / 255.0    # 0.0〜1.0の値に変換

    fg = fg_alpha[:,:,:3]

    f_h, f_w, _ = fg.shape # アルファ画像の高さと幅を取得
    b_h, b_w, _ = bg.shape # 背景画像の高さを幅を取得

    # 画像の大きさと開始座標を表示
    print("f_w:{} f_h:{} b_w:{} b_h:{} s({}, {})".format(f_w, f_h, b_w, b_h, s_x, s_y))

    bg[s_y:f_h+s_y, s_x:f_w+s_x] = (bg[s_y:f_h+s_y, s_x:f_w+s_x] * (1.0 - alpha)).astype('uint8') # ②アルファ以外の部分を黒で合成
    bg[s_y:f_h+s_y, s_x:f_w+s_x] = (bg[s_y:f_h+s_y, s_x:f_w+s_x] + (fg * alpha)).astype('uint8')  # ③合成

    return bg

 
 ①のアルファ部分を取得した結果は以下のようになります。アルファ部分がマスクとなって取得されています。
f:id:rikoubou:20190514143216p:plain

 ②で合成した結果が以下のようになります。背景に①のマスクの白い部分が黒色で合成されています。
f:id:rikoubou:20190514143329p:plain

 そして最後に③の行で、②の黒い部分にアルファ以外の部分が合成されて完成です。
f:id:rikoubou:20190514143427p:plain


3:サンプルプログラム
 2で背景画像にアルファ画像を合成する方法がわかったので、以下のようなサンプルプログラムを作成しました。

・alpha_add.py

import cv2
from datetime import datetime

BG_PATH    = "./img/bg.jpg"   # 背景画像
ALPHA_PATH = "./img/fg.png" # 合成アルファ画像
ALPHA_SCALE = 1.0

# メイン関数
def main():
    add_img = load_alphaImage(ALPHA_PATH, ALPHA_SCALE)
    bg_img  = cv2.imread(BG_PATH)

    bg_img = merge_images(bg_img, add_img, 0, 0) # 座標を指定してアルファ画像を合成
    save_image(bg_img) # 画像を保存

# アルファ画像を読み込む関数
def load_alphaImage(path, scale):
    add_img = cv2.imread(path, -1) # アルファチャンネルで読み込み
    add_img = img_resize(add_img, scale) # リサイズ
    return add_img

# 画像をリサイズする関数
def img_resize(img, scale):
    h, w  = img.shape[:2]
    img = cv2.resize(img, (int(w*scale), int(h*scale)) )
    return img

# 画像を保存
def save_image(img):
    date = datetime.now().strftime("%Y%m%d_%H%M%S")
    path = "./result/" + date + ".png"
    cv2.imwrite(path, img) # ファイル保存

# 画像を合成する関数(s_xは画像を貼り付けるx座標、s_yは画像を貼り付けるy座標)
def merge_images(bg, fg_alpha, s_x, s_y):
    alpha = fg_alpha[:,:,3]  # アルファチャンネルだけ抜き出す(要は2値のマスク画像)

    alpha = cv2.cvtColor(alpha, cv2.COLOR_GRAY2BGR) # grayをBGRに
    alpha = alpha / 255.0    # 0.0〜1.0の値に変換

    fg = fg_alpha[:,:,:3]

    f_h, f_w, _ = fg.shape # アルファ画像の高さと幅を取得
    b_h, b_w, _ = bg.shape # 背景画像の高さを幅を取得

    # 画像の大きさと開始座標を表示
    print("f_w:{} f_h:{} b_w:{} b_h:{} s({}, {})".format(f_w, f_h, b_w, b_h, s_x, s_y))

    bg[s_y:f_h+s_y, s_x:f_w+s_x] = (bg[s_y:f_h+s_y, s_x:f_w+s_x] * (1.0 - alpha)).astype('uint8') # アルファ以外の部分を黒で合成
    bg[s_y:f_h+s_y, s_x:f_w+s_x] = (bg[s_y:f_h+s_y, s_x:f_w+s_x] + (fg * alpha)).astype('uint8')  # 合成

    return bg

if __name__ == '__main__':
    main()

 上記の内容をコピペして「alpha_add.py」という名前で保存した後、imgとresultフォルダを同階層に作成してimgフォルダ内に背景画像と合成用アルファ画像を配置します。
f:id:rikoubou:20190514144256p:plain

 この状態でalpha_add.pyを実行すると以下のように結果が得られます。
f:id:rikoubou:20190514143427p:plain

 背景画像の画面サイズよりも合成用アルファ画像の画像サイズが大きい場合はエラーとなるため、ALPHA_SCALEの値を0.5(半分のサイズ)などに変更して実行してください。

 またmerge_images関数の第3引数、第4引数の値を変更すれば、貼り付ける位置を変更できます。


 以上が背景画像にアルファ画像を合成する方法です。

 背景画像をWebカメラから取得した画像に変更すれば、装飾フレームのようにすることもできそうです。

 今回作成したサンプルプログラムも一応公開しておきます。


・参考資料