質問をすることでしか得られない、回答やアドバイスがある。

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

新規登録して質問してみよう
ただいま回答率
85.48%
scikit-learn

scikit-learnは、Pythonで使用できるオープンソースプロジェクトの機械学習用ライブラリです。多くの機械学習アルゴリズムが実装されていますが、どのアルゴリズムも同じような書き方で利用できます。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Tkinter

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

Q&A

解決済

1回答

2580閲覧

Tkinterのウィンドウに画像認識の結果を渡したいが、フリーズしてしまう

falcon_titan

総合スコア14

scikit-learn

scikit-learnは、Pythonで使用できるオープンソースプロジェクトの機械学習用ライブラリです。多くの機械学習アルゴリズムが実装されていますが、どのアルゴリズムも同じような書き方で利用できます。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Tkinter

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

0グッド

0クリップ

投稿2020/03/19 10:20

編集2020/03/20 09:14

ウィンドウのフリーズ
現在画像認識のプログラムを作成しており、認識自体はできるようになりました。
実行の結果をTkinterで文字として表示しようと思ったのですが、動作が固まってしまいます。
Tkinterのウィンドウを消せばまた認識をしてくれるのですが、止まることなく実行するにはどうすればいいでしょうか。
解決策を教えていただけると嬉しいです。

画像認識について
認識は、あらかじめ作成したデータを読み込んで使用しています。
写真はcv2で撮影したリアルタイム映像から、静止画として同じ階層に一時的に保存し、
PILで読み込んでいます。

環境
python 3.6.1
opencv-contrib-python 4.2.0.32
opencv-python 4.2.0.32
pillow 4.1.1
scikit-learn 0.18.1

コードです↓

python

1import cv2 2import os 3import datetime 4import pickle 5from PIL import Image 6from sklearn import svm 7import numpy as np 8import tkinter as tk 9from tkinter import font 10 11# ウィンドウ表示の定義 12def print_text(result): 13 root = tk.Tk()#ウィンドウを作る 14 15 root.title("ティッシュ認識システム") #タイトルの変更 16 root.geometry("300x100") #サイズの指定 17 18 font1 = font.Font(family='Helvetica', size=20, weight='bold') #フォントを設定 19 20 text = tk.StringVar()#textをStringVar関数にする 21 22 if result ==1: 23 text.set("これはティッシュです") 24 25 else: 26 text.set("これは床です") 27 28 label = tk.Label(textvariable = text, font = font1) #ラベルを追加 29 label.pack() 30 31 root.mainloop() #実行して表示 32 33 34# 画像認識の学習データを読み込む 35filename = 'tissue_model_1.sav' 36clf = pickle.load(open(filename, 'rb')) 37 38# 変数の定義 39size = 16 40 41# 認識システムを関数で定義 42def save_frame_camera_cycle(device_num, dir_path, basename, cycle, ext='JPG', delay=1, window_name='frame'): 43 cap = cv2.VideoCapture(1)# カメラは1(usbカメラ)を用いる 44 45 if not cap.isOpened():#カメラが見つからなかった場合 46 return#中止する  47 48 os.makedirs(dir_path, exist_ok=True) 49 base_path = os.path.join(dir_path, basename) 50 51 n = 0 52 while True: #Trueでずっとループさせる 53 ret, frame = cap.read() 54 cv2.imshow(window_name, frame) 55 if cv2.waitKey(delay) & 0xFF == ord('q'):# qが押されたら終了する 56 break 57 if n == cycle:#cycleとnが同じ数になったら写真を保存する 58 59 cv2.imwrite('{}_{}.{}'.format(base_path, datetime.datetime.now().strftime('%Y%m%d%H%M%S%f'), ext), frame)#画像を保存(学習用に保存) 60 cv2.imwrite('picture.' + ext, frame)#認識用の画像を保存 61 img = Image.open('picture.JPG')#画像を開く 62 img = img.resize((size,size))#リサイズ 63 ary = np.array(img).reshape(-1,)#一次元の配列にする 64 result = clf.predict(ary)#認識 65 print(result)#結果を表示 66 67 print_text(result = result) 68 69 n = 0 70 n += 1 71 72 cv2.destroyWindow(window_name) 73 74#関数を実行 75save_frame_camera_cycle(0, 'camera_data', 'camera_capture_cycle', 15)

アドバイスを受け、マルチスレッドで実行するようにプログラムを変更しました。

python

