【Python/tkinter】画像の差分確認アプリを開発

Pythonでの画像の差分確認アプリの開発方法の解説ページをアイキャッチ

今回は画像の差分を確認するアプリを Python の tkinter を用いて開発していきたいと思います。

といっても、Python での差分を確認するための画像の作り方は下記ページで解説していますので、このページでの解説は主にアプリへの仕立て方の解説になります。

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

画像の差分確認アプリ

まずは今回作成するアプリがどのようなものであるかを紹介していきます。

アプリの起動画面

今回作成する画像の差分確認アプリの起動画面は下の画像のようになります。

差分確認アプリの起動画面

左側にキャンバスを3つ設けており、各キャンバスはそれぞれ下記を表示するためのものになります

  • 左:画像の1つ目
  • 中:画像の2つ目
  • 右:画像の1つ目と画像の2つ目の差分画像

また右側にはボタンを設けており、このボタンでユーザーからの操作を受け付けられるようにしています。

具体的には下記の7つのボタンを用意しています。

  • 画像1選択
  • 画像2選択
  • 単純差分をとる
  • 差分を強調する
  • 大きな差分のみ強調する
  • 差分に色をつける
  • 差分に色を混ぜる

上の2つは「差分をとる画像を選択するボタン」になります。

一方で、下の5つは「差分画像を作成するボタン」になります。

差分画像は差分をとる画像が選択されていないと作成することができませんので、上の2つのボタンで画像が選択されるまでは「無効」状態にしています。

アプリ起動時のボタンの状態

スポンサーリンク

アプリの使い方

続いてアプリの使い方について解説します。

このアプリは画面右側の7つのボタンによって操作を行えるようにしています。

画像の選択

「画像1選択」ボタンと「画像2選択」ボタンを押せば、下の画像のようなファイル選択画面が表示されます。

ファイル選択画面

差分を取りたい画像を選択して Open ボタンを押せば、その選択した画像がアプリの左側のキャンバスに表示されます。

選択した画像が表示される様子

差分画像の作成

画像を2つ選択するまでは、下側の5つのボタンは「無効」状態にしていますが、画像を2つ選択すれば、下側の5つのボタンが「通常」状態に変化し、ボタンクリックによって差分画像が作成できるようになります。

画像2つ選択後のボタンの状態

下側の5つのボタンは全て差分画像を作成するものですが、ボタンによって下記のように作成される差分画像が異なります。

  • 「単純差分をとる」ボタン:単純な差分画像を作る
  • 「差分を強調する」ボタン:差分のある輝度値を 255 にする
  • 「大きな差分のみ強調する」ボタン:差分の大きな輝度値を 255 にする
  • 「差分に色をつける」ボタン:元画像に対して差分のある画素に色をつける
  • 「差分に色を混ぜる」ボタン:元画像に対して差分のある画素に色を混ぜる

具体的にどのような差分画像が作成されるかは下記ページで解説していますので、こちらを参考にしていただければと思います。

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

ボタンを押して作成された差分画像はキャンバスに表示されます(一番右側のキャンバス)。

差分画像が表示される様子

差分画像はキャンバスに合わせて拡大縮小しますので、差分画像そのものではないことに注意してください。

画像の差分確認アプリの作り方

では Python での tkinter を用いた「画像の差分確認アプリの作り方」について解説していきます。

クラス設計

今回作成するアプリに用意するクラスは Diff のみになります。

UI 部分と差分画像を作成する処理を行う部分などはクラスとして分けてもよかったかもしれないですが、今回はクラスとしては Diff のみ用意し、このクラスに差分画像を確認するアプリの全ての処理を行わせるようにしています。

クラスには下記のような属性を用意し、それぞれの初期化をコンストラクタ __init__ で実行するようにしています。

  • 読み込んだ or 作成した画像
    • im1:読み込んだ画像の1つ目
    • im2:読み込んだ画像の2つ目
    • diff:作成した差分画像
  • 描画中の画像
    • draw_im1:左のキャンバスに描画中の画像
    • draw_im2:中のキャンバスに描画中の画像
    • draw_diff:右のキャンバスに描画中の画像
  • 画像を描画するキャンバス
    • canvas_im1:左のキャンバス
    • canvas_im2:中のキャンバス
    • canvas_diff:右のキャンバス
  • キャンバスのサイズ
    • canvas_width:キャンバスの幅(単位:px)
    • canvas_height:キャンバスの高さ(単位:px)
  • 差分を作成するボタンのリスト
    • buttons:差分を作成するボタンを要素とするリスト

