アルファチャンネルつき png を透過画像で貼り付ける with Python/OpenCV


TL; DR

bg.jpg の左上に、png_image.png (アルファチャンネルつき画像)を重ねる場合のコードです。

import cv2

frame = cv2.imread("bg.jpg")
png_image = cv2.imread("alpha.png", cv2.IMREAD_UNCHANGED)  # アルファチャンネル込みで読み込む

# 貼り付け先座標の設定。とりあえず左上に
x1, y1, x2, y2 = 0, 0, png_image.shape[1], png_image.shape[0]

# 合成!
frame[y1:y2, x1:x2] = frame[y1:y2, x1:x2] * (1 - png_image[:, :, 3:] / 255) + \
                      png_image[:, :, :3] * (png_image[:, :, 3:] / 255)
bg.jpg alpha.png result

簡単な解説

PNG ファイルには「アルファチャンネル」という各ピクセルの透明度を表すデータが入っています。値域は RGB と同じく 0-255 です。255 のときに 100% 有効で、0 のときに 0% (完全に透明)になります。

# 画像の読み込み
png_image = cv2.imread("alpha.png", cv2.IMREAD_UNCHANGED)  # アルファチャンネル込みで読み込む

通常の cv2.imread() では、[h, w, 3]numpy.ndarray の形になりますが、 cv2.IMREAD_UNCHANGED を指定して cv2.imread() を呼び出すと、[h, w, 4] の形になります。BGRBGRA と、最後にアルファチャンネルがつきます。

画像を読み込めたら、合成します。といっても、NuPy の通常の行列演算で合成可能です。やっていることは、元々の背景画像・描画する画像を、それぞれアルファチャンネルの数値で配分して、足し合わせています。

# 合成!
frame[y1:y2, x1:x2] = frame[y1:y2, x1:x2] * (1 - png_image[:, :, 3:] / 255) + \
                      png_image[:, :, :3] * (png_image[:, :, 3:] / 255)

png_image[:, :, 3:] がアルファチャンネルの取り出しです。アルファチャンネルの値域は 0-255 なので、255 で割って 0-1 の比率にします。描画する画像には計算した比率を掛け、背景のほうには比率の "残り" を掛けて、両者を足し合わせることで、最終的な画像を得ることが出来ます。

ちなみに、png_image[:, :, 3] と書くと、行列のサイズが合わないと怒られるので注意してください(間違えた)。

余録

色々参考になるサイトはありつつ、そのものズバリなコードをうまく検索できなかったので、記事を作ってみました。(当たり前すぎてかえって書かなかったりするのかもしれませんね)

参考にさせてもらったサイト

画像は かわいいフリー素材集 いらすとや の素材を使わせてもらいました。