【Python】PIL ⇔ OpenCV2 ⇔ Tkinter 画像オブジェクトの相互変換

画像オブジェクト変換方法の解説ページアイキャッチ

このページでは PIL(Pillow)・OpenCV2・Tkinter(PhotoImage)それぞれの画像オブジェクトを相互に変換する方法について解説します。

具体的には下記のように画像オブジェクトを変換することができるようになります。

  • OpenCV2 → PIL
  • OpenCV2 → Tkinter
  • PIL → OpenCV2
  • PIL → Tkinter
  • Tkinter→ OpenCV2
  • Tkinter → PIL

これにより、OpenCV2 や PIL モジュールで読み込んだ画像や画像処理等を行った結果を Tkinter で扱って GUI アプリに表示する事などができるようになります。

動作確認環境

このページで紹介するサンプルスクリプトは下記環境で動作確認を行っています

  • OS:macOS Catalina
  • Python:3.8
  • Tkinter:8.6
  • NumPy:1.18.2
  • OpenCV2:4.2.0
  • Pillow:7.1.1
透過画像

透過画像については動作未確認ですのでご注意ください

というか対応してないです

必要があれば対応方法考えてみますので教えてください

画像変換のやり方まとめ

最初に結論として PIL ⇔ OpenCV2 ⇔ Tkinter 画像オブジェクトの変換の方法を下記にまとめておきます(下記は1例であり、他の方法も存在すると思います)。

  • ①Tkinter → OpenCV2:NumPy 配列に変換
  • ②PIL → OpenCV2:NumPy 配列に変換
  • ③OpenCV2 → PIL:NumPy 配列から変換
  • ④Tkinter → PIL:①+③で変換
  • ⑤PIL → Tkinter:ImageTk.PhotoImage で変換
  • ⑥OpenCV2 → Tkinter:③+⑤で変換

1枚の図で表すと下のようになります。

画像オブジェクトの変換まとめ図

ポイントは NumPy 配列

PIL ⇔ OpenCV2 ⇔ Tkinter 画像オブジェクトの相互変換を考えるにあたって1つのポイントになるのは、上の図で何回も登場する「NumPy 配列」です。

実は OpenCV2 の画像オブジェクトの実体は、要素に各ピクセルの BGR データを格納した この「NumPy 配列」です。

例えば、下記のように画像ファイルを読み込んだ場合、

CV2画像オブジェクトの生成
# 画像を読み込んでCV2画像オブジェクトを生成
cv2_image = cv2.imread("cute_cat_very_small.png")

OpenCV2 によりデコードされたデータが下記のような構造の NumPy 配列が生成されます。

array(
  [
    [[B, G, R], [B, G, R], ・・・, [B, G, R]],
    [[B, G, R], [B, G, R], ・・・, [B, G, R]],
        ・
        ・
        ・
    [[B, G, R], [B, G, R], ・・・, [B, G, R]]
  ], dtype='uint8'
)
[B, G, R] が1ピクセルの BGR データを表しており、それが横方向に画像の横方向サイズ分並びます。

さらにそれが縦方向に画像の縦方向サイズ分並びます。そして、各要素のデータタイプとして 'uint8' を設定するための要素が存在しています。

実際に上記スクリプトの cv2.imread 実行直後の cv2_image の値をコピーして貼り付けたものが下記になります(5 x 5 のサイズの画像を読み込んでいます)。

array([[[ 77, 117, 110],
        [ 78, 126, 120],
        [ 92, 136, 136],
        [ 99, 135, 133],
        [103, 115, 105]],

       [[ 92, 182, 162],
        [ 92, 190, 168],
        [107, 189, 179],
        [ 95, 164, 150],
        [ 78, 174, 140]],

       [[113, 195, 180],
        [117, 174, 167],
        [131, 140, 142],
        [136, 189, 174],
        [ 85, 203, 174]],

       [[110, 224, 201],
        [117, 160, 155],
        [124,  96,  91],
        [154, 209, 194],
        [101, 221, 202]],

       [[ 95, 206, 184],
        [ 94, 202, 180],
        [ 99, 181, 156],
        [ 97, 193, 169],
        [ 95, 213, 190]]], dtype=uint8)

例えば下記スクリプトのように NumPy 配列を作成してやれば、画像を読み込まなくても cv2.imshow で画像を表示することが可能です。

NumPy配列を表示
import cv2
import numpy

