ユーニックス総合研究所

  • home
  • archives
  • python-pillow-image-alpha-composite

PythonのPillowのImage.alpha_composite()を使って画像をアルファブレンドする

  • 作成日: 2020-10-06
  • 更新日: 2023-12-26
  • カテゴリ: Python

PillowのImage.alpha_compositeの使い方

Pythonの画像処理ライブラリであるPillowには、画像を管理するImageモジュールがあります。
このImageモジュールは異なる2つの画像をアルファブレンドで合成する関数Image.alpha_composite()を持っています。

RGBAなどのアルファチャンネルを持っている画像同士、たとえば背景画像とキャラクターの画像の合成などに使うことが出来ます。
画像のアルファチャンネルの部分が透過されて、別の画像に重ね合わさる様に合成することが出来ます。
ゲーム系のプログラミングでは2次元のマップとキャラクターを合成するなど、アルファブレンドは必須の技術と言えます。
また、Webアプリでユーザーの投稿した画像をサービス側の背景画像と合成したいときなどにも使えます。

PillowのImageモジュールを使えば、それらの実現が簡単に行えます。
Imageモジュールにはアルファブレンドに関する関数が複数あります。
Image.alpha_composite()はその中の関数の1つです。

Image.alpha_composite()の特徴としては合成比を指定せずに単純なアルファブレンドを行う点があげられます。

アルファブレンドとは?

Image.alpha_composite()で実現できる「アルファブレンド」とはどういうものなのでしょうか?

RGBAなどの画像は「アルファ値」という画素の透明度に関する情報を持っています。
このアルファ値の値を低くすると画素が透明になり、アルファ値を高くすると画素が不透明になります。
アルファ値を中間の値にすると画素の透明度は中間になり、背景の画素が透けて見えるようになります。

アルファブレンドは映像表現の世界や、ゲームの世界で広く使われている必須技術です。
アルファブレンドができないと背景画像に人物などの画像を合成することが難しくなってしまいます。
合成自体はできますが、人物の背景を透かせることができないので不格好な合成になってしまいます。

アルファブレンドを使えば人物の背景の画素を透明にして、背景画像が見えるように人物の画像を合成することが可能になります。
これによって視覚的にリッチな表現が可能になります。

たとえばゲームでは、2次元のマップの上にメニューを表示したいとします。
メニューの形は角が丸まっていて、その背景はアルファ値を低くして透明になるようにします。
このようにしておいて背景画像の上にメニューを表示すれば、メニューの背景にマップの画像が透けて見えるようになり、自然な合成になります。

それから2次元のマップの上にアニメーションをするキャラクターを描画したいとします。
アニメーションをするキャラクターは複雑な輪郭をしていますが、輪郭の外の画素、つまりキャラクターの背景の画素は透けています。
そのためマップの上にそのキャラクターを合成すると、キャラクターはマップの背景上でアニメーションをしているようにプレイヤーの目には写ります。

このようにアルファブレンドは映像技術において必須の技術です。
これがないと始まらないと言ってもいいかもしれません。

使用する画像

今回使用する画像はこちらです。

どちらもRGBA横幅500px x 高さ500pxの画像です。
前者の方の画像の上に、後者の画像を合成する感じでやってみたいと思います。

🦝 < 執筆当時は海の季節でした

後者の画像は黒い線の他は透明の画素で構成されています。
そのため前者の画像の上に後者の画像を合成すると、黒い線が前面に見えるように合成されるはずです。

Image.alpha_compositeの構造

Image.alpha_composite()は↓のような構造を持っています。
この関数は引数を2つ取り、返り値を1つ返します。

```:::text
PIL.Image.alpha_composite(im1, im2)


↓は引数と返り値の詳細です。  

<h2 id="im1(第1引数)">im1(第1引数)</h2>  

第1引数にはアルファブレンドで合成したい画像(`Image`オブジェクト)を渡します。  
この画像は`RGBA`である必要があります。  

`RGB`などアルファチャンネルをもっていない画像を合成しようとすると↓のように`ValueError`になります。  

```text  
ValueError: image has wrong mode  

これは画像がアルファチャンネルを持っていないため、画素を透過することができないためです。
このためImage.alpha_composite()を使う場合はアルファチャンネルを持っている画像を使用するようにしましょう。

