質問: result) TypeError: print_text() got an unexpected keyword argument 'result' ``` と、結果となるresultをTkinterウィンドウの関数が受け取ることができませんでした。 変数を共有するには、どうしたらよいでしょうか?
回答: > > TypeError: print_text() got an unexpected keyword argument 'result' > > > t2 のスレッドとは別に、t1 スレッドの save_frame_camera_cycle関数内から `print_text(result = result)` のように呼び出されていますが、 関数print_text()は引数を取るように設計されていません。 > > RuntimeError: main thread is not in main loop > > > これは、全く同じエラーは再現できなかったのですが tkinterのウィンドウを閉じた後のものではないでしょうか? ウィンドウを閉じられた後に、別スレッドからtkinterを操作しようとして 実行時エラーになっているものだと思われます。 (エラーの行自体の問題ではなく、終了時のタイミング次第) 改善案: ``` - tkinterは普通にメインスレッドで実行するようにします。 - save_frame_camera_cycleのスレッドをデーモン化する(daemon=True) - これにより、tkinterのウィンドウが閉じられた時(メインスレッドが終了した時) サブスレッドも一緒に終了するようになるため、 サブスレッドが終了後のtkinterにアクセスすることを(ある程度)防げます。 ``` スレッド→Tkinterへの値の受け渡しに関して、 スレッド内から別スレッドで作った GUI の描画を直接行うのは スレッドセーフな操作ではないので、 GUIライブラリが用意しているキューを経由して行います。 tkinterの場合は`root.after()`を使います。 スレッドで演算した結果をtkinterに表示するデモ ``` import time import threading import tkinter as tk # ウィンドウ表示の定義 def create_result_viewer(): root = tk.Tk() label = tk.Label(root) label.pack() return root, label # 認識システムを関数で定義 def save_frame_camera_cycle(cycle, updateResult, dummyResultList): while True: for _ in range(cycle): time.sleep(1) else: updateResult(next(dummyResultList)) if __name__ == '__main__': # resultをシミュレートするダミーの結果リスト import itertools dummyResultList = itertools.cycle([1, 0, 1, 1, 0, 0, 0, 1]) root, label = create_result_viewer() def showResult(result): if result == 1: label["text"] = "これはティッシュです" else: label["text"] = "これは床です" def updateResult(result): root.after(100, lambda: showResult(result)) # NOTE: この小さなサンプルプログラムでは、updateResult の代わりに showResult を引数に渡しても動作しますが、 # 複雑になってくると破綻するので、root.after 経由で実行するほうが安全。(スレッドセーフ) t1 = threading.Thread(target=save_frame_camera_cycle, args=(5, updateResult, dummyResultList), daemon=True) t1.start() root.mainloop() ``` 課題: cv2.imshow, cv2.waitKey はスレッド内では期待通りに動作しません cv2.imshow でウィンドウが表示されますがフリーズ状態になります。 (上記サンプルコードからは意図的に省きました) cv2.imshowとtkinterを同時に使う場合は、 双方をメインスレッドで動かすことになるので、 プロセスを別ける(マルチプロセス)事になると思います。 マルチスレッドで行う場合、tkinterを用いてるので、 imshowの代わりにtkitnerのキャンバスに描画するのが良いでしょう。 cv2.waitKey の入力待ちは、tkinterでキー入力を受けてキューに入れ、 スレッド側で非同期で読み出すことで実現できます。 以下の質問の回答で、tkinter+opencvでの マルチプロセスやマルチスレッドでのキューを用いた例がありました。 関連 * opencvで異常検知すると、Tkinterにて警告を出したい マルチプロセスでキューを使って通信 * Tkinterのウインドウ内にwebカメラから取得した動画を表示したい * tkinterでRunTimeErrorを出ないようにしたい * Tkinterで別スレッドの終了方法 キューを使って通知