勉強しないとな~blog

ちゃんと勉強せねば…な電気設計エンジニアです。

OpenCVやってみる - 48. GUIでカメラ画像表示

前の記事通り、GUIでのカメラ画像表示をやってみます。

前回と前々回の内容の組み合わせ+αです。

カメラ画像表示

前回、前々回の内容に加えて、OpenCV画像→PIL画像の変換があるぐらいです。

下記参照。

【Python/tkinter】OpenCVのカメラ動画をCanvasに表示する | イメージングソリューション

after()での待ちを入れていますが、いまいち理解できていないので、まずはキーボード入力で更新するようにします。

キー入力受付

と、そのためにはTkinterでキー入力受付が必要。
下記参照で。

Tkinter: イベントを検出する(クリック・キー入力・マウス移動)

キーの値はevent.keysymで取得。

tkinter超入門【第45回 キー入力イベント】 | ITよろず雑記帳

import tkinter
import cv2
from PIL import Image, ImageTk, ImageOps
def key_handler(e):
    print(e.keycode, e.keysym)

root = tkinter.Tk()
root.title('Key event test')
root.bind('<KeyPress>', key_handler)

frame = tkinter.Frame(root)

label = tkinter.Label(frame, text='Input text')
t = tkinter.StringVar()
entry =tkinter.Entry(frame, textvariable=t)

frame.pack()
label.pack()
entry.pack()

root.mainloop()

65 a
66 b
67 c
68 d
69 e
70 f
13 Return
27 Escape

最後にEnterキー、Escキーを押しています。
keycodeは基本的にはASCIIコード通りになっているのか。
環境にもよるかな?

今度こそカメラ画像表示

DroidCamアプリのIPアドレス、ポート番号は入力できるようにしておきます。
また、接続ボタンも用意します。

StringVar()は、ウィジェット変数で、ユーザーが入力した値をリアルタイムで反映してくれるものだそう。

Tkinterで使われるWidget変数とは?StringVarを中心に解説!? | 「モノづくりから始まるエンジニア」

今回は、スペースキーで画像更新にしましたが、テキストボックスとの併用はあんまりよくないかも。

  • テキストボックスにスペース入力をしたいだけでも画像更新されてしまう
  • テキストボックスをアクティブにしたときに、画像更新のスペースキーが入力になってしまう

このへんはGUI設計のときに要検討。

cap = cv2.VideoCapture()

root = tkinter.Tk()
root.title('Display DroidCam Image')
root.geometry('500x300')

# frame = tkinter.Frame(root, padx=10, pady=10)
frame = tkinter.Frame(root)
frame.pack()

#### Entries for connection information ####
t_ip = tkinter.StringVar(value='192.168.1.7')
t_port = tkinter.StringVar(value='4747')
entry_ip = tkinter.Entry(frame, textvariable=t_ip)
entry_port = tkinter.Entry(frame, textvariable=t_port)
entry_ip.grid(row=0, column=0)
entry_port.grid(row=1, column=0)

#### Connect button ####
text_connect = 'Connect   '
text_disconnect = 'Disconnect'
t_connect = tkinter.StringVar(value=text_connect)

def event_connect(e):
    if(t_connect.get() == text_connect):
        ret = cap.open(f'http://{t_ip.get()}:{t_port.get()}/video')
        if ret:
            print('Camera opened')
            t_connect.set(text_disconnect)
        else:
            print('Camera open failed')
    else:
        cap.release()
        print('Camera closed')
        t_connect.set(text_connect)

button_connect = tkinter.Button(frame, textvariable=t_connect)
button_connect.bind('<Button-1>', event_connect)
button_connect.grid(row=0, column=1)

#### Image canvas ####
canvas_image = tkinter.Canvas(frame, bg='white')
canvas_image.grid(row=2,column=0)
frame.update()
w, h = canvas_image.winfo_width(), canvas_image.winfo_height()
print(f'Canvas size: {w},{h}')
disp_img = None

#### Capture and display camera image on canvas ####
def update_image():
    global disp_img

    ret, img = cap.read()
    if not ret:
        print('Can\'t capture image')
        return
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img = Image.fromarray(img)
    img = ImageOps.pad(img, (w,h))
    disp_img = ImageTk.PhotoImage(image=img)
    canvas_image.create_image(w/2,h/2,image=disp_img)
    # canvas_image.create_image(0,0,image=img, anchor=tkinter.NW)
    print('updated')

def key_handler(e):
    # print(e.keysym)
    if e.keysym == 'space':
        update_image()

root.bind('<KeyPress>', key_handler)

root.mainloop()
Canvas size: 382,269
Camera opened
Camera closed
Camera opened
updated
updated
updated
updated
Camera closed

ちょっとはまってしまいましたが、なんとかできた。

Canvas.create_image()自体では画面の更新はしておらず、mainloopに戻ってから実際の更新をしているよう。
また、Canvas.create_image()では画像への参照を登録しているのみと思われます。

ということで、mainloop内からアクセスできる変数になっている必要があると。
で、グローバル変数を使いました。
グローバル変数を使う関数の中でglobal (変数名)を書いておかないといけないのも要注意。

【Python】tkinterのmainloopについて解説 | だえうホームページ

after()についても解説があり、結局理解することになりました。

ここまで

とりあえず今回やることはできました。
次回は春のパン祭りスクリプトとのつなぎになります。