【Python】tkinterのGUIにmatplotlibのグラフを表示する

matplotlibを使ってグラフを表示すると、通常は、matplotlib独自のウィンドウで表示されますが、これをtkinterのGUIに組み込んで表示する方法を紹介します。

基本的な処理の流れとしては、matplotlibのFigureクラスでグラフの描画領域を確保し、グラフ描画用の座標軸を作成します。

FigureCanvasTkAggクラスで作成したFigureとFigureの配置先のウィジェットを指定し、matplotlib用のキャンバスを作成します。

グラフを描画する時は、作成した軸に対してグラフを描画し、最後にFigureCanvasTkAggクラスで作成したオブジェクトのdraw()メソッドを呼び出して、グラフを表示します。

 

以下にできるだけシンプルにしたサンプルを示します。

import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import numpy as np

class Application(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.master = master
        self.master.title('matplotlib graph')

        #-----------------------------------------------

        # matplotlib配置用フレーム
        frame = tk.Frame(self.master)
        
        # matplotlibの描画領域の作成
        fig = Figure()
        # 座標軸の作成
        self.ax = fig.add_subplot(1, 1, 1)
        # matplotlibの描画領域とウィジェット(Frame)の関連付け
        self.fig_canvas = FigureCanvasTkAgg(fig, frame)
        # matplotlibのツールバーを作成
        self.toolbar = NavigationToolbar2Tk(self.fig_canvas, frame)
        # matplotlibのグラフをフレームに配置
        self.fig_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)

        # フレームをウィンドウに配置
        frame.pack()

        # ボタンの作成
        button = tk.Button(self.master, text = "Draw Graph", command = self.button_click)
        # 配置
        button.pack(side = tk.BOTTOM)

        #-----------------------------------------------

    def button_click(self):
        # 表示するデータの作成
        x = np.arange(-np.pi, np.pi, 0.1)
        y = np.sin(x)
        # グラフの描画
        self.ax.plot(x, y)
        # 表示
        self.fig_canvas.draw()

root = tk.Tk()
app = Application(master=root)
app.mainloop()

(実行結果)

 

ポイント

Figureは、今回のやり方とは別に、通常のmatplotlibのウィンドウで表示する場合は、

import matplotlib.pyplot as plt

fig = plt.figure()

のようにする場合もありますが、このようにすると、tkinterで作成したウィンドウの×ボタンでプログラムを終了させても、プロセスが終了しない状態になってしまうので、注意が必要です。

参考

https://matplotlib.org/stable/gallery/user_interfaces/embedding_in_tk_sgskip.html

matplotlibで画像データ(OpenCV,pillow,list)を表示する

matplotlibを使って画像を表示すると、下図のように画像の画像の座標軸が表示され、右下にはマウスポインタの座標および、その位置の画像の輝度値が表示されるので便利です。

さらに矢印アイコンで、画像の移動、虫眼鏡アイコンで画像の領域を選択すると、その領域が拡大表示されます。

 

Pillow画像の表示

上図ではPillowで画像を開いて、matplotlibで表示しているのですが、そのプログラムは以下の通りです。

import matplotlib.pyplot as plt
from PIL import Image

# Pillow でカラー画像を読み込む
pil_image_color = Image.open("Parrots.bmp")
# matplotlibで表示
plt.imshow(pil_image_color)    
plt.show()

ただし、モノクロ画像を以下のようなプログラムで画像を表示すると、変な色合いになります。

import matplotlib.pyplot as plt
from PIL import Image

# Pillow でモノクロ画像を読み込む
pil_image_color = Image.open("Text.bmp")
# matplotlibで表示
plt.imshow(pil_image_color)    
plt.show()

 

モノクロのグレースケールで表示するには、imshowの引数にグレースケールの cmap(カラーマップ、カラーパレット) “gray” を指定して、以下のようにします。

import matplotlib.pyplot as plt
from PIL import Image