スポンサーリンク

ウィジェットの作成

では、この Diff クラスを作り込むことでアプリを開発していきます。

まずはアプリの画面を構成するウィジェットを作成していきます。

画面を構成するウィジェットは下の図のようになります。

アプリを構成するウィジェット

左側のフレームにはキャンバス3つを grid メソッドにより横に並べています。

Canvasをgridで配置する様子

右側のフレームにはボタン7つを pack メソッドにより並べています。

buttonをpackで配置する様子

まあ両方 grid もしくは pack で配置しても良いのですが…。

ウィジェットの配置については下記で詳しく解説していますので、配置について知りたい方は是非下記ページを読んでみてください。

ウィジェット配置方法解説ページのアイキャッチTkinterの使い方:ウィジェットの配置(pack・grid・place)

また、これらのボタンに関してはクリック時にそれぞれに対するメソッドを実行するために、command を設定しています(具体的に設定するメソッドについては後述する差分画像作成機能の実装で解説していきます)。

さらに、下側5つのボタンに関しては、差分画像が作れるようになるまではクリックできないように、statetkinter.DISABLED として Button クラスのインスタンスを生成するようにしています。

ボタンウィジェット・Button クラスについてや、commandstate については下記ページで解説していますので、詳しく知りたい方は是非こちらを読んでみてください。

ボタンウィジェット作成解説ページのアイキャッチTkinterの使い方:Buttonクラスでボタンウィジェットを作成

これらのウィジェットの作成を行うスクリプトは、最後に紹介する画像の差分確認アプリのスクリプトの下記のメソッドで実装しています。

  • createWidgets
  • createCanvas
  • createSelectButton
  • createDiffButton

画像の読み込み機能の実装

次はボタンがクリックされた時に実行する機能を実装をしていきます。

まずは「画像1選択」ボタンと「画像2選択」ボタンがクリックされた時に実行する「画像の読み込みを行う機能」を実装していきます。

この「画像の読み込みを行う機能」では、より具体的には下記のことを行います。

  • ファイル選択画面を表示する
  • 選択された画像を読み込む
  • 読み込んだ画像をリサイズする
  • リサイズした画像を tkinter 画像に変換する
  • tkinter 画像をキャンバスに描画する
  • 差分画像作成ボタンの有効化・無効化を行う

1つ1つの処理について解説していきたいと思います。

ファイル選択画面を表示する

まずはユーザーが差分をとりたい画像を選択できるようにファイル選択画面を表示します。

ファイル選択画面とは下の画像のような画面です。OS によっては見た目は異なるは思いますが、PC 使っているとよく見るマウス操作でファイルを選択できる画面ですね。

ファイル選択画面

ファイル選択画面の表示の仕方などは下記のページで詳しく説明していますので、興味のある方は是非読んでみてください。

ファイル選択画面表示の解説ページアイキャッチPython でファイル選択画面を表示する

要は、tkinter.filedialog モジュールに用意された askopenfilename を実行することでファイル選択画面を表示することができます。

さらに、askopenfilename の戻り値として、ファイル選択画面でユーザーが選択したファイルへのパスを取得することができます。

ファイル選択画面の表示
file_path = tkinter.filedialog.askopenfilename()

この「ファイル選択画面を表示する」を行う処理は、最後に紹介する画像の差分確認アプリのスクリプトの下記のメソッドで実装しています。

  • selectIm1(1つ目の画像選択用)
  • selectIm2(2つ目の画像選択用)

選択された画像を読み込む

で、ファイル選択画面でユーザーが選択するのはあくまでもファイルへのパスですので、そのパスの画像を読み込んでやる必要があります。

今回は、この画像の読み込みに PIL の Image モジュールを利用します。Image モジュールに用意された open 関数を実行することで、引数に指定したパスのファイルを画像オブジェクトとして読み込むことができます。

画像の読み込み
im = Image.open(file_path)

この「選択された画像を読み込む」を行う処理についても、最後に紹介する画像の差分確認アプリのスクリプトの下記のメソッドで実装しています。

  • selectIm1(1つ目の画像選択用)
  • selectIm2(2つ目の画像選択用)

読み込んだ画像をリサイズする

続いて読み込んだ画像をキャンバスに描画するための準備をしていきます。