im2(第2引数)

第2引数には第1引数の画像とアルファブレンドで合成させたい画像(Imageオブジェクト)を渡します。
この画像もRGBAである必要があります。

この画像は第1引数の画像と同じサイズ(横幅と高さ)でなければなりません。
第1引数の画像と第2引数の画像のサイズが異なる場合、↓のようにValueErrorになります。

ValueError: images do not match  

2つの画像のサイズが異なるため、合成処理を実行できないからです。
Image.alpha_composite()は内部でC言語のfor文で画素を参照します。
for文は2重のループで、行列を参照するように画素を参照します。
そのため範囲外の画素の参照ができないようになっています。

返り値

返り値はアルファブレンドで合成され新しく作成された画像(Imageオブジェクト)です。
第1引数の画像と第2引数の画像がアルファブレンドされた結果がこの返り値のImageオブジェクトに結果として保存されます。

アルファブレンドで合成する

先ほどの2つの画像をImage.alpha_composite()で合成してみます。

まずImage.open()に画像ファイルのパスを指定して、海の画像を開きます。
この画像はImageオブジェクトで、↓のコードではa, b, cの変数がそれにあたります。
海の画像はaです。
続いて同じようにして笑っているスマイリーな画像を開きます。これはbです。

2つの画像を開いたらImage.alpha_composite()にその画像を渡して、アルファブレンドを実行します。
アルファブレンドが完了するとImage.alpha_composite()は返り値としてImageオブジェクトを返します。

Image.show()で画像を表示しても良いのですが、ここではディスクに画像を保存します。
Image.save()に保存する画像のパス(dst/out0.png)を指定し、アルファブレンドで生成した画像をディスクに保存します。

from PIL import Image  

a = Image.open('img/a.png')  # 海の画像を開く  
b = Image.open('img/smile.png')  # スマイルの画像を開く  
c = Image.alpha_composite(a, b)  # アルファブレンドする  

c.save('dst/out0.png')  # 画像をディスクに保存  

dst/out0.pngを開くと↓のような画像になっています。

見事、アルファブレンドで2つの画像が合成されました。

ソースコードの解析

Image.alpha_composite()は最終的にC言語で書かれたモジュールにたどり着きます。
Pillowでは速度を確保するためにC言語でPythonのモジュールを書いているんですね。

具体的にはAlphaComposite.cモジュール内のImagingAlphaComposite()関数がそうです。

この関数の最初の引数のチェックを見ると、↓のようになています。

    /* Check arguments */  
    if (!imDst || !imSrc ||  
        strcmp(imDst->mode, "RGBA") ||  
        imDst->type != IMAGING_TYPE_UINT8 ||  
        imDst->bands != 4) {  
        return ImagingError_ModeError();  
    }  

    if (strcmp(imDst->mode, imSrc->mode) ||  
        imDst->type  != imSrc->type  ||  
        imDst->bands != imSrc->bands ||  
        imDst->xsize != imSrc->xsize ||  
        imDst->ysize != imSrc->ysize) {  
        return ImagingError_Mismatch();  
    }  

かなり厳密に引数の画像(imDstimSrc)のチェックを行っています。
引数の画像がNULLの場合、または第1引数の画像のモードがRGBAじゃない場合などにモードエラーを送出しています。

2つ目のif文では第1引数の画像と第2引数の画像の情報が違う場合にミスマッチエラーを返しています。
具体的にはモードとタイプ、バンドの数と横幅と高さの値です。

関連する関数

Image.alpha_composite()の他にもImage.blend()というアルファブレンドを行う関数もあります。
こちらの関数はブレンドの量を引数で指定することが可能になっています。
詳しくは↓の記事をご覧ください。

問題

Q1: Image.alpha_composite()の第1引数として適当なものを答えよ

  1. dict
  2. list
  3. Imageオブジェクト

Q2: Image.alpha_composite()の返り値として適当なものを答えよ

  1. list
  2. dict
  3. Imageオブジェクト

Q3: Image.alpha_composite()に渡す画像のモードとして適当なものを答えよ

  1. L
  2. RGB
  3. RGBA

問題の正解はこちら↓

Q1: 3
Q2: 3
Q3: 3