Pythonでの差分画像の作り方【NumPy・PIL】

Pythonでの差分画像の作り方解説ページアイキャッチ

このページでは Python での差分画像の作り方について解説していきたいと思います。

C言語での差分画像の作り方を下記ページで紹介していますが、Python であれば NumPy や PIL を利用することでより簡単に差分画像を作ることができます。

差分画像の作成方法の解説ページアイキャッチ【C言語】差分画像を作成する

C言語でも結構簡単だったけどね!

Python の場合はもっと簡単だよ!

NumPy 使えばループなしで差分画像作れるし、PIL を使うことでより簡単に画像の操作を行うことができる

上記のページでは大きく分けて下記の5つの種類の差分画像の作り方を解説しました。

  • 単純な差分画像を作る
  • 差分のある輝度値を 255 にする
  • 差分の大きな輝度値を 255 にする
  • 元画像に対して差分のある画素に色をつける
  • 元画像に対して差分のある画素に色を混ぜる

このページではこれらの作り方を Python バージョンで解説していきたいと思います。

NumPy での画像の扱い

まず最初に、今後の解説がより分かりやすくなるように NumPy での画像の扱い方や差分画像を作成する時に利用する処理について簡単に説明しておきます。

NumPy についてご存知の方は次の単純な差分画像を作るまでスキップしていただければと思います。

画像の NumPy 配列への変換

NumPy を用いて画像を扱う場合、まず必要になるのが「画像の NumPy 配列(ndarray)への変換」処理です。

要は .jpg.png などの画像ファイルから、画像の各画素の輝度値を格納した配列(多次元行列)に変換します。

NumPy 配列に変換してしまえば、あとは行列演算等を用いて画像処理を簡単に行うことができます。

画像を PIL で読みこむ場合、下記により「画像の NumPy 配列(ndarray)への変換」を行うことができます。

画像のNumPy配列への変換
import numpy as np
from PIL import Image

# 画像の読み込み
image = Image.open("image.png")

# NumPy配列へ変換
im_arr = np.array(image)

要は PIL で画像を読み込んで画像オブジェクトを生成し、その画像オブジェクトに対して NumPy の array 関数を実行することで画像を NumPy 配列に変換することができます。

im_arr がその変換後の NumPy 配列で、この配列に対して行列演算等を行なって画像処理等を実行することができます。

なんで画像処理するのにわざわざ NumPy に変換するの?

PIL で画像処理できるじゃん…

もちろん PIL でできる画像処理は PIL の関数やメソッドを使って実行すればいいよ

だけどそれ以外の画像処理を行う場合は自身でその処理をプログラミングしてあげる必要があるよね

そのプログラミングを簡単に行うために NumPy 配列に変換するんだ

画像処理には行列演算により実現できるものが非常に多くあります。

NumPy 配列を用いれば、この行列演算をより直感的にプログラミングすることができ、さらに高速に処理させることができます。

スポンサーリンク

NumPy 配列から画像への変換

ただし、NumPy 配列は結局は配列であり、画像ではありません。

ですので、NumPy 配列に対して処理を行なった結果を画像として扱いたい(例えば画像として画面に表示したり画像としてファイル保存したりしたい)場合は、NumPy 配列から画像への変換が必要になります。

これも PIL の関数を実行することで簡単に変換することができます。

NumPy配列の画像への変換
import numpy as np
from PIL import Image

# 画像への変換
image = Image.fromarray(im_arr)

im_arr が NumPy 配列で、これを画像に変換したものが image になります。

PIL 画像オブジェクトの場合、下記のように saveshow メソッド実行させることで画像の保存や表示も行うことができます。

画像の保存・表示
# imageはPIL画像オブジェクト

# 画像保存(PNG)
image.save("gazou.png")

# 画像表示
image.show()

NumPy 配列の構造

では画像を変換して作成した NumPy 配列は具体的にはどのようなものになるでしょうか?これについて解説していきたいと思います。

配列の中身は輝度値

画像を変換することで作成された NumPy 配列は多次元配列になります。

カラー画像だと3次元配列で、グレースケール画像だと2 or 3次元配列になります。

この配列の各軸(各次元)はそれぞれ下記を表します

  • 第1軸:横方向の座標
  • 第2軸:縦方向の座標
  • 第3軸:色

グレースケールの場合は画像の色は1つのみで表されることがあり、この場合は2次元配列として扱われます。

例えばカラー画像(RGB カラー画像)を NumPy 配列に変換した場合、この配列を図で表すと下のような構造になります。

NumPy配列の構成

1つ1つの四角に値を格納することができます。この四角を今後は要素と呼びます。

要素に設定できるのは単なる値で、色 RGB(赤緑青)に格納されている値によって、その座標の画素の色が決まります。

画素の色

この画素の色を決定する値を輝度値と言います。輝度値が大きいほど、その色の度合いが強くなります。

例えば画像から作成した NumPy 配列 im_arr に対して下記を実行すれば、画像の座標 (100, 200) の緑色の輝度値を表示することができます。