キャンバスと画像のサイズが異なる可能性がありますので、そのまま表示するとキャンバスから画像がはみ出たりする可能性があります。

キャンバスから画像がはみ出る様子

なので、まずは画像をキャンバスに合わせてリサイズします。

リサイズ自体は PIL の Image モジュールの resize メソッドにより行うことができます。

この resize メソッドでは、このメソッドを実行した画像を引数で指定したサイズにリサイズすることができます。

リサイズ
resized_image = image.resize(resize_size)

ただし、単純にこの引数にキャンバスのサイズを指定してしまうと、画像のサイズとキャンバスのサイズの関係によっては変に画像が縦方向もしくは横方向に伸ばされた形で表示されてしまいます。

画像のリサイズでアスペクト比が変わってしまう様子

要は縦方向と横方向とで異なる拡大縮小率でリサイズされてしまうのです…。

なので、縦横のサイズ関係(要はアスペクト比)を保ったまま画像のリサイズを行うため、下記のようにリサイズ後の画像サイズを設定します。

  1. キャンバスの幅 / 画像の幅 を計算
  2. キャンバスの高さ / 画像の高さ を計算
  3. 1. と 2. との計算結果の小さい方を拡大縮小率に設定
  4. リサイズ後のサイズを 3. で設定した拡大縮小率を用いて下記により計算
    • リサイズ後の画像の幅 = 画像の幅 * 拡大縮小率
    • リサイズ後の画像の高さ = 画像の高さ * 拡大縮小率

3. で “小さい方” を選択して拡大縮小率に設定することで、キャンバスからはみ出ることなく、ちょうどキャンバスに収まるサイズでリサイズすることができます。

拡大縮小率の計算

この「読み込んだ画像をリサイズする」を行う処理は、最後に紹介する画像の差分確認アプリのスクリプトの下記のメソッドで実装しています。

  • resize

リサイズした画像を tkinter 画像に変換する

続いてリサイズした画像を tkinter のキャンバスに描画可能な tkinter 画像に変換します。

この変換を行うのは、PIL で作成した画像はそのままではキャンバスには描画できないためです。

この変換方法は下記ページで詳しく解説します。

画像オブジェクト変換方法の解説ページアイキャッチ【Python】PIL ⇔ OpenCV2 ⇔ Tkinter 画像オブジェクトの相互変換

要は PIL の ImageTk モジュールに用意された PhotoImage 関数を実行することで PIL 画像を tkinter 画像に変換することができます。

PIL から tkinter への画像変換

この「リサイズした画像を tkinter 画像に変換する」を行う処理は、最後に紹介する画像の差分確認アプリのスクリプトの下記のメソッドで実装しています。

  • drawImage

tkinter 画像をキャンバスに描画する

続いて tkinter 画像をキャンバスに描画します。

この tkinter 画像はリサイズも完了していますので、あとはキャンバスに描画すれば良いだけです。

このキャンバスへの図形や画像の描画については下記ページで解説しています。

tkinterキャンバスに図形を描画する方法解説ページのアイキャッチTkinterの使い方:Canvasクラスで図形を描画する

要は Canvas クラスの create_image メソッドの実行によりキャンバスに画像を描画することができます。

ただし、今回は画像の中心がキャンバスの中心に来るようにキャンバスへの画像の描画を行うため、そのための引数指定を行う必要があります。

create_image メソッドでは、これも下記ページで解説していますが、第1引数と第2引数及び anchor 引数により画像の描画位置を調整することができます。

tkinterキャンバスに図形を描画する方法解説ページのアイキャッチTkinterの使い方:Canvasクラスで図形を描画する

具体的には、第1引数と第2引数では基準位置を表す座標を指定し、anchor 引数ではこの基準位置を画像のどの位置にするかを指定して描画位置を指定します。

つまり、第1引数と第2引数でキャンバスの中心座標を指定し、anchor 引数で画像を中心を意味する tkinter.CENTER を指定すれば、キャンバスの中心に画像の中心が来るように画像を描画することができます。

描画する画像の配置

この「tkinter 画像をキャンバスに描画する」を行う処理についても、最後に紹介する画像の差分確認アプリのスクリプトの下記のメソッドで実装しています。

  • drawImage

差分画像作成ボタンの有効化・無効化を行う

画像の描画はここまでで、最後に差分画像作成ボタンの有効化・無効化を行います。

前述でも触れましたが、差分画像が作成できるのは、差分をとる2つの画像が読み込まれてからです。

