🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
並列処理

複数の計算が同時に実行される手法

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

2回答

4372閲覧

tkinterの並列処理

t_n_k

総合スコア4

並列処理

複数の計算が同時に実行される手法

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

1クリップ

投稿2020/11/25 02:47

前提・実現したいこと

tkinterにpcカメラの映像を1ミリごとに表示するアプリを作りました。
そこで、映像の表示が途切れることなく、別のループ処理を実行する、という機能を加えたいと思っています。
要点は、root.afterを二つ並列して使いたいです。

発生している問題・エラーメッセージ

メインスレッドで映像処理をroot.afterで1ミリ秒ごとにループしているので、別のループ処理(xfunc)を別スレッドで実行しようとthreadingを使って書いてみたのですが、xfuncを実行している間、メインスレッドの映像処理が止まってしまいます。threadingの使い方が正しいかどうかを教えて頂きたいです。

該当のソースコード

python

1import tkinter as tk 2import cv2 3from PIL import Image 4from PIL import ImageTk 5import sys 6import threading 7import time 8wi=600 9he=400 10root=tk.Tk() 11root.geometry("600x400") 12canvas=tk.Canvas(root, width=wi, height=he) 13canvas.place(x=0,y=0) 14 15def capStart():#カメラのキャプチャー処理 16 global c, w, h, img 17 c=cv2.VideoCapture(0) 18 c.set(cv2.CAP_PROP_FRAME_WIDTH, wi) 19 c.set(cv2.CAP_PROP_FRAME_HEIGHT, he) 20 w, h= c.get(cv2.CAP_PROP_FRAME_WIDTH), c.get(cv2.CAP_PROP_FRAME_HEIGHT) 21 22def update():#キャプチャー映像をtkinterに表示する(処理を止めたくない) 23 global img 24 ret, frame =c.read() 25 if ret: 26 img=ImageTk.PhotoImage(Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))) 27 canvas.create_image(wi*0.5,he*0.5,image=img) 28 else: 29 print("Fail") 30 root.after(1,update) 31 32def xfunc():#時間のかかるループ処理。updata()を止めずにこの処理を並列して行いたい。 33 for i in range(10000000): 34 con=i 35 print(con) 36 root.after(1000,th1) 37  #root.after(1000,xfunc) #こちらもダメでした 38 39def subthreadRun(): 40 thread_1 = threading.Thread(target=xfunc) 41 thread_1.setDaemon(True) 42 thread_1.start() 43 44capStart() 45update() 46subthreadRun() 47root.mainloop() 48 49

試したこと

concurrent.futuresを使っても駄目でした。

補足情報(FW/ツールのバージョンなど)

windows10
python3.7
tkinter8.7

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

少し状況を整理して、回答はこれだけでも良いのかな。
update()関数のパフォーマンス面の問題は別として、要点についてのみ。

python

1 2def xfunc():#時間のかかるループ処理。updata()を止めずにこの処理を並列して行いたい。 3 for i in range(10000000): 4 con=i 5 print(con) 6 root.after(1000,th1) 7  #root.after(1000,xfunc) #こちらもダメでした 8

問題点: root.after で実行される関数は、メインスレッドでtkinterにより実行される。
別スレッドで実行されるのは一回目のみになってます。

解決策: 別スレッドであれば 1秒待つために time.sleep が使えます

def xfunc(): import time while True: for i in range(10000000): con=i print(con) time.sleep(1)

投稿2020/11/25 07:11

編集2020/11/25 07:12
teamikl

総合スコア8738

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

映像処理をroot.afterで1ミリ秒ごとにループしているので

映像の更新であれば、FPS を見積もって適切な値を設定してください。
1ms -> 1000fps なので、大抵 60fps (16ms) 辺りで十分なはずです。

xfuncを実行している間、メインスレッドの映像処理が止まってしまいます。

原因は主に、

  • 更新の間隔が早すぎる → 1ms ごとにループ
  • メインループで時間の掛かる処理を行ってる為

対策として、更新間隔を緩める他に
前処理迄は極力サブスレッド側で行います。具体的には

  • Tk側(main thread)で行う処理: ImageTk.PhotoImage
  • sub thread 側で行う処理: Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)))

サブスレッドからtkinter(メインスレッド)への通知手段は幾つか有り

  • キューを使う方法1:

 メインスレッド側で root.after を使い queue から読み出す

  • サブスレッド側から event_generate でイベント通知。queueから読みだす。
  • サブスレッド側から root.after_idle で通知・メインスレッドで実行

時間のかかるループ処理。updata()を止めずにこの処理を並列して行いたい。

  • update 自体も処理の一部を、サブスレッドにした方が良いです。
  • root.after(1000,th1) の th1 が何か解りませんでしたが、

 root.after で指定実行される関数は、main thread で実行されます。
なので、時間の掛かる処理は mainloop をブロックすることになり、
GUIの応答なしに繋がります。


※ 回答のコードではありません。
logging のサンプルと、スレッド内でのroot.afterの利用についての指摘。

python