輝度値の表示
print(im_arr[100][200][1])

第3軸は RGB 画像の場合、指定する値によってアクセスする輝度値が下記のように変わります。

  • 0:R(赤)の輝度値
  • 1:G(緑)の輝度値
  • 2:B(青)の輝度値

ただし、これは画像を PIL で読み込んだ場合です。OpenCV で画像を読み込んだ場合は値と色の関係が上記と逆になるので気をつけてください。

  • 2:R(赤)の輝度値
  • 1:G(緑)の輝度値
  • 0:B(青)の輝度値

画像を扱う NumPy 配列の型は基本的に uint8

で、ポイントはこの配列に格納される値の “型” です。

型?!

Python だと変数の型は勝手に決まるものだと思ってたよ…

NumPy の場合はこの型によって配列内で扱える値の範囲が変わるんだ

なので、NumPy を使う場合は特にこの型をしっかり理解しておいた方がいいよ

NumPy 配列では、この “型” によって、配列に格納できる値の範囲が決まります。

この “型” は下記のように NumPy 配列の type 属性に格納されています。

型の表示
print(im_arr.type)

さらに、この型は astype メソッドで変換することが可能です(より正確にいうと型変換後の NumPy 配列を生成することが可能)。下記は im_arr の型を int16 に変換する例になります。

型の変換
im_arr_i16 = im_arr.astype(np.int16)

整数のみを扱う場合は、使用する型は intN, uintNN はビット数を指定)のみになります。

intuint には下記のような違いがあります。

  • int:符号あり(負の値も表現可能)
  • uint:符号なし(負の値は表現不可能)

また N によって表現できる数値の範囲が変わります。下記は型ごとの表現できる数値の範囲の一例です。

  • int8-128127
  • uint80255
  • int16-3276832767
  • uint16065535

で、基本的に一般的な画像を読み込んだ場合、作成される NumPy 配列の型は  uint8 になります。

つまり、画像から変換した NumPy 配列には、そのままだと負の値や 255 を超える値を配列に格納しようとしても、0255 の値に丸められてしまうので注意してください。

また NumPy 配列から画像を作成する場合も、NumPy 配列の型は uint8 である必要があります(もしかしたら他の型で良い場合もあるのかも)。

ですので、uint8 以外の型の NumPy 配列から画像に変換するような場合は、型を uint8 に変換した後に画像への変換を行う必要があります。

NumPy 配列に対する基本的な演算

続いて差分画像を作成する上で必要になる NumPy 配列に対する処理について解説しておきます。

ループなどを使わなくても様々な処理が行える点がポイントです!

スカラー値との演算

NumPy 配列とスカラー値(単なる値)との四則演算や論理演算では、「配列の全要素に対してそのスカラー値との演算」が実行され、その演算結果を各要素の値とした NumPy 配列が生成されます。

例えば下記のスクリプトでは、2次元の NumPy 配列 A の各要素に b の値を足した結果を各要素とした NumPy 配列が生成されます(その配列を C が参照)。

スカラー値との演算
import numpy as np

A = np.array([[1, 2], [3, 4]])
b = 5

# NumPy配列とスカラー値との演算
C = A + b

print(C)

print(C) の結果は下記のようになります。

[[6 7]
 [8 9]]

ループ使わなくてもこんな演算できちゃうんだね…

便利!

そうだね!

その分簡潔に、直感的にプログラミングできるよ!

スカラー値との比較

NumPy 配列とスカラー値(単なる定数)との比較では、「配列の全要素に対してそのスカラー値との比較」が実行され、その比較結果(True or False)を各要素の値とした NumPy 配列が生成されます。

この生成される NumPy の型は bool になり、True / False のみが格納できる配列になります。

例えば下記のスクリプトでは、2次元の NumPy 配列 A の各要素が b よりも大きいかどうかを判断した結果が格納された NumPy 配列が生成されます(その配列を C が参照)。

スカラー値との比較
import numpy as np

A = np.array([[1, 2], [3, 4]])
b = 2

# NumPy配列とスカラー値との比較
C = A > b

print(C)

print(C) の結果は下記のようになります。

[[False False]
 [ True  True]]

演算や比較で型が変わることもあるんだね!

元々の NumPy 配列の方が変わるわけではないんだけど、結果として生成される NumPy 配列の型は演算に用いた NumPy 配列とは異なることがあるよ

例えば、比較すると bool になったり、演算結果が浮動小数点数になると float64 になったりするのでこの辺りは注意が必要だね

同じサイズの配列との演算

同じサイズの NumPy 配列同士の四則演算や論理演算では、「配列の同じ位置の要素同士に対して演算」が実行され、その演算結果を各要素の値とした NumPy 配列が生成されます。

例えば下記のスクリプトでは、2次元の NumPy 配列 AB の同じ位置の要素同士の演算結果を要素とした NumPy 配列が生成されます(その配列を C が参照)。

配列同士の演算
import numpy as np

A = np.array([[1, 2], [3, 4]])
B = np.array([[3, 4], [5, 6]])