ですので、差分画像が作成できない時にはボタンが押すことができないように、差分画像作成ボタンを無効化するようにしています(ウィジェットの作成時にボタンの statetkinter.DISABLED に設定しておく)。

そして、画像の読み込みの最後で、下記の条件を満たしているか確認し、満たしている時、つまり差分画像の作成の準備が整った時に差分画像作成ボタンを有効(通常状態)に設定するようにしています。

  • 2つの画像が読み込まれている
  • 2つの画像のサイズが同じ

より具体的には、上記の2条件を満たしている時にボタンの statetkinter.NORMAL に設定します。

こんな感じでボタンの状態(無効状態や通常状態)を切り替えることで、準備が整っていない間にユーザーから処理が実行されたりすることを防ぐことができ、ユーザーにとっても操作しやすいアプリにすることができます。

この「差分画像作成ボタンの有効化・無効化を行う」を行う処理は、最後に紹介する画像の差分確認アプリのスクリプトの下記のメソッドで実装しています。

  • enableButton

差分画像作成機能の実装

続いて差分画像作成機能を実装していきます。

差分画像の作成

といっても、この機能のコアの処理やそのスクリプトは下記ページで紹介していますので、このページでは次に紹介する画像の差分確認アプリのスクリプトのメソッドとの関係だけを説明したいと思います。

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

上記ページで紹介している5つの方法による差分画像の作成を、次に紹介する画像の差分確認アプリのスクリプトDIff クラスのメソッドでそれぞれ作成するようにしています。

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

画像の差分確認アプリで説明したように、アプリでは各ボタンがクリックされた際には下記の処理を行うようにしていますので、

  • 「単純差分をとる」ボタン:単純な差分画像を作る
  • 「差分を強調する」ボタン:差分のある輝度値を 255 にする
  • 「大きな差分のみ強調する」ボタン:差分の大きな輝度値を 255 にする
  • 「差分に色をつける」ボタン:元画像に対して差分のある画素に色をつける
  • 「差分に色を混ぜる」ボタン:元画像に対して差分のある画素に色を混ぜる

各ボタンは command 引数に下記のメソッドを設定して作成する(Button クラスのコンストラクタを実行する)ことになります。

  • 「単純差分をとる」ボタン:simpleDiff
  • 「差分を強調する」ボタン:emphasizeDiff
  • 「大きな差分のみ強調する」ボタン:emphasizeLargeDiff
  • 「差分に色をつける」ボタン:colorDiff
  • 「差分に色を混ぜる」ボタン:blendDiff

差分画像の描画

差分画像を作成した後は、その差分画像をキャンバスに描画してアプリに表示します。

これは画像のキャンバスへの描画に関しては画像の読み込み機能の実装で解説しており、ここで紹介した方法で、読み込んだ画像の代わりに作成した画像を描画してやれば良いです。

スポンサーリンク

画像の差分確認アプリのスクリプト

ここまで解説してきた内容を踏まえて作成した画像の差分確認アプリのスクリプトは下記のようになります。

画像の差分確認アプリ
import tkinter
import numpy as np
from PIL import Image, ImageTk
import tkinter.filedialog

