python/OpenCVで透過pngをオーバレイする

昨日のリアルタイムな笑い男を実装するために使っていた画像の合成っていうかオーバレイっていうか上書きっていうか、とにかく笑い男をかぶせる奴。ググってもなかなかそれっぽい情報が出てこないので記録。

レナさんの画像に適当に書いた星マークを乗っけます。

レナさん 適当な五芒星

とりあえず透過とか考えずに乗っけるだけ乗っけたバージョン。

import cv2

src = cv2.imread('star.png')  # 乗っけたい画像。星。
dst = cv2.imread('lena.jpg')  # 下敷きになる画像。レナさん。

width, height = src.shape[:2]  # サイズを取得しておく。

# dst[上のy座標:下のy座標, 左のx座標:右のx座標]
dst[0:height, 0:width] = src

cv2.imwrite('out.jpg', dst)

凄くシンプル。

結果はこんな感じ。

透過を無視して上書きした画像

で、アルファチャンネルを考慮して乗っけるバージョン。

import cv2

src = cv2.imread('star.png', -1)  # -1を付けることでアルファチャンネルも読んでくれるらしい。
dst = cv2.imread('lena.jpg')

width, height = src.shape[:2]

mask = src[:,:,3]  # アルファチャンネルだけ抜き出す。
mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)  # 3色分に増やす。
mask = mask / 255  # 0-255だと使い勝手が悪いので、0.0-1.0に変更。

src = src[:,:,:3]  # アルファチャンネルは取り出しちゃったのでもういらない。

dst[0:height:, 0:width] *= 1 - mask  # 透過率に応じて元の画像を暗くする。
dst[0:height:, 0:width] += src * mask  # 貼り付ける方の画像に透過率をかけて加算。

cv2.imwrite('out.jpg', dst)

ちょっと長くなった。

結果はこんな。

透過を考慮して乗っけた画像

2020-06-19 追記

最新の環境(Python 3.8 / OpenCV 4.2)でも動くようにプログラムを更新しました。