search
LoginSignup
22

posted at

updated at

アルファチャンネルつき 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
bg_moon_sky_mangetsu_building.jpg stand_naname3_man.png testout.jpg

最後に、関数化したバージョンも載せています。

簡単な解説

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 を指定した場合は、例外が出ます。

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

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

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
What you can do with signing up
22