class Diff():
	def __init__(self, master):
		# アプリのインスタンス
		self.master = master

		# 読み込んだor作成した画像(PIL画像)
		self.im1 = None
		self.im2 = None
		self.diff = None

		# 描画中の画像(tkinter画像)
		self.draw_im1 = None
		self.draw_im2 = None
		self.draw_diff = None

		# 画像を描画するキャンバス
		self.canvas_im1 = None
		self.canvas_im2 = None
		self.canvas_diff = None

		# キャンバスのサイズ
		self.canvas_width = 300
		self.canvas_height = 300

		# 差分を作成するボタンのリスト
		self.buttons = []
		
		# ウィジェット作成
		self.createWidgets()

	def createWidgets(self):
		'''
		ウィジェットの作成と配置を行う
		'''

		# キャンバスを配置するフレーム作成
		frame1 = tkinter.Frame(
			self.master,
		)
		frame1.grid(column=0, row=0)

		# ボタンを配置するフレーム作成
		frame2 = tkinter.Frame(
			self.master,
		)
		frame2.grid(column=1, row=0)

		# キャンバスを作成
		self.canvas_im1 = self.createCanvas(
			master=frame1, column=0, row=0
		)
		self.canvas_im2 = self.createCanvas(
			master=frame1, column=1, row=0
		)
		self.canvas_diff = self.createCanvas(
			master=frame1, column=2, row=0
		)
		
		# 画像選択ボタンを作成
		self.createSelectButton(
			master=frame2,
			text="画像1選択",
			command=self.selectIm1
		)

		self.createSelectButton(
			master=frame2,
			text="画像2選択",
			command=self.selectIm2
		)

		# 差分画像作成ボタンを作成(リストに追加)
		self.buttons.append(
			self.createDiffButton(
				master=frame2,
				text="単純差分をとる",
				command=self.simpleDiff
			)
		)

		self.buttons.append(
			self.createDiffButton(
				master=frame2,
				text="差分を強調する",
				command=self.emphasizeDiff
			)
		)

		self.buttons.append(
			self.createDiffButton(
				master=frame2,
				text="大きな差分のみ強調する",
				command=self.emphasizeLargeDiff
			)
		)

		self.buttons.append(
			self.createDiffButton(
				master=frame2,
				text="差分に色を付ける",
				command=self.colorDiff
			)
		)

		self.buttons.append(
			self.createDiffButton(
				master=frame2,
				text="差分に色を混ぜる",
				command=self.blendDiff
			)
		)


	def createCanvas(self, master, column, row):
		'''
		キャンバスを作成する
		master:キャンバスの配置先のウィジェット
		column:gridで貼り付ける位置(行)
		row:gridで貼り付ける位置(列)
		'''

		# Canvasインスタンスを作成して配置
		canvas = tkinter.Canvas(
			master,
			bg="white",
			width=self.canvas_width,
			height=self.canvas_height,
			highlightthickness=0
		)
		canvas.grid(
			column=column, row=row,
			padx=10, pady=10
		)

		return canvas

	def createSelectButton(self, master, text, command):
		'''
		画像選択ボタンを作成する
		master:ボタンの配置先のウィジェット
		text:ボタンに表示するテキスト
		command:ボタンクリック時に実行する関数(メソッド)
		'''

		# Buttonインスタンスを作成して配置
		button = tkinter.Button(
			master,
			text=text,
			command=command
		)
		button.pack(
			padx=10, pady=10
		)

		return button

	def createDiffButton(self, master, text, command):
		'''
		差分画像作成ボタンを作成する
		master:ボタンの配置先のウィジェット
		text:ボタンに表示するテキスト
		command:ボタンクリック時に実行する関数(メソッド)
		'''
		button = tkinter.Button(
			master,
			state=tkinter.DISABLED, # 画像選択するまで無効
			text=text,
			command=command
		)
		button.pack(
			padx=10, pady=10
		)

		return button


	def resize(self, img):
		'''
		画像をリサイズする
		img:リサイズするPIL画像
		'''

		# 画像のサイズ取得
		width, height = img.size

		# キャンバスと画像のサイズから拡大縮小率計算
		width_ratio = self.canvas_width / width
		height_ratio = self.canvas_height / height
		ratio = min((width_ratio, height_ratio))

		# リサイズ後のサイズ計算
		resize_width = int(ratio * width)
		resize_height = int(ratio * height)

		# リサイズ実行
		resize_img = img.resize((resize_width, resize_height))

		return resize_img

	def drawImage(self, canvas, img):
		'''
		キャンバスに画像を描画する
		canvas:描画先のキャンバス
		img:描画する画像
		'''

		# キャンバスに合わせてリサイズ
		resize_img = self.resize(img)
		draw_img = ImageTk.PhotoImage(resize_img)

		# キャンバス上のオブジェクトを全て削除
		ids = canvas.find_all()
		for id in ids:
			canvas.delete(id)

		# キャンバスに画像描画
		canvas.create_image(
			self.canvas_width  // 2, self.canvas_height // 2,
			image=draw_img,
			anchor=tkinter.CENTER
		)

		return draw_img


	def selectIm1(self):
		'''
		1つ目の画像を読み込みキャンバスに描画する
		'''

		# ファイル選択ダイアログの表示
		file_path = tkinter.filedialog.askopenfilename()

		if len(file_path) != 0:
			# ファイルが選択された場合

			# 画像としてファイルを開いてRGB変換
			im = Image.open(file_path)
			self.im1 = im.convert("RGB")

			# 画像を描画後、描画している画像を受け取る
			self.draw_im1 = self.drawImage(self.canvas_im1, self.im1)

			# 差分画像作成ボタンの有効・無効化
			self.enableButton()

	def selectIm2(self):
		'''
		2つ目の画像を読み込みキャンバスに描画する
		'''

		# ファイル選択ダイアログの表示
		file_path = tkinter.filedialog.askopenfilename()

		if len(file_path) != 0:
			# ファイルが選択された場合

			# 画像としてファイルを開いてRGB変換
			im = Image.open(file_path)
			self.im2 = im.convert("RGB")

			# 画像を描画後、描画している画像を受け取る
			self.draw_im2 = self.drawImage(self.canvas_im2, self.im2)

			# 差分画像作成ボタンの有効・無効化
			self.enableButton()
			
	def enableButton(self):
		'''
		差分画像作成ボタンの有効化/無効化を行う
		'''

		if not self.im1 or not self.im2:
			# 画像がセット済でないならボタン無効

			for button in self.buttons:
				button.config(state=tkinter.DISABLED)
			
			return

		if self.im1.size != self.im2.size:
			# 画像サイズが異なるならボタン無効
			
			for button in self.buttons:
				button.config(state=tkinter.DISABLED)

			return

		# 画像がセット済&サイズ一緒ならボタン有効
		for button in self.buttons:
			button.config(state=tkinter.NORMAL)

	def simpleDiff(self):
		'''
		単純な差分画像を作成する
		'''

		# 差分配列作成
		diff_i16 = self.createDiffArr()

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

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

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

		# キャンバスに描画
		self.draw_diff = self.drawImage(self.canvas_diff, self.diff)

	def emphasizeDiff(self):
		'''
		差分のある輝度値を強調した画像を作成する
		'''

		# 差分配列作成
		diff_i16 = self.createDiffArr()

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

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

		# キャンバスに描画
		self.draw_diff = self.drawImage(self.canvas_diff, self.diff)

	def emphasizeLargeDiff(self):
		'''
		差分の大きな輝度値を強調した画像を作成する
		'''

		# 差分配列作成
		diff_i16 = self.createDiffArr()

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

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

		# キャンバスに描画
		self.draw_diff = self.drawImage(self.canvas_diff, self.diff)

	def colorDiff(self):
		'''
		差分のある画素に色をつけた画像を作成する
		'''

		# 1つ目の画像を配列変換
		im1_u8 = np.array(self.im1)

		# 差分配列作成
		diff_i16 = self.createDiffArr()

		# 差分の絶対値が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つ目の画像に貼り付け
		self.diff = self.im1.copy()
		self.diff.paste(im=green_img, mask=mask_img)

		# キャンバスに描画
		self.draw_diff = self.drawImage(self.canvas_diff, self.diff)
	
	def blendDiff(self):
		'''
		差分のある画素に色を混ぜた画像を作成する
		'''

		# 1つ目の画像を配列変換
		im1_u8 = np.array(self.im1)

		# 差分配列作成
		diff_i16 = self.createDiffArr()

		# 差分の絶対値が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
		mask_img = Image.fromarray(mask_u8)

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

		# 色を混ぜる
		blend_u8 = im1_u8 * 0.75 + green_u8 * 0.25
		blend_img = Image.fromarray(blend_u8.astype(np.uint8))

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

		# キャンバスに描画
		self.draw_diff = self.drawImage(self.canvas_diff, self.diff)
	
	
	def createDiffArr(self):
		'''
		差分配列を作成する
		'''

		# NumPy配列へ変換
		im1_u8 = np.array(self.im1)
		im2_u8 = np.array(self.im2)
		
		# 負の値も扱えるようにnp.int16に変換
		im1_i16 = im1_u8.astype(np.int16)
		im2_i16 = im2_u8.astype(np.int16)

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

# プログラムの開始
app = tkinter.Tk()
diff = Diff(app)
app.mainloop()

まとめ

このページでは画像の差分確認アプリの作り方について解説しました。

差分画像の作り方自体は下記ページで全て解説していますが、アプリに仕立てる上でいろいろ工夫が必要である点を感じ取っていただいたのではないかと思います。

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

このページのスクリプトでも使用している PIL や OpenCV を利用することで、高度な画像処理を行なう画像処理アプリなんかも作成することができます。

tkinter + PIL や tkinter + OpenCV 等の組み合わせによりさまざまなアプリが作成できますので、差分確認アプリ以外にもいろんなアプリを作ってみてください!

コメントを残す

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