# Pillow でモノクロ画像を読み込む
pil_image_color = Image.open("Text.bmp", cmap = "gray")
# matplotlibで表示
plt.imshow(pil_image_color)    
plt.show()

 

このカラーマップ(cmap)にはデフォルトで “viridis” というものが設定されていて、下図のような色味になっています。

 

カラーマップは他にも hsv や rainbow, jet など様々用意されており、例えば、rainbowを使うと、下図のようになります。

 

カラーマップは色々用意されているので、モノクロ画像を疑似カラーで表示したい場合は、gray以外のものを使用するのも便利だと思います。

カラーマップの詳細については、以下のページで確認できます。

https://matplotlib.org/stable/gallery/color/colormap_reference.html

 

OpenCV画像の表示

matplotlibのimshow関数に指定する画像データはPillowの画像以外にも、OpenCVの画像データ(実態はnumpyのndarray)も指定可能です。

以下のようなプログラムを実行すると

import matplotlib.pyplot as plt

import cv2

# OpenCVでモノクロ画像を読み込む
cv_image_mono = cv2.imread("Text.bmp", cv2.IMREAD_UNCHANGED)
print(type(cv_image_mono))
print(cv_image_mono.shape)
# matplotlibで表示
plt.imshow(cv_image_mono, cmap = "gray")    
plt.show()

下図のようにモノクロ画像が表示されます。

さらにコマンドライン上には、OpenCVで読み込んだ画像の型が <class ‘numpy.ndarray>と表示されていることが確認でき、OpenCVの画像データがnumpyのndarrayそのものだという事がわかります。

画像データのサイズを shapeで表示していますが、モノクロ画像データの時は、二次元配列となっています。

 

同様にして、OpenCVのカラー画像を表示すると、

import matplotlib.pyplot as plt

import cv2

# OpenCVでカラー画像を読み込む
cv_image_color = cv2.imread("Parrots.bmp", cv2.IMREAD_UNCHANGED)
print(type(cv_image_color))
print(cv_image_color.shape)
# matplotlibで表示
plt.imshow(cv_image_color)    
plt.show()

プログラムの実行結果は下図のようになります。

OpenCVのカラー画像をmatplotlibで表示すると、モノクロの時のように、また、変な色になってしまいます。

これは、OpenCVのカラー画像のデータの並びが B, G, R, B, G, R ・・・となっているのですが、matplotlibへは R, G, B, R, G, B ・・・の並びのデータで渡す必要があり、OpenCVのカラー画像をmatplotlibで表示するには、RGBのデータの並びを逆にする必要があります。

また、 shapeの結果が (256, 256, 3) と表示されているように、カラー画像の場合、三次元配列である事が確認できます。

 

BGRのデータをRGBのデータへ変換するには、OpenCVのcvtColor関数を使い

cv_image_color = cv2.cvtColor(cv_image_color, cv2.COLOR_BGR2RGB)

のように、2番目の引数にcv2.COLOR_BGR2RGBを指定し、RGBの並びを逆にします。

import matplotlib.pyplot as plt

import cv2

# OpenCVでカラー画像を読み込む
cv_image_color = cv2.imread("Parrots.bmp", cv2.IMREAD_UNCHANGED)

# BGR から RGB へ変換
cv_image_color = cv2.cvtColor(cv_image_color, cv2.COLOR_BGR2RGB)
#cv_image_color = cv_image_color[:, :, ::-1] # numpyのndarrayに対してスライスを使って反転
# matplotlibで表示
plt.imshow(cv_image_color, cmap = "gray")    
plt.show()

 

OpenCVの画像データはnumpyのndarrayなので、スライスを使って、BGRからRGBへ変換することも可能です。

cv_image_color = cv_image_color[:, :, ::-1]

ただし、この場合、カラー画像データが32bitの場合、OpenCVのカラーデータの並びはB, G, R, A となっているため、このまま並びを反転すると A, R, G, B となり、matplotlibで表示すると、変な色になってしまいます。

32bitのカラー画像をmatplotlibへ渡すには、R, G, B, A の順になっている必要があるため、numpyで並びを逆にしたい場合は

