このページでは 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
透過画像については動作未確認ですのでご注意ください
というか対応してないです
必要があれば対応方法考えてみますので教えてください
Contents
画像変換のやり方まとめ
最初に結論として 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_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
で画像を表示することが可能です。
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()
実行すると下のような小さな画像が表示されます。
この結果からも OpenCV2 で扱う画像オブジェクトの実体は NumPy 配列であることが理解していただけると思います。
ただし、ピクセルデータが RGB ではなく BGR の形式になっている点には注意が必要です。
OpenCV2 で扱う画像オブジェクトの実体は NumPy 配列ですので、OpenCV2 画像オブジェクトに変換するためには、「NumPy 配列への変換」を行えば良いことになります。
OpenCV2 画像オブジェクトからの変換に関しては「NumPy 配列からの変換」を考える必要があります。
こういった理由より、PIL ⇔ OpenCV2 ⇔ Tkinter 画像オブジェクトの変換には NumPy 配列が大きなポイントになります。
スポンサーリンク
①Tkinter → OpenCV2
では、具体的にどうやって変換するかについて解説していきます。
まずは「Tkinter → OpenCV2」への変換です。はっきり言ってこれが一番難しいです。しかし使いどころがあんまりない…。
Tkinter → OpenCV2 の変換方法
Tkinter の画像オブジェクト(PhotoImage
クラスのインスタンス)については、下記ページで解説しているように get
メソッドにより各ピクセルの RGB データを取得することができます。
したがって、この全ピクセルの RGB データを前述の構造に合わせて3次元リストを作成し、そこから NumPy 配列に変換(numpy.array
で変換可能)してやれば OpenCV2 の画像オブジェクトに変換できることになります。
ただし前述の通り OpenCV2 で扱う画像オブジェクトは、各ピクセルのデータが RGB 形式ではなく BGR 形式で格納されていますので、RGB から BGR への変換も必要です(cv2.cvtColor
により変換可能)。
以上により「Tkinter → NumPy 配列の変換」を行うことができますので、実質的に「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」の変換を行うサンプルスクリプトは下記になります。
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」の変換を行うサンプルスクリプトは下記になります。
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」変換を行うことで実現することができます。
「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」の変換を行うサンプルスクリプトは下記になります。
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 の画像オブジェクトに変換することも可能だと思います。
ですが、処理速度が遅いので注意が必要です。
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 アプリに表示するのに便利ですので、是非変換方法を覚えておいてください!