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]
の形になります。前者ではおなじみの BGR
形式になり、後者だと BGRA
と最後にアルファチャンネルがつきます。
画像を読み込めたら、合成します。といっても、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]
と書くと、行列のサイズが合わないと怒られるので注意してください(間違えた)。
余録
色々参考になるサイトはありつつ、そのものズバリなコードをうまく検索できなかったので、記事を作ってみました。(当たり前すぎてかえって書かなかったりするのかもしれませんね)
おまけ: 関数化バージョン・はみ出した座標に対応
import cv2
import numpy as np
def alpha_blend(frame: np.array, alpha_frame: np.array, position: (int, int)):
"""
frame に alpha_frame をアルファブレンディングで描画する。
:param frame: ベースとなるフレーム。frame に直接、書き込まれるので、中身が変更される。
:param alpha_frame: 重ね合わる画像フレーム。アルファチャンネルつきで読み込まれている前提。
:param position: alpha_frame を描画する座標 (x, y)。負の値などはみ出る値も指定可能。
:return: 戻り値はなし。frame に直接、描画する。
usage:
base_frame = cv2.imread("bg.jpg")
png_image = cv2.imread("alpha.png", cv2.IMREAD_UNCHANGED) # アルファチャンネル込みで読み込む
alpha_blend(base_frame, png_image, (1500, 300))
"""
# 貼り付け先座標の設定 - alpha_frame がはみ出す場合への対処つき
x1, y1 = max(position[0], 0), max(position[1], 0)
x2 = min(position[0] + alpha_frame.shape[1], frame.shape[1])
y2 = min(position[1] + alpha_frame.shape[0], frame.shape[0])
ax1, ay1 = x1 - position[0], y1 - position[1]
ax2, ay2 = ax1 + x2 - x1, ay1 + y2 - y1
# 合成!
frame[y1:y2, x1:x2] = frame[y1:y2, x1:x2] * (1 - alpha_frame[ay1:ay2, ax1:ax2, 3:] / 255) + \
alpha_frame[ay1:ay2, ax1:ax2, :3] * (alpha_frame[ay1:ay2, ax1:ax2, 3:] / 255)
alpha_frame
がはみ出すケースにも対応しています。ただし、完全にはみ出している position
を指定した場合は、例外が出ます。
参考にさせてもらったサイト
画像は かわいいフリー素材集 いらすとや の素材を使わせてもらいました。