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()
実行結果
ポイント
- 動画を表示するときは、while文ではなく、after()関数で繰り返し処理を行う。
- OpenCVの画像は、カラー画像の場合、BGRのデータの並びをRGBへ並び替えてからOpenCVの画像データ(NumPyのndarray)をPillowのImageへImage.fromarray()関数で変換する。