1import cv2 2import os 3import datetime 4from sklearn import svm 5import pickle 6from PIL import Image 7import numpy as np 8import tkinter as tk 9from tkinter import font 10import threading 11 12# ウィンドウ表示の定義 13def print_text(): 14 root = tk.Tk()#ウィンドウを作る 15 16 root.title("ティッシュ認識システム") #タイトルの変更 17 root.geometry("300x100") #サイズの指定 18 19 font1 = font.Font(family='Helvetica', size=20, weight='bold') #フォントを設定 20 21 text = tk.StringVar()#textをStringVar関数にする 22 23 if result ==1: 24 text.set("これはティッシュです") 25 26 else: 27 text.set("これは床です") 28 29 label = tk.Label(textvariable = text, font = font1) #ラベルを追加 30 label.pack() 31 32 root.mainloop() #実行して表示 33 print("a") 34 35# 画像認識の学習データを読み込む 36filename = 'tissue_model_1.sav' 37clf = pickle.load(open(filename, 'rb')) 38 39# 変数の定義 40size = 16 41 42# 認識システムを関数で定義 43def save_frame_camera_cycle(device_num, dir_path, basename, cycle, ext='JPG', delay=1, window_name='frame'): 44 cap = cv2.VideoCapture(1)# カメラは1(usbカメラ)を用いる 45 46 if not cap.isOpened():#カメラが見つからなかった場合 47 return#中止する  48 49 os.makedirs(dir_path, exist_ok=True) 50 base_path = os.path.join(dir_path, basename) 51 52 n = 0 53 while True: #Trueでずっとループさせる 54 ret, frame = cap.read() 55 cv2.imshow(window_name, frame) 56 if cv2.waitKey(delay) & 0xFF == ord('q'):# qが押されたら終了する 57 break 58 if n == cycle:#cycleとnが同じ数になったら写真を保存する 59 60 cv2.imwrite('{}_{}.{}'.format(base_path, datetime.datetime.now().strftime('%Y%m%d%H%M%S%f'), ext), frame)#画像を保存 61 cv2.imwrite('picture.' + ext, frame)#画像を1時的に保存 62 img = Image.open('picture.JPG')#画像を開く 63 img = img.resize((size,size))#リサイズ 64 ary = np.array(img).reshape(-1,)#一次元の配列にする 65 global result 66 result = clf.predict(ary)#認識 67 print(result)#結果を表示 68 69 print_text(result = result) 70 71 n = 0 72 n += 1 73 74 cv2.destroyWindow(window_name) 75 76#関数を実行 77t1 = threading.Thread(target = save_frame_camera_cycle, args = (0, 'camera_data', 'camera_capture_cycle', 15)) 78t2 = threading.Thread(target = print_text,) 79t2.start() 80t1.start()

しかし、

python

1Exception in thread Thread-12: 2Traceback (most recent call last): 3 File "C:\Users\ユーザー名\Anaconda3\lib\threading.py", line 916, in _bootstrap_inner 4 self.run() 5 File "C:\Users\ユーザー名\Anaconda3\lib\threading.py", line 864, in run 6 self._target(*self._args, **self._kwargs) 7 File "<ipython-input-8-c90b360b3e2d>", line 19, in print_text 8 font1 = font.Font(family='Helvetica', size=20, weight='bold') #フォントを設定 9 File "C:\Users\ユーザー名\Anaconda3\lib\tkinter\font.py", line 93, in __init__ 10 tk.call("font", "create", self.name, *font) 11RuntimeError: main thread is not in main loop 12 13C:\Users\ユーザー名\Anaconda3\lib\site-packages\sklearn\utils\validation.py:395: DeprecationWarning: Passing 1d arrays as data is deprecated in 0.17 and will raise ValueError in 0.19. Reshape your data either using X.reshape(-1, 1) if your data has a single feature or X.reshape(1, -1) if it contains a single sample. 14 DeprecationWarning) 15Exception in thread Thread-11: 16Traceback (most recent call last): 17 File "C:\Users\ユーザー名\Anaconda3\lib\threading.py", line 916, in _bootstrap_inner 18 self.run() 19 File "C:\Users\ユーザー名\Anaconda3\lib\threading.py", line 864, in run 20 self._target(*self._args, **self._kwargs) 21 File "<ipython-input-8-c90b360b3e2d>", line 69, in save_frame_camera_cycle 22 print_text(result = result) 23TypeError: print_text() got an unexpected keyword argument 'result'

と、結果となるresultをTkinterウィンドウの関数が受け取ることができませんでした。
変数を共有するには、どうしたらよいでしょうか?

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

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

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

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

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

t_obara

2020/03/19 10:26

GUIプログラムでは基本的にUIの反応をmainloopで行なっているためmainloop以外のループを使用してはいけません。その場合には、マルチスレッド、マルチプロセス、タイマーなどを利用する必要があります。
falcon_titan

2020/03/20 08:57

分かりました。マルチスレッドを利用してみます。アドバイスありがとうございます。
falcon_titan

2020/03/20 09:15

https://python.civic-apps.com/threading/ を参考に、threadingを用いてマルチスレッドにしてみたのですがうまくいきませんでした。 変数を共有させるには、どうしたらよいでしょうか?
guest

回答1

0

ベストアンサー

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での
マルチプロセスやマルチスレッドでのキューを用いた例がありました。

関連

マルチプロセスでキューを使って通信

投稿2020/03/21 01:56

編集2020/03/21 11:05
teamikl

総合スコア8664

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問