# NumPy配列同士の演算
C = A * B

print(C)

print(C) の結果は下記のようになります。

[[ 3  8]
 [15 24]]

スカラー値と配列とで演算の仕方が変わるのか…

そうだね…

なので、演算に用いる変数がスカラー値か NumPy 配列かは意識しながらプログラミングした方がいいね

スポンサーリンク

NumPy 配列のスライス

NumPy 配列においても通常のリストなどと同様にスライスが活用できます。

例えば、RGB の3色から構成される3次元の NumPy 配列を各色ごとの2次元の NumPy 配列にスライスすることができます。

NumPy配列のスライス

このようにスライスするためには、下記のように第3軸に対してスライスするように、第3軸の値のみを指定します。

NumPy配列のスライスによる選択
r_arr = im_arr[:, :, 0]
g_arr = im_arr[:, :, 1]
b_arr = im_arr[:, :, 2]

また、スライスに対して代入を行うこともできます。右辺がスカラー値の場合はブロードキャストされてそのスライスに値が代入されることになります。

例えば下記は、スライスを利用して第3軸が 2 の要素全てに対して 255 を代入する例になります。

NumPy配列のスライスによる代入
im_arr[:, :, 2] = 255

他の軸でスライスすることで、特定の座標の画素のみを取得したりすることも可能です。

単純な差分画像を作る

では Python での差分画像の作り方について解説していきたいと思います。

まずは2つの画像の輝度値の差を画像として表現した「単純な差分画像」を作っていきたいと思います。

スポンサーリンク

スポンサーリンク

作り方

差分画像の作り方の基本的な考え方は、2つの画像の各輝度値の差分の計算です。

差分画像作成のイメージ

この差分を画像の輝度値にしてしまえば、2つの画像の差を画像として表現することができます。

差分配列の作り方

で、画像から作成した NumPy 配列であれば、配列同士の引き算でこれを実行することができます。

差分配列の作成
# im1とim2はNumPy配列
diff = im1 - im2

この引き算で生成される配列を、このページでは差分配列と呼ばせていただきます。

型を考慮した差分配列の作り方

上記により各輝度値の差を求めることができるのですが、”型” に注意する必要があります。

前述の通り、一般的な画像から NumPy 配列を作成すると型が uint8 になります。

さらに型が uint8 同士の NumPy 配列の引き算結果も型が uint8 の NumPy 配列になります。

ですので、引き算を行なって負の値が発生しても 0255 の値に強制的に丸められてしまいます。つまり差分が上手く表現できなくなってしまいます。

なので、引き算を行う際には、型を事前に負の値も扱えるように変換しておく必要があります。

下記は事前に配列の型を int16 に変換してから差分配列を生成する例になります。

型変換後の差分配列の作成
# im1_u8とim2_u8はuint8のNumPy配列
im1_i16 = im1_u8.astype(np.int16)
im2_i16 = im2_u8.astype(np.int16)

diff_i16 = im1_i16 - im2_i16

差分配列の正規化

画像から作成した NumPy 配列の各要素が取りうる値は 0255 ですので、差分配列の各要素は -255 〜 255 の値を取りうることになります。

ただし、一般的な画像では輝度値は 0255 の値で表現する必要があります。

なので、まず全要素に対して +256 を行って負の値を全て正の値に変換し、さらにその結果を 2 で割ることで配列の全要素の値が 0255 に収まるようにしてから画像の変換を行いたいと思います。

上記の演算は下記により行うことができます。