# リストからCV2画像オブジェクト(NumPy配列)を生成
cv2_image = numpy.array(
	[
		[
			[77, 117, 110],
			[78, 126, 120],
			[92, 136, 136],
			[99, 135, 133],
			[103, 115, 105]
		],

		[
			[92, 182, 162],
			[92, 190, 168],
			[107, 189, 179],
			[95, 164, 150],
			[78, 174, 140]
		],

		[
			[113, 195, 180],
			[117, 174, 167],
			[131, 140, 142],
			[136, 189, 174],
			[85, 203, 174]
		],

		[
			[110, 224, 201],
			[117, 160, 155],
			[124,  96,  91],
			[154, 209, 194],
			[101, 221, 202]
		],

		[
			[95, 206, 184],
			[94, 202, 180],
			[99, 181, 156],
			[97, 193, 169],
			[95, 213, 190]
		]
	], dtype='uint8'
)

# CV2画像オブジェクトを描画
cv2.imshow('image', cv2_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

実行すると下のような小さな画像が表示されます。

NumPy配列の表示

この結果からも OpenCV2 で扱う画像オブジェクトの実体は NumPy 配列であることが理解していただけると思います。

ただし、ピクセルデータが RGB ではなく BGR の形式になっている点には注意が必要です。

OpenCV2 で扱う画像オブジェクトの実体は NumPy 配列ですので、OpenCV2 画像オブジェクトに変換するためには、「NumPy 配列への変換」を行えば良いことになります。

OpenCV2 画像オブジェクトからの変換に関しては「NumPy 配列からの変換」を考える必要があります。

こういった理由より、PIL ⇔ OpenCV2 ⇔ Tkinter 画像オブジェクトの変換には NumPy 配列が大きなポイントになります。

①Tkinter → OpenCV2

では、具体的にどうやって変換するかについて解説していきます。

まずは「Tkinter → OpenCV2」への変換です。はっきり言ってこれが一番難しいです。しかし使いどころがあんまりない…。

Tkinter → OpenCV2 の変換方法

Tkinter の画像オブジェクト(PhotoImage クラスのインスタンス)については、下記ページで解説しているように get メソッドにより各ピクセルの RGB データを取得することができます。

【Python】Tkinter PhotoImage の get・put を利用してピクセル単位で画像処理を行う(透過処理も)

したがって、この全ピクセルの RGB データを前述の構造に合わせて3次元リストを作成し、そこから NumPy 配列に変換(numpy.array で変換可能)してやれば OpenCV2 の画像オブジェクトに変換できることになります。

ただし前述の通り OpenCV2 で扱う画像オブジェクトは、各ピクセルのデータが RGB 形式ではなく BGR 形式で格納されていますので、RGB から BGR への変換も必要です(cv2.cvtColor により変換可能)。

以上により「Tkinter → NumPy 配列の変換」を行うことができますので、実質的に「Tkinter → OpenCV2 の変換」も行うことができたことになります。

Tkinter → OpenCV2 の変換スクリプト

「Tkinter → OpenCV2」の変換を行うサンプルスクリプトは下記になります。

Tkinter → OpenCV2
import tkinter
import cv2
import numpy


def tk_to_cv2(tk_image):
	'Tkinter -> CV2'

	# 画像の縦横サイズを取得
	height = tk_image.height()
	width = tk_image.width()

	# ピクセルデータの3次元リストを作成

	# 空のリストを作成
	bitmap = []
	for y in range(height):
		# 空のリストを作成
		line = []
		for x in range(width):

			# 座標(x,y)のピクセルデータを取得
			pixel = list(tk_image.get(x, y))

			# 取得したピクセルデータをリストに追加
			line.append(pixel)

		# 1行分のピクセルデータを追加
		bitmap.append(line)

	# 作成したリストをNumPy配列に変換
	cv2_rgb_image = numpy.array(bitmap, dtype='uint8')

	# RGB -> BGRによりCV2画像オブジェクトに変換
	cv2_image = cv2.cvtColor(cv2_rgb_image, cv2.COLOR_RGB2BGR)

	return cv2_image


# tkinter.PhptoImage実行用
root = tkinter.Tk()

# Tkinter画像オブジェクトを作成
tk_image = tkinter.PhotoImage(
	file="cute_cat.png"
)

# Tkinter画像オブジェクトをCV2画像オブジェクトに変換
cv2_image = tk_to_cv2(tk_image)

# CV2画像オブジェクトを描画
cv2.imshow('image', cv2_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

スポンサーリンク

②PIL → OpenCV2

次は「PIL → OpenCV2」への変換です。「Tkinter → OpenCV2」に比べると簡単です。 

PIL → OpenCV2 の変換方法

「Tkinter → OpenCV2」は、NumPy 配列を生成するために各画素のピクセルのデータを1つずつ get で取得する必要があったので変換が大変でした。

しかし PIL 画像オブジェクトの場合は、Numpy モジュールの提供する array 関数にそのオブジェクトを渡すだけで NumPy 配列を生成することが可能です。

ただしこちらも「Tkinter → OpenCV2」同様に、RGB から BGR への変換が必要です(cv2.cvtColor により変換可能)。

PIL → OpenCV2 の変換スクリプト

「PIL → OpenCV2」の変換を行うサンプルスクリプトは下記になります。

PIL → OpenCV2
import cv2
from PIL import Image
import numpy


def pil_to_cv2(pil_image):
	'PIL -> CV2'

	# pil_imageをNumPy配列に変換
	pil_image_array = numpy.array(pil_image)

	# RGB -> BGR によりCV2画像オブジェクトに変換
	cv2_image = cv2.cvtColor(pil_image_array, cv2.COLOR_RGB2BGR)

	return cv2_image


# 画像を読み込んでPIL画像オブジェクトを生成
pil_image = Image.open(
	"cute_cat.png"
)
# PIL画像オブジェクトをCV2画像オブジェクトに変換
cv2_image = pil_to_cv2(pil_image)

# CV2画像オブジェクトを描画
cv2.imshow('image', cv2_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

③OpenCV2 → PIL

続いては「OpenCV2 → PIL」への変換です。割と使いどころは多い変換です。 

OpenCV2 → PIL の変換方法

PIL の Image モジュールでは、NumPy 配列を PIL 画像オブジェクトに変換する関数を提供しています(Image.fromarray)。

したがって、OpenCV2 の画像オブジェクト(NumPy 配列)を BGR 形式から RGB 形式に変換し(cv2.cvtColor により変換可能)、さらに変換後の NumPy 配列を Image.fromarray 関数に渡してやれば、PIL の画像オブジェクトに変換することができます。

OpenCV2 → PIL の変換スクリプト

「OpenCV2 → PIL」の変換を行うサンプルスクリプトは下記になります。

OpenCV2 → PIL
import cv2
from PIL import Image
import numpy


def cv2_to_pil(cv2_image):
	'CV2 -> PIL'

	# BGR -> RGB
	rgb_cv2_image = cv2.cvtColor(cv2_image, cv2.COLOR_BGR2RGB)

	# NumPy配列からPIL画像オブジェクトを生成
	pil_image = Image.fromarray(rgb_cv2_image)

	return pil_image


# 画像を読み込んでCV2画像オブジェクトを生成
cv2_image = cv2.imread("cute_cat.png")

# CV2画像オブジェクトをPIL画像オブジェクトに変換
pil_image = cv2_to_pil(cv2_image)

# PIL画像オブジェクトを描画
pil_image.show()

④Tkinter → PIL

今度は「Tkinter → PIL」への変換です。 

Tkinter → PIL の変換方法

「Tkinter → PIL」の変換は、①の「Tkinter → OpenCV2」変換を行ったのち、③の「OpenCV2 → PIL」変換を行うことで実現することができます。

MEMO

「Tkinter → PIL」変換を直接行う方法は分かりませんでした…

Tkinter → PIL の変換スクリプト

「Tkinter → PIL」の変換を行うサンプルスクリプトは下記になります。

Tkinter → PIL
import tkinter
import cv2
import numpy


def tk_to_cv2(tk_image):
	'Tkinter -> CV2'

	# 画像の縦横サイズを取得
	height = tk_image.height()
	width = tk_image.width()

	# ピクセルデータの3次元リストを作成

	# 空のリストを作成
	bitmap = []
	for y in range(height):
		# 空のリストを作成
		line = []
		for x in range(width):

			# 座標(x,y)のピクセルデータを取得
			pixel = list(tk_image.get(x, y))

			# 取得したピクセルデータをリストに追加
			line.append(pixel)

		# 1行分のピクセルデータを追加
		bitmap.append(line)

	# 作成したリストをNumPy配列に変換
	cv2_rgb_image = numpy.array(bitmap, dtype='uint8')

	# RGB -> BGRによりCV2画像オブジェクトに変換
	cv2_image = cv2.cvtColor(cv2_rgb_image, cv2.COLOR_RGB2BGR)

	return cv2_image


# tkinter.PhptoImage実行用
root = tkinter.Tk()

# Tkinter画像オブジェクトを作成
tk_image = tkinter.PhotoImage(
	file="cute_cat.png"
)

# Tkinter画像オブジェクトをCV2画像オブジェクトに変換
cv2_image = tk_to_cv2(tk_image)

# CV2画像オブジェクトを描画
cv2.imshow('image', cv2_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

スポンサーリンク

⑤PIL → Tkinter

次は「PIL → Tkinter」への変換です。Tkinter へ変換できると、画像処理後の画像を GUI アプリに表示できるようになるので、特に GUI アプリ開発者は覚えておいた方が良いと思います。 

PIL → Tkinter の変換方法

PIL の ImageTk モジュールが提供する PhotoImage クラスを利用すれば Tkinter の画像オブジェクトに一発で変換できます。

より具体的には、PhotoImage クラスのコンストラクタに PIL の画像オブジェクトを引数として渡してやれば、その画像オブジェクトを Tkinter の画像オブジェクトに変換したオブジェクトが生成されます。

PIL → Tkinter の変換スクリプト

「PIL → Tkinter」の変換を行うサンプルスクリプトは下記になります。

PIL → Tkinter
import tkinter
from PIL import Image, ImageTk
import numpy


def pil_to_tk(pil_image):
	'PIL -> Tkinter'

	# Tkinter画像オブジェクトをPIL画像オブジェクトから生成
	tk_image = ImageTk.PhotoImage(pil_image)

	return tk_image


# Tk root を作成
root = tkinter.Tk()

# PIL画像オブジェクトを生成
pil_image = Image.open(
	"cute_cat.png"
)

# PIL画像オブジェクトをTkinter画像オブジェクトに変換
tk_image = pil_to_tk(pil_image)

# キャンバスの作成と配置
canvas = tkinter.Canvas(
	root,
	width=tk_image.width(),
	height=tk_image.height()
)
canvas.pack()

# Tkinter画像オブジェクトの描画
canvas.create_image(
	tk_image.width() // 2,
	tk_image.height() // 2,
	image=tk_image
)

root.mainloop()

⑥OpenCV2 → Tkinter

最後は「OpenCV2 → Tkinter」への変換です。OpenCV2 で高度な処理を施した結果を自作の GUI アプリに表示したい場合に活躍します。 

OpenCV2 → Tkinter の変換方法

「OpenCV2 → Tkinter」の変換は、③の「OpenCV2 → PIL」の変換を行った後、⑤の「PIL → Tkinter」の変換を行うことで実現することができます。

OpenCV2 の画像オブジェクトは前述の通り実体は NumPy 配列ですので、その配列からピクセルデータを取得し、そのピクセルデータを下記ページで解説している Tkinter PhotoImage クラスの put メソッドを利用して Tkinter の画像オブジェクトに変換することも可能だと思います。

【Python】Tkinter PhotoImage の get・put を利用してピクセル単位で画像処理を行う(透過処理も)

ですが、処理速度が遅いので注意が必要です。

OpenCV2 → Tkinter の変換スクリプト

「OpenCV2 → Tkinter」の変換を行うサンプルスクリプトは下記になります。

OpenCV2 → Tkinter
import tkinter
import cv2
from PIL import Image, ImageTk
import numpy


def cv2_to_tk(cv2_image):
	'CV2 -> Tkinter'

	# BGR -> RGB
	rgb_cv2_image = cv2.cvtColor(cv2_image, cv2.COLOR_BGR2RGB)

	# NumPy配列からPIL画像オブジェクトを生成
	pil_image = Image.fromarray(rgb_cv2_image)

	# PIL画像オブジェクトをTkinter画像オブジェクトに変換
	tk_image = ImageTk.PhotoImage(pil_image)

	return tk_image


# Tk root を作成
root = tkinter.Tk()

# CV2画像オブジェクトを生成
# 画像を読み込んでCV2画像オブジェクトを生成
cv2_image = cv2.imread("cute_cat.png")

# CV2画像オブジェクトをTkinter画像オブジェクトに変換
tk_image = cv2_to_tk(cv2_image)

# キャンバスの作成と配置
canvas = tkinter.Canvas(
	root,
	width=tk_image.width(),
	height=tk_image.height()
)
canvas.pack()

# Tkinter画像オブジェクトの描画
canvas.create_image(
	tk_image.width() // 2,
	tk_image.height() // 2,
	image=tk_image
)

root.mainloop()

まとめ

このページでは PIL ⇔ OpenCV2 ⇔ Tkinter 画像オブジェクトの相互変換方法の解説及びそのサンプルスクリプトの紹介を行いました。

ポイントは NumPy 配列だと思います。NumPy 配列への変換と NumPy 配列からの変換が出来てしまえば、あとは関数呼び出しで簡単に変換できてしまいます。

特に Tkinter 画像オブジェクトへの変換は、PIL や OpenCV2 で処理した画像を自前の GUI アプリに表示するのに便利ですので、是非変換方法を覚えておいてください!

コメントを残す

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