cv_image_color = cv_image_color[:, :, [2, 1, 0, 3]]

とする必要があり面倒なので、OpenCVを使っている場合、cvtColorを使った方がよさそうです。

ちなみに、OpenCVで32bitのカラー画像の並びを反転させるには、本来であれば、

cv_image_color = cv2.cvtColor(cv_image_color, cv2.COLOR_BGRA2RGBA)

もしくは

cv_image_color = cv2.cvtColor(cv_image_color, cv2.COLOR_BGRA2RGB)

とすべきなのですが、

cv_image_color = cv2.cvtColor(cv_image_color, cv2.COLOR_BGR2RGB)

としても、 cv2.COLOR_BGRA2RGBと同じ動き(32bitBGRAから24bitRGBへの変換)となっていました。

 

listデータの表示

matplotlibのimshowで表示する画像データは、ここ↓のページ

https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.imshow.html

を見ても分かるように、配列かPIL Imageを渡す事ができます。

この配列の値は 0~1のfloatの値か、もしくは 0~255のintの値であれば、表示することが可能です。

という事は、Deep Learningでは画像データの0~255を0~1へ変換することが多いので、便利そうです。

また、配列はnumpyのndarrayだけでなく、Pythonのlistも渡す事ができ、以下にサンプルを示します。

import matplotlib.pyplot as plt

list_data = [list(range(256)) for i in range(256)]
plt.imshow(list_data, cmap = "gray")    
plt.show()

 

参考

https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.imshow.html

https://matplotlib.org/stable/gallery/color/colormap_reference.html

【Python,matplotlib】動くグラフをAnimationGifに保存する方法

matplotlibで動くグラフを書いて、AnimationGifに保存するにはImageMagickという画像のソフトをmatplotlibから呼び出すように設定すれば、こんな風な動くグラフが作成できます。

 

まずはImageMagickの入手から。

ImageMagickは下記ページより、ダウンロードします。

http://www.imagemagick.org/script/download.php

 

Windows版のファイルは下の方にあるので、downloadをクリックしてファイル(ImageMagick-7.0.6-0-Q16-x64-dll.exe)を入手します。

 

ImageMagickのインストールはダウンロードしたファイルを実行し、基本デフォルトのままのインストールで大丈夫だと思います。

 

ImageMagickのインストールが出来たら、matplotlibの設定を行います。

 

まず、matplotlibのmatplotlibrcというファイルがある場所を確認します。

場所はPythonのプログラムで

 

import matplotlib
print(matplotlib.matplotlib_fname())

 

と入力し実行すると、matplotlibrcのファイルのパスが表示されるので、matplotlibrcファイルをメモ帳などのテキストエディタで開きます。

 

私の場合のパスは

C:\Program Files\Anaconda3\Lib\site-packages\matplotlib\mpl-data\matplotlibrc

でした。

 

このmatplotlibrcファイルの一番下の方に

 

#animation.convert_path: ‘convert’ #・・・・・・

と書いてある部分があるので、これを編集してもいいのですが、私は↓のようにImageMagickの実行ファイル(magick.exe)のパスを追記しました。

 

この状態で、このようなコード↓を書くと、最初に示したアニメーションGifのファイルが生成されます。

 

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

fig = plt.figure()
x = np.arange(-np.pi, np.pi, 0.1)

ims = []
for a in range(40):
    y = np.sin(x - a / 20 *  np.pi)
    # sinカーブ
    im1, = plt.plot(x, y, "b")
    # 点
    im2, = plt.plot(-np.pi, np.sin(-np.pi - a / 20 *  np.pi), marker='o', color='b' )

    ims.append([im1, im2])

ani = animation.ArtistAnimation(fig, ims, interval=33)
ani.save('sample.gif', writer='imagemagick')

 

Sinカーブだけのサンプルは探せばいっぱいあるのですが、線上に点を打つだけでも、訳わかんなかった~(まだ、分かってないですけど。。)