1#!/usr/bin/env python3.8 2 3import tkinter as tk 4from threading import Thread 5import logging 6 7# POINT: 2回目以降の xfunc は MainThread で実行されるのが確認できると思います 8 9def xfunc(root): 10 logging.info("xfunc") 11 root.after(1000, xfunc, root) 12 13def xfunc2(root): # 別スレッドでの実行なら、1秒待つのには time.sleep が使えます 14 import time 15 while True: 16 logging.info("xfunc2") 17 time.sleep(1) 18 19def main(): 20 root = tk.Tk() 21 button = tk.Button(root, text="Quit", command=root.quit) 22 button.pack() 23 24 thread = Thread(target=xfunc, args=(root,), daemon=True) 25 thread.start() 26 27 root.mainloop() 28 29if __name__ == "__main__": 30 logging.basicConfig( 31 level=logging.DEBUG, 32 format="[%(threadName)-10s] %(message)s") 33 34 main()

デバッグのポイント、

  • ログを取る事で、関数がどのスレッドで実行されているかを把握しやすくします。
  • 止まってしまうのは、tkinter のイベントループが止まるのが原因です。

 root.after で実行される関数、bind で登録した関数などは全て
root.mainloop 内で実行されます。mainloop でGUIの描画やイベント処理をしているので、
これらの関数では、処理をすぐ終わり mainloop に制御を返さなければなりません。
ログを取ってみて、メインスレッドで時間の掛かる処理をしていないか調べて見ましょう。


追記
丁度最近、cv2.VideoCapture/tkinter での動作確認のために書いたコードがあったので、
解説等は出来ませんが、参考になればと思い張っておきます。
https://repl.it/@MiKLTea/tkVideoCanvas#main.py

  • update() 関数を、サブスレッド側(cv2) とメインスレッド(tkinter)に分離する場合の参考になると思います。
  • プログラムの終了時にエラーが出る場合は、状況次第ですが、

 リソースの解放順序を明示的にする必要があります。

投稿2020/11/25 06:59

編集2020/11/26 11:10
teamikl

総合スコア8738

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

t_n_k

2020/11/26 02:10

丁寧なご説明ありがとうございます。 update関数をupdate(画像をキャンバスに表示する処理、メインスレッドで実行)と、subupdate(カメラの映像から画像を保存、サブスレッドで実行)に分け、キューを使ってデータ間のやりとりもうまくいきました。 ただ、別のサブスレッドで時間のかかる処理(xfunc)を実行すると、その処理をしている間だけ、一瞬カメラの映像が止まってしまう、という問題は解決できませんでした。 そこで、スレッドを分化して処理しているかを確認するため、各関数にthreading.current_thread()をプリントし、どの関数でどのスレッドを使用しているかを確認したところ、updateは"MainThread"、subupdateは"Thread-6"、xfuncは"Thread-7"のように表示されたので、スレッドの並列処理もうまくいっているようなのですが。。。 メインスレッドを一瞬たりとも止めずに(カメラ映像を一瞬も止めたくない)、他の処理をサブスレッドで実行する、というのはそもそも可能でしょうか?
teamikl

2020/11/26 07:42 編集

>メインスレッドを一瞬たりとも止めずに(カメラ映像を一瞬も止めたくない) ここの要点は、mainloopの実行を妨げない事で実現可能です。 気を付けるべき点 - 処理時間待ちをするブロッキング操作(I/O や time.sleep 等) - 時間の掛かるループ 等は、mainloop を実行してるスレッドでは行わないようにします。 >他の処理をサブスレッドで実行する、というのはそもそも可能でしょうか? 処理内容の規模次第です。 Pythonのスレッドは単一コアで実行されるので、 処理能力を超えるタスクは実行できません。 もし、過剰な量の処理がある場合、mainloopに影響がある等は考えられます。 →解決策はマルチプロセスを検討 → もしくは、その処理の最適化 ==== 確認点: - fps の調整はされたでしょうか (更新間隔を fps に合わせて緩和) - xfunc 実行の度にスレッドが作成されるのですか?  それとも他にもスレッドがあり Thread-7 になっているのか。  もし、毎回スレッドを作成してるようであれば改善の余地あり。 - xfunc の処理内容を、簡単ですぐ終わるものにすると改善しますか? - リソースモニター等で CPU を確認。単純に処理が追い付いてない場合  (Pythonの)スレッドでは CPU bounce な処理の高速化は出来ない為、  「マルチプロセス」を検討することになります。 ※ マルチプロセスの場合、別プロセスとのデータのやり取りの仕方は変わってきます。 キュー等もマルチプロセス用のモノを使う。 画像データ(numpy の配列)は、python側の公式リファレンスに、 共有メモリを用いたデータ受け渡しの例有り。 追記: 状況次第ですが、全てをマルチプロセス化する必要もないので、 まずは原因を特定してからでよいと思います。
teamikl

2020/11/26 07:06

>threading.current_thread()をプリントし、どの関数でどのスレッドを使用しているかを確認したところ 利用サンプルとして、回答に書いたコードで使ってますが、 スレッドの確認は、loggingモジュールの利用がお勧めです。 デフォルトでスレッドセーフ。 マルチプロセスの場合は、 マルチプロセス対応の準備が必要ですが、 安全なログの枠組みは用意されてます。
teamikl

2020/11/26 07:20

確認点をもうひとつ >別のサブスレッドで時間のかかる処理(xfunc)を実行すると、その処理をしている間だけ、一瞬カメラの映像が止まってしまう、 xfunc 内では tkinter のリソースへアクセスせずに、 完全に独立してますか? tkinter 内部でも tkinter(python側) -> tcl/tk のライブラリ とのやり取りをしてます。 tkinter へのアクセスがある場合は、 mainloop の遅延に影響する可能性があります。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問