輝度値の正規化
diff_n_i16 = ((diff_i16 + 256) // 2)

NumPy 配列とスカラー値の四則演算ですので、NumPy 配列の各要素に対してスカラー値との四則演算が行われ、その結果が格納された NumPy 配列(diff_n_i16)が生成されることになります。

画像への変換

最後にこの NumPy 配列を画像に変換すれば差分画像の完成です。

先程生成した型は int16 ですので、uint8 に変換してから画像に変換します。

画像への変換
# NumPy配列をnp.uint8型に変換
diff_u8 = diff_n_i16.astype(np.uint8)

# PIL画像に変換
diff_img = Image.fromarray(diff_u8)

スポンサーリンク

スポンサーリンク

スクリプト

ここまでの解説を踏まえて単純な差分画像を作成するスクリプトは下記のようになります。

単純な差分画像を作る
import numpy as np
from PIL import Image
import sys

# 画像の読み込み
image1 = Image.open("org_1.png")
image2 = Image.open("org_2.png")

# RGB画像に変換
image1 = image1.convert("RGB")
image2 = image2.convert("RGB")

# NumPy配列へ変換
im1_u8 = np.array(image1)
im2_u8 = np.array(image2)

# サイズや色数が違うならエラー
if im1_u8.shape != im2_u8.shape:
    print("サイズが違います")
    sys.exit()

# 負の値も扱えるようにnp.int16に変換
im1_i16 = im1_u8.astype(np.int16)
im2_i16 = im2_u8.astype(np.int16)

# 差分配列作成
diff_i16 = im1_i16 - im2_i16

'''ここから作成する画像によって異なる処理'''

# np.uint8型で扱える値に変換
diff_n_i16 = ((diff_i16 + 256) // 2)

# NumPy配列をnp.uint8型に変換
diff_u8 = diff_n_i16.astype(np.uint8)

# PIL画像に変換
diff_img = Image.fromarray(diff_u8)

'''ここまで作成する画像によって異なる処理'''

# 画像表示
diff_img.show()

最初のスクリプト紹介なので、ここまで解説してこなかった内容を中心にここで説明しておきます。

変数名

NumPy 配列の変数には型が分かるような変数名をつけています。

  • u8uint8
  • i16int16
  • boolbool

RGB 画像への変換

下記で画像を RGB 画像に変換しています。これはグレースケール画像やアルファチャンネル付きの画像も同様の処理で差分画像作成ができるようにするための変換になります。

RGBへの変換
# RGB画像に変換
image1 = image1.convert("RGB")
image2 = image2.convert("RGB")

エラーチェック

今回紹介する差分画像の作り方は、2つの画像のサイズが同じであることを前提としたものになります。

ですので、2つの画像のサイズが異なる場合には下記でエラーとして扱い、スクリプトを即座に終了するようにしています。

エラーチェック
# サイズや色数が違うならエラー
if im1_u8.shape != im2_u8.shape:
    print("サイズが違います")
    sys.exit()

作成する画像によって異なる処理

ここから様々な差分画像の作成スクリプトについて解説していきますが、今回紹介したスクリプトと異なるのは下記の部分のみになります。

作成する画像によって異なる処理
'''ここから作成する画像によって異なる処理'''

# 〜処理〜

'''ここまで作成する画像によって異なる処理'''

ですので、今後紹介するスクリプトでは特に上記の部分に注目して内容を確認していただければと思います。

スポンサーリンク

スクリプト実行結果

ではスクリプトを実行してどのような差分画像が作成できるかを確認していきましょう!

差分をとる1つ目を下の画像(org_1.png)、

差分をとる画像の1つ目

2つ目を下の画像(org_2.png)として差分画像を作成したいと思います。

差分をとる画像の2つ目

一見同じ画像に見えますが、スクリプトを実行すると下の図のような画像が表示されます。

差分画像1

ほぼグレーの画像ですね。一番多いのは R=128, G=128, B=128 のグレーの画素で、これは2つの画像に差がない画素になります。

逆にそれ以外の色の画素は差がある画素になります。

上の例だと黒に近いグレーの部分があり、ヒゲ部分に差があることが確認できます。

ただ、実は上記の2つの画像ではヒゲ以外の部分にも差分があります。どのあたりに差分があるかがもっと分かりやすくなるように、ここから色んな差分画像を作成していきたいと思います。

差分のある輝度値を 255 にする

次は2つの画像の輝度値の差を全て 255 として表現した差分画像を作っていきたいと思います。

差のある輝度値を全て255にした例

このような画像を作成することで、2つの画像のどこに差があるかを簡単に確認することができます。

スポンサーリンク

スポンサーリンク

作り方

先程の例では差分をそのまま差分画像に変換しましたが、今度は差分をより強調して差分画像を作成していきます。

要は、差分配列の要素の値が 0 でない要素は 255 (つまり輝度値の最大値)に、それ以外は 0 とした配列を作成し、それを画像に変換することで差分画像を作成します。

まず、下記の差分配列とスカラー値との比較により、比較結果を要素とした NumPy 配列を作成します。

0以外であるかの判断
# diff_i16は差分配列
diff_bool = diff_i16 != 0

上記では 0 以外であるかどうかを判断していますので、差分配列の要素が 0 である位置には False が、差分配列の要素が 0 以外である位置には True が格納された NumPy 配列が生成されます(diff_bool が参照)。

生成された NumPy 配列の型は bool なので、数値として扱うために uint8 型に変換します。

uint8への変換
diff_bin_u8 = diff_bool.astype(np.uint8)

これにより、False0 に、True1 に変換された NumPy 配列が生成されます(diff_bin_u8 が参照)。

ですので、最後にスカラー値 255 を掛けてやれば、この NumPy 配列の全要素に対して 255 を掛けることができます。

1を255に変換
diff_u8 = diff_bin_u8 * 255

つまり、0 の要素は 0 のまま、1 の要素は 255 に変換された NumPy 配列が生成されます(diff_u8 が参照)。

最初に使用した差分配列から考えると、目的としていた差分配列の要素の値が 0 でない要素は 255 に、それ以外は 0 とした配列が作成できたことになります。

最後にこの NumPy 配列を画像に変換してやれば、差分のあった輝度値のみを 255 として差分を強調した画像を作成することができます。

画像への変換
# PIL画像に変換
diff_img = Image.fromarray(diff_u8)

スポンサーリンク

スポンサーリンク

スクリプト

ここまでの解説を踏まえて作成した「差分のある輝度値を 255 にする」スクリプトは下記のようになります。

差分のある輝度値を255にする
import numpy as np
from PIL import Image
import sys

# 画像の読み込み
image1 = Image.open("org_1.png")
image2 = Image.open("org_2.png")

# RGB画像に変換
image1 = image1.convert("RGB")
image2 = image2.convert("RGB")

# NumPy配列へ変換
im1_u8 = np.array(image1)
im2_u8 = np.array(image2)

# サイズや色数が違うならエラー
if im1_u8.shape != im2_u8.shape:
    print("サイズが違います")
    sys.exit()

# 負の値も扱えるようにnp.int16に変換
im1_i16 = im1_u8.astype(np.int16)
im2_i16 = im2_u8.astype(np.int16)

# 差分配列作成
diff_i16 = im1_i16 - im2_i16

'''ここから作成する画像によって異なる処理'''

# 差分の絶対値が0以外の輝度値を255に変換
diff_bool = diff_i16 != 0
diff_bin_u8 = diff_bool.astype(np.uint8)
diff_u8 = diff_bin_u8 * 255

# PIL画像に変換
diff_img = Image.fromarray(diff_u8)

'''ここまで作成する画像によって異なる処理'''

# 画像表示
diff_img.show()

スポンサーリンク

スクリプト実行結果

単純な差分画像を作ると同じ画像(org_1.pngorg_2.png)を入力画像としてスクリプトを実行すると、下のような画像が表示されます。

差分画像2

単純な差分画像を作るの時とは違ってヒゲだけでなく複数の円が存在することも確認できると思います。

実は org_2.pngorg_1.png にヒゲと薄い円を追加した画像で、ぱっと見では分からないようになっていますが、差分のある輝度値を 255 にすることでその差が差分画像ではっきりと確認でき流ようになっています。

スポンサーリンク

差分の大きな輝度値を 255 にする

続いては、2つの画像の輝度値の差が大きいものだけ 255 として画像として表現した差分画像を作っていきたいと思います。

差の大きい輝度値を全て255にした例

このような画像を作成することで、2つの画像のどこに大きな差があるかを簡単に確認することができます。

スポンサーリンク

スポンサーリンク

作り方

作り方は差分のある輝度値を 255 にするで紹介した方法とほぼ同じです。

ただし、今回は「差分のある輝度値」ではなく「差分の “大きい” 輝度値」のみを強調するため、最初の条件文の書き方のみが異なります。

要は、差分のある輝度値を 255 にするではその条件文が「0 以外であるか」でしたが、今回は「絶対値が閾値よりも大きいか」を条件文とします。

今回はこの閾値を 30 に設定し、下記のように NumPy 配列とスカラー値との比較を行います。

絶対値が30を超えるかの判断
# diff_i16は差分配列
diff_bool = np.abs(diff_i16) > 30

np.abs 関数は NumPy 配列の各要素をその絶対値に変換した新たな NumPy 配列を生成する関数です。

ですので、上記ではその生成した NumPy 配列と 30 の比較を行い、30 よりも大きい要素を True に、30 以下の要素を False に変換した NumPy 配列を生成することになります(diff_bool が参照)。

スポンサーリンク

スポンサーリンク

スクリプト

ここまでの解説を踏まえて作成した「差分の大きい輝度値を 255 にする」スクリプト例は下記のようになります。

差分の大きい輝度値を255にする
import numpy as np
from PIL import Image
import sys

# 画像の読み込み
image1 = Image.open("org_1.png")
image2 = Image.open("org_2.png")

# RGB画像に変換
image1 = image1.convert("RGB")
image2 = image2.convert("RGB")

# NumPy配列へ変換
im1_u8 = np.array(image1)
im2_u8 = np.array(image2)

# サイズや色数が違うならエラー
if im1_u8.shape != im2_u8.shape:
    print("サイズが違います")
    sys.exit()

# 負の値も扱えるようにnp.int16に変換
im1_i16 = im1_u8.astype(np.int16)
im2_i16 = im2_u8.astype(np.int16)

# 差分配列作成
diff_i16 = im1_i16 - im2_i16

'''ここから作成する画像によって異なる処理'''

# 差分の絶対値が30を超える輝度値を255に変換
diff_bool = np.abs(diff_i16) > 30
diff_bin_u8 = diff_bool.astype(np.uint8)
diff_u8 = diff_bin_u8 * 255

# PNG 画像として保存
diff_img = Image.fromarray(diff_u8)

'''ここまで作成する画像によって異なる処理'''

# 画像表示
diff_img.show()

スポンサーリンク

スクリプト実行結果

単純な差分画像を作ると同じ画像(org_1.pngorg_2.png)を入力画像としてスクリプトを実行すると、下のような画像が表示されます。

差分画像3

差分のある輝度値を 255 にするの時の結果とは違って、円が消え、ヒゲ部分差分が確認できるようになりました。

これは円部分の差が小さいからです。差の大きい輝度値のみを強調するようにしていますので、差が小さい差分はこの作り方では無視されて差分画像が作成されます。

ですので、差の大きい差分だけを確認したい場合にこの差分画像の作り方は有効です。

元画像に対して差分のある画素に色をつける

今度は、差が発生している箇所を元画像上で確認できるように差分画像を作っていきたいと思います。

MEMO

元画像とは、差分画像の元になった2つの画像のことを指しています

2つの画像のうち、どちらを元画像としても良いです

より具体的には、元画像に対し、差分のある画素にのみ特定の色をつけることで、元画像上で差分を確認できるようにします。

差のある画素を緑色にした例

このような画像を作成することで、元画像上のどこに差があるかを簡単に確認することができます。

スポンサーリンク

スポンサーリンク

作り方

元画像に対して差分のある画素に色をつけた差分画像を作るために、PIL 画像に用意されている paste メソッドを利用します(PIL で作成した画像オブジェクトを PIL 画像と呼んでいます)。

paste メソッドは画像に他の画像を貼り付けるメソッドです。

pasteメソッド
# src_imgとtarget_imgはPIL画像
src_img.paste(im=target_img)

paste メソッドを実行した PIL 画像に引数 im で指定した PIL 画像が貼り付けられます。

pasteメソッドの説明図

さらに paste メソッドでは引数 mask を指定することができ、これにより引数 im で指定する画像の各画素の透明度を設定して画像を貼り付けることができます。

mask指定時のpasteメソッド
# src_imgとtarget_imgとmask_imgはPIL画像
src_img.paste(im=target_img, mask=mask_img)

具体的にいうと、引数 mask でグレースケール画像を指定した場合、引数 im で指定する画像の各座標の画素の透明度は下記のように設定されます。

  • mask の輝度値が 0 の座標(黒色):透明度 100 %
  • mask の輝度値が 255 の座標(白色):透明度 0 %
  • mask の輝度値が上記以外の座標(灰色):透明度 (輝度値 / 255) %(おそらく)

ポイントは mask の画像の黒色の画素の座標には透明度 100 % で im の画像の画素が貼り付けられる点です。

その座標には透明な画素が貼り付けられることになるので、画像に変化はありません。元の画素がそのまま残ることになります。

一方で mask の画像の白色の画素の座標には透明度 0 % で im の画像の画素が貼り付けられることになりますので、im の画像の画素に置き換えられます。

なので、画像を貼り付けたい座標の画素を白色、貼り付けたくない座標の画素を黒色としたマスク画像を用意し、それを mask に指定して paste を実行することで、特定の座標のみに画像を貼り付けるようなことができます。

pasteメソッドでのmaskの使用例

で、今回の目的は「元画像に対して差分のある画素に特定の色をつけた差分画像を作ること」です。

これは paste メソッドに下記の引数 im と引数 mask を指定して実行すれば良いです。

  • im:全面一色の PIL 画像
  • mask:差分のある画素を白色、それ以外の画素を黒色にしたグレースケールの PIL 画像

paste メソッドを元画像の PIL 画像に実行させれば、元画像に対して差分のある画素に特定の色をつけた差分画像を作ることができます。

マスク画像の作り方

マスク画像は前述の通り「差分のある画素を白色、それ以外の画素を黒色にしたグレースケール画像」です。

差分のある輝度値を 255 にするで差分のある輝度値のみを 255 する方法を解説しましたが、基本的な考え方はこれと一緒です。

ただし、今回は差分のある “画素” のみを輝度値 255 にしたグレースケール画像を作成する必要があります。差分のある画素とは、RGB の一色でも輝度値に差がある画素です。

これは NumPy においては下記のように簡単に作成することができます。

まず、差分配列に対して「0 以外であるかどうか」の比較を行います。

0以外であるかの判断
# diff_i16は差分配列
diff_bool = diff_i16 != 0

これにより各輝度値に対して上記比較を行った結果(True or False)が格納された NumPy 配列が得られます(diff_bool が参照)。

続いてその配列にスライスを使って R 輝度値、G 輝度値、B 輝度値それぞれの比較結果に分離した 3つの NumPy 配列を参照します。

色ごとに分離
# 色ごとに分離した配列を参照
r_bool = diff_bool[:,:,0]
g_bool = diff_bool[:,:,1]
b_bool = diff_bool[:,:,2]

さらにこの3つの NumPy 配列で OR 演算を行います。同じサイズの NumPy 配列同士の OR 演算ですので、同じ座標の各要素ごとにこの OR 演算が行われることになります。

差分のある画素の抽出
mask_bool = r_bool | g_bool | b_bool

これにより各座標において、RGB の一つでも差分のある場合は True が、差分のない場合は False が格納された2次元の NumPy 配列が得られます(mask_bool が参照)。

この NumPy 配列の型は bool ですので、数値が扱えるように uint8 に型変換した後にスカラー値 255 を掛けます。

Trueを255に変換
mask_u8 = mask_bool.astype(np.uint8) * 255

これにより True255 に、False0 に変換された NumPy 配列が得られます(mask_u8 が参照)。

これにより 1255 に変換された NumPy 配列が生成されます。

あとはこの NumPy 配列を PIL 画像に変換してやれば「差分のある画素を白色、それ以外の画素を黒色にしたグレースケール画像」を作成することができます。

画像への変換
mask_img = Image.fromarray(mask_u8)

全面一色の画像の作り方

全面一色の画像も NumPy を使って簡単に作成できます(PIL でも簡単に作成できるのかも)。

今回は全面緑色の画像作成したいと思います。

まず下記で元画像と同じサイズの NumPy 配列を作成します。

NumPy配列の作成
green_u8 = np.zeros(im1_u8.shape, np.uint8)

np.zeros は第1引数(shape)で指定したサイズの NumPy 配列を生成する関数です。各要素の値は全て 0 になり、型は第2引数(dtype)で指定したものになります。

あとはスライスを利用して色の設定を行います。全座標を同じ色に設定するので、第3軸に対してのみスライスを行うこと全座標に対して色の設定を行います。

NumPy配列のスライス

例えば色を緑色にするのであれば、下記のように第3軸に対するスライスに対してスカラー値 255 を代入すれば良いです。

全座標のGを255に設定
green_u8[:,:,1] = 255

上記のスカラー値の代入では、そのスカラー値が第1軸と第2軸の形状に合わせてブロードキャストされますので、全座標の G の輝度値(第3軸が 1 の要素 )が 255 に設定されることになります。

あとはこの NumPy 配列を PIL 画像に変換してやれば「全面一色の画像」を作成することができます。

画像への変換
green_img = Image.fromarray(green_u8)

画像の貼り付け

最後に元画像に対して、先ほど作成した「全面一色の画像」をマスク画像を「差分のある画素を白色、それ以外の画素を黒色にしたグレースケール画像」として paste メソッドを実行して貼り付けます。

画像の貼り付け
diff_img = image1.copy()
diff_img.paste(im=blend_img, mask=mask_img)

以上により、元画像に対して差分のある画素に色をつけた差分画像を作成することができます。

スポンサーリンク

スポンサーリンク

スクリプト

ここまでの解説を踏まえて作成した「差分のある画素に色をつける」スクリプトは下記のようになります(差分のある画素には緑色をつけています)。

差分のある画素に色をつける
import numpy as np
from PIL import Image
import sys

# 画像の読み込み
image1 = Image.open("org_1.png")
image2 = Image.open("org_2.png")

# RGB画像に変換
image1 = image1.convert("RGB")
image2 = image2.convert("RGB")

# NumPy配列へ変換
im1_u8 = np.array(image1)
im2_u8 = np.array(image2)

# サイズや色数が違うならエラー
if im1_u8.shape != im2_u8.shape:
    print("サイズが違います")
    sys.exit()

# 負の値も扱えるようにnp.int16に変換
im1_i16 = im1_u8.astype(np.int16)
im2_i16 = im2_u8.astype(np.int16)

# 差分配列作成
diff_i16 = im1_i16 - im2_i16

'''ここから作成する画像によって異なる処理'''

# 差分の絶対値が0以外の輝度値を255に変換
diff_bool = diff_i16 != 0

# 輝度値に差がある画素の輝度値を255とするグレースケール画像の配列作成
mask_bool = diff_bool[:,:,0] | diff_bool[:,:,1] | diff_bool[:,:,2]
mask_u8 = mask_bool.astype(np.uint8) * 255

# 全ての画素の色を(0,255,0)とした配列作成
green_u8 = np.zeros(im1_u8.shape, np.uint8)
green_u8[:,:,1] = 255

# PIL画像に変換
mask_img = Image.fromarray(mask_u8)
green_img = Image.fromarray(green_u8)

# 1つ目の画像に貼り付け
diff_img = image1.copy()
diff_img.paste(im=green_img, mask=mask_img)

'''ここまで作成する画像によって異なる処理'''

# 画像表示
diff_img.show()

スポンサーリンク

スクリプト実行結果

単純な差分画像を作ると同じ画像(org_1.pngorg_2.png)を入力画像としてスクリプトを実行すると、下のような画像が表示されます。

差分画像4

ここまでの差分画像とは異なり、元画像上で差分が確認できるので、どの箇所に差分があるかが分かりやすくなっていると思います。

今回は差分のある画素を緑色にしましたが、他の画像で差分をとる場合は、画像中に少ない色を選択すると良いと思います。

元画像に対して差分のある画素に色を混ぜる

最後に、元画像に対し、差分のある画素にのみ特定の色を混ぜることで差分画像を作成していきたいと思います。

差のある画素に緑色を混ぜた例

このような画像を作成することで、元画像上のどこに差があるかを簡単に確認することができます。

スポンサーリンク

スポンサーリンク

作り方

作り方は元画像に対して差分のある画素に色をつけると同じです。

paste メソッドを使って差分のある画素に対してのみ画像の貼り付けを行います。

ただし、貼り付ける画像(引数 im で指定する画像)を、単なる全面一色の画像ではなく、元画像に特定の色を混ぜた画像にします。

pasteメソッドでのmaskの使用例2

これにより、元画像に対して差分のある画素のみに特定の色を混ぜた画像を作成することができます。

つまり、元画像に対して差分のある画素に色をつけるとは貼り付ける画像が異なるだけで、マスク画像などに関しては全く同じものを用います。

ですので、ここではその貼り付ける画像である「元画像に特定の色を混ぜた画像」の作り方についてのみ解説したいと思います。

元画像に特定の色を混ぜた画像の作り方

この画像も NumPy を用いれば簡単に作成します。

まず元画像(差分をとった画像の1つ目 or 2つ目の画像)から変換した NumPy 配列と、その NumPy 配列と同じサイズの全面一色(色は画像に混ぜたい色)の NumPy 配列を用意します。

あとは下記のように同じサイズの NumPy 配列同士の足し算を行います。

画像のブレンド
# im1_u8とgreen_u8はNumPy配列
blend_u8 = im1_u8 * (1 - α) + green_u8 * α

α は混ぜたい色の濃さの度合いを設定するパラメータで、この α が大きいほど混ぜる色が濃くなります。α0 から 1 の値を設定することができます。

例えば、下記のように α=0.25 にすれば、元画像を 3/4 の度合いで、特定の色を 1/4 の度合いで混ぜたような画像を作成することができます。

α=0.25での画像のブレンド
# im1_u8とgreen_u8はNumPy配列
blend_u8 = im1_u8 * 0.75 + green_u8 * 0.25

最後に NumPy 配列を画像に変換すれば、元画像に特定の色を混ぜた画像を作成することができます。

上記の式で得られる NumPy 配列の型が float64 になるので、uint8 に変換してから画像に変換する必要がある点に注意です。

画像への変換
blend_img = Image.fromarray(blend_u8.astype(np.uint8))

スポンサーリンク

スポンサーリンク

スクリプト

ここまでの解説を踏まえて作成した「差分のある画素に色を混ぜる」スクリプトは下記のようになります(差分のある画素には緑色を混ぜています)。

差分のある画素に色を混ぜる
import numpy as np
from PIL import Image
import sys

# 画像の読み込み
image1 = Image.open("org_1.png")
image2 = Image.open("org_2.png")

# RGB画像に変換
image1 = image1.convert("RGB")
image2 = image2.convert("RGB")

# NumPy配列へ変換
im1_u8 = np.array(image1)
im2_u8 = np.array(image2)

# サイズや色数が違うならエラー
if im1_u8.shape != im2_u8.shape:
    print("サイズが違います")
    sys.exit()

# 負の値も扱えるようにnp.int16に変換
im1_i16 = im1_u8.astype(np.int16)
im2_i16 = im2_u8.astype(np.int16)

# 差分配列作成
diff_i16 = im1_i16 - im2_i16

'''ここから作成する画像によって異なる処理'''

# 差分の絶対値が0以外の輝度値を255に変換
diff_bool = diff_i16 != 0

# 色ごとに分離した配列を参照
r_bool = diff_bool[:,:,0]
g_bool = diff_bool[:,:,1]
b_bool = diff_bool[:,:,2]

# 輝度値に差がある画素の輝度値を255とするグレースケール画像の配列作成
mask_bool = r_bool | g_bool | b_bool
mask_u8 = mask_bool.astype(np.uint8) * 255

# PIL画像に変換
mask_img = Image.fromarray(mask_u8)

# 全ての画素の色を(0,255,0)とした配列作成
green_u8 = np.zeros(im1_u8.shape, np.uint8)
green_u8[:,:,1] = 255

# 1つ目の画像に色を混ぜる
blend_u8 = im1_u8 * 0.75 + green_u8 * 0.25

# 画像に変換
blend_img = Image.fromarray(blend_u8.astype(np.uint8))

# 1つ目の画像に貼り付け
diff_img = image1.copy()
diff_img.paste(im=blend_img, mask=mask_img)

'''ここまで作成する画像によって異なる処理'''

# 画像表示
diff_img.show()

スポンサーリンク

スクリプト実行結果

単純な差分画像を作ると同じ画像(org_1.pngorg_2.png)を入力画像としてスクリプトを実行すると、下のような画像が表示されます。

差分画像5

元画像に対して差分のある画素に色をつけるでは差分のある画素を他の色で完全に置き換えてしまいましたが、今回のスクリプトでは色を混ぜているだけですので、差分を示す色に元画像の画素が透けて確認できるようになります。

元画像に対して差分のある画素に色をつけるのスクリプトだと、差分のある画素が多いと特定の色一色になってむしろ差分が確認しづらいですが、このスクリプトだと元画像も透けて見えるので、そういった差分のある画素が多い画像でも確認しやすくなります。

スポンサーリンク

まとめ

このページでは Python でさまざまな差分画像を作成する方法について解説しました!

NumPy や PIL を用いることで簡単に差分画像を作成することができます。

特に NumPy 入門においては差分画像作成はうってつけのテーマだと思います!

NumPy での処理で画像がどのように変化するかを是非いろいろ試してみてください!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です