tkinter

【Python/tkinter】OpenCVのカメラ動画をCanvasに表示する

USBカメラなどで取得した画像(動画)をOpenCVの cv2.imshow() で表示するには比較的簡単に表示する事ができますが、tkinterを使ってWindow付でCanvasに表示したい場合には、少しコツが必要になります。

そこで、tkinterのCanvasに動画を表示する方法を紹介します。

OpenCVでUSBカメラの動画を表示

USBカメラの動画をOpenCVのの cv2.imshow() で表示するプログラムは、以下のようにすれば表示されます。

import cv2

# カメラをオープンする
capture = cv2.VideoCapture(0)

# カメラがオープン出来たか?
camera_opened = capture.isOpened()

while camera_opened:

    # フレーム画像の取得
    ret, frame = capture.read()
    
    # 画像の表示
    cv2.imshow("Image", frame)

    if cv2.waitKey(1) != -1:
        # キー入力で終了
        break

capture.release()
cv2.destroyAllWindows()

tkinterのCanvasに動画を表示

tkinterのCanvasに動画を表示する場合は、OpenCVの動画表示プログラムの cv2.imshow() の部分を書き換えて表示しようとしても動画が表示されません。

これは、while文中でCanvasへ画像を表示しようとすると、画像の更新スレッドがブロックされた状態となってしまい、画像を更新するには、少しの時間、スレッドを空ける必要があります。

このスレッドを空ける処理には after() 関数を使います。

after() 関数では、指定した時間を待ってから、指定した関数を実行することができます。

処理の意味合い的には sleep() 関数とも似ていますが、sleep() 関数では、指定時間分、スレッドをブロックしてしまいますが、 after() 関数ではスレッドがブロックされません。

それらを考慮し、tkinterのCanvas上にUSBカメラの動画を表示したプログラムが以下になります。

 

以下のサンプルプログラムでは、Camvas上をマウスの左ボタンをクリックすると、画像の取得が開始/停止するようになっています。

import tkinter as tk
from tkinter import filedialog
from PIL import Image, ImageTk, ImageOps  # 画像データ用

import cv2

class Application(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)
        self.pack()

        self.master.title("OpenCVの動画表示")       # ウィンドウタイトル
        self.master.geometry("400x300")     # ウィンドウサイズ(幅x高さ)
        
        # Canvasの作成
        self.canvas = tk.Canvas(self.master)
        # Canvasにマウスイベント(左ボタンクリック)の追加
        self.canvas.bind('<Button-1>', self.canvas_click)
        # Canvasを配置
        self.canvas.pack(expand = True, fill = tk.BOTH)

        # カメラをオープンする
        self.capture = cv2.VideoCapture(0)

        self.disp_id = None

    def canvas_click(self, event):
        '''Canvasのマウスクリックイベント'''

        if self.disp_id is None:
            # 動画を表示
            self.disp_image()
        else:
            # 動画を停止
            self.after_cancel(self.disp_id)
            self.disp_id = None

    def disp_image(self):
        '''画像をCanvasに表示する'''

        # フレーム画像の取得
        ret, frame = self.capture.read()
    
        # BGR→RGB変換
        cv_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        # NumPyのndarrayからPillowのImageへ変換
        pil_image = Image.fromarray(cv_image)

        # キャンバスのサイズを取得
        canvas_width = self.canvas.winfo_width()
        canvas_height = self.canvas.winfo_height()

        # 画像のアスペクト比(縦横比)を崩さずに指定したサイズ(キャンバスのサイズ)全体に画像をリサイズする
        pil_image = ImageOps.pad(pil_image, (canvas_width, canvas_height))

        # PIL.ImageからPhotoImageへ変換する
        self.photo_image = ImageTk.PhotoImage(image=pil_image)

        # 画像の描画
        self.canvas.delete("all")
        self.canvas.create_image(
                canvas_width / 2,       # 画像表示位置(Canvasの中心)
                canvas_height / 2,                   
                image=self.photo_image  # 表示画像データ
                )

        # disp_image()を10msec後に実行する
        self.disp_id = self.after(10, self.disp_image)

if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

実行結果

Python tkinter CanvasにOpenCVの動画を表示する

ポイント

  • 動画を表示するときは、while文ではなく、after()関数で繰り返し処理を行う。
  • OpenCVの画像は、カラー画像の場合、BGRのデータの並びをRGBへ並び替えてからOpenCVの画像データ(NumPyのndarray)をPillowのImageへImage.fromarray()関数で変換する。

関連記事

【OpenCV-Python】Tkinter GUI Sample
OpenCVのPython版でもC#のようなウィンドウのGUIプログラムを作りたい! ということで、GUIにTkinterを使って、簡単なサンプルプログラムを作成しました。 ここで公開しているコードは自由に変更して使って頂いてもらって構いま...
【Python/tkinter】Canvasに画像を表示する
まず、Canvasを作成し、画像ファイルを開き、Canvasに画像を表示するサンプルは以下のようになります。 import tkinter as tk from PIL import ImageTk class Application(tk...
【Python】画像データ(NumPy,Pillow(PIL))の相互変換
Pythonで画像処理をしていると、画像データの扱いは各ライブラリによって、NumPyのndarrayかPillowのPIL.Imageのどちらかになる場合が多いかと思います。 そこで NumPyとPillowの画像データの相互変換をまとめ...
【Python/NumPy】カラー画像データをRGBからBGRへ変換
カラー画像データは各データが8bit(0~255の256諧調)のR, G, Bの要素からなる24bitカラー画像や、さらに透過率(A)を追加しR, G, B, Aの要素からなる32bitカラー画像があります。 Pythonでは、このカラー画...

コメント

タイトルとURLをコピーしました