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

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

新規登録して質問してみよう
ただいま回答率
85.50%
OpenCV

OpenCV(オープンソースコンピュータービジョン)は、1999年にインテルが開発・公開したオープンソースのコンピュータビジョン向けのクロスプラットフォームライブラリです。

Tkinter

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

Python

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

Q&A

解決済

2回答

4296閲覧

GUI上からopencvのカメラキャプチャーし、それをボタンで停止したい

ysyk77

総合スコア4

OpenCV

OpenCV(オープンソースコンピュータービジョン)は、1999年にインテルが開発・公開したオープンソースのコンピュータビジョン向けのクロスプラットフォームライブラリです。

Tkinter

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

Python

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

0グッド

0クリップ

投稿2021/08/06 05:12

編集2021/08/09 23:34

前提・実現したいこと

Python3.9.6 + tkinter + opencv という環境で、USBに接続したWEBカメラの画像を表示しています。
tkinterでGUIを作成し、”Camera”ボタンを押したらカメラキャプチャー画像を表示します。
”Quit”ボタンを押したらカメラキャプチャー画像を終了し、画面を閉じる仕様です。
カメラ画像を表示するウィンドウにフォーカスしキー入力すれば終了出来るのですが、tkinterのGUI上で停止する事が出来ません。
WEBで色々探し、スレッド等を試したのですが、実現出来ていません。
良い方法は無いでしょうか?
宜しくお願いします。

該当のソースコード

Python

1#!/usr/bin/python3 2# -*- coding: utf8 -*- 3import tkinter 4import cv2 5def Capture(): 6 cap = cv2.VideoCapture(0, cv2.CAP_DSHOW) 7 if cap.isOpened() == False: 8 return 9 while True: 10 ret, frame = cap.read() 11 name = "video" 12 cv2.namedWindow(name, cv2.WINDOW_NORMAL) 13 cv2.imshow(name,frame) 14 k = cv2.waitKey(1) 15 if k == 27: 16 break 17 cap.release() 18 cv2.destroyAllWindows() 19 return 20 21class Application(tkinter.Frame): 22 def __init__(self, master=None): 23 super().__init__(master) 24 self.pack() 25 self.create_widgets() 26 return 27 28 def create_widgets(self): 29 # capture 30 self.button = tkinter.Button(self) 31 self.button["text"] = u"Camera" 32 self.button["command"] = self.button_func 33 self.button["width"] = 20 34 self.button.pack() 35 36 # Quit 37 self.quit = tkinter.Button(self) 38 self.quit["text"] = u"Quit" 39 self.quit["width"] = 20 40 self.quit["command"] = self.master.destroy 41 self.quit.pack() 42 return 43 44 # Callbacks 45 def button_func(self): 46 Capture() 47 return 48 49# メイン関数 50def main(): 51 # Windowの生成 52 root = tkinter.Tk() 53 root.geometry("200x100") 54 root.title(u"Test") 55 app = Application(master=root) 56 app.mainloop() 57 pass 58 59if __name__ == '__main__': 60 main()

試したこと

マルチスレッドをWEB上の情報を参考に試しましたが、うまく行きませんでした。
マルチスレッド化したときのコードです。
cameraボタンを押しcaptureを開始すると、黒画面が出力し、ハングアップしてしまいます。

python

1 2#!/usr/bin/python3 3# -*- coding: utf8 -*- 4import tkinter 5import cv2 6import threading 7import time 8 9stopflag = 0 10 11# Thread Test → OK 12class ThreadJob(threading.Thread): 13 def __init__(self, v=""): 14 threading.Thread.__init__(self) 15 16 def run(self): 17 global stopflag 18 19 while not(stopflag): 20 print('hello,stop=',stopflag) 21 time.sleep(1) 22 if stopflag != 0: 23 break 24 25# camera capture 26class ThreadJob2(threading.Thread): 27 def __init__(self, v=""): 28 threading.Thread.__init__(self) 29 30 def run(self): 31 global stopflag 32 cap = cv2.VideoCapture(0, cv2.CAP_DSHOW) 33 if cap.isOpened() == False: 34 return 35 36 while not(stopflag): 37 ret, frame = cap.read() 38 name = "video" 39 cv2.namedWindow(name, cv2.WINDOW_NORMAL) 40 cv2.imshow(name,frame) 41 if stopflag != 0: 42 break 43 44 cap.release() 45 cv2.destroyAllWindows() 46 47 48class Application(tkinter.Frame): 49 50 def __init__(self, master=None): 51 super().__init__(master) 52 self.pack() 53 self.create_widgets() 54 55 def create_widgets(self): 56 # capture 57 self.button = tkinter.Button(self) 58 self.button["text"] = u"Camera" 59 self.button["command"] = self.button_func 60 self.button["width"] = 20 61 self.button.pack() 62 63 # stop 64 self.btnStop = tkinter.Button(self) 65 self.btnStop["text"] = u"Stop" 66 self.btnStop["command"] = self.button_Stop 67 self.btnStop["width"] = 20 68 self.btnStop.pack() 69 70 # Quit 71 self.quit = tkinter.Button(self) 72 self.quit["text"] = u"Quit" 73 self.quit["width"] = 20 74 self.quit["command"] = self.master.destroy 75 self.quit.pack() 76 77 # Callbacks 78 def button_func(self): 79 global stopflag 80 stopflag = 0 81 #t = ThreadJob() # スレッド確認OK 82 t = ThreadJob2() # capture 83 t.start() # スレッドを実行 84 85 def button_Stop(self): 86 global stopflag 87 print('stop') 88 stopflag = 1 89 90 91# メイン関数 92def main(): 93 # Windowの生成 94 root = tkinter.Tk() 95 root.geometry("200x100") 96 root.title(u"basic1") 97 app = Application(master=root) 98 app.mainloop() 99 100if __name__ == '__main__': 101 main() 102

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

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

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

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

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

guest

回答2

0

ベストアンサー

問題点: button_func -> Capture 内での無限ループが GUIのイベントループを阻害

GUIのプログラムは、イベントループ内(root.mainloop)で
描画更新・マウスキーボード等のイベントの処理が行われます。

イベントハンドラとして登録した関数は、このイベントループ内から呼び出される為、
関数の実行が終わらずに、イベントループに処理が戻らないと、GUIが応答なしになります。

このようなイベントループ上で実行される形式を、イベント駆動型プログラミングと言うのですが、
作法として、イベントハンドラとして呼び出す関数は、直ぐに終了しなくてはなりません。

時間が掛かる処理を行いたい場合は、タイマーで分割実行したり別スレッドで起動する等の対策を取ります。


解決策: root.mainloop()while True: は、同一スレッド上で同時に実行できないので、
別ウィンドウで表示したい場合はスレッドを使い、tkinter側から終了を通知出来るようにします。

  • Step1: Capture関数は別スレッドで実行する。
  • Step2: while True: -> while runnning: 中断可能なループにする。(要: global running 宣言)
  • Step3: tkinter 側から 変数 running の値を変更。(要: global running 宣言)

ボタンを複数回押した時の処理は、スレッドが複数起動しないように別途対応してください。
グローバル変数を使いたくない場合は、Capture側もクラスで実装し、フラグをインスタンス変数にする。

別の解決策としては、cv2.imshow は使わず tkinter 内に表示する方式もあります。
その場合、別スレッドも使わず、tkinter のタイマーでキャプチャした内容を画像として描画します。


問題とは関係ない部分ですが、末尾の return pass について、
Pythonの関数は、バイトコードにコンパイルされる時点で自動的に、末尾に return None 相当の処理が挿入されます。
戻り値が無い場合、関数末尾の空 return 等は不要です。

投稿2021/08/07 00:01

編集2021/08/07 00:05
teamikl

総合スコア8664

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

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

ysyk77

2021/08/09 01:27

ご回答、ありがとうございました。 ご指摘の通り、ボタンを押して、直ぐに戻らないから、tkinterのmainloopでstopボタンの検知が出来ないというのは、なんとなくわかります。 無理やりスレッド化したのが下のコードです。 実行すると、ビデオキャプチャーでハングアップしてしまいます。 ネットを探すとcanvasを使用したサンプルがありました。 ただ、今回の目的では、画像を単独で表示した状態で、メインから停止したいです。 ```python #!/usr/bin/python3 # -*- coding: utf8 -*- import tkinter import cv2 import threading import time stopflag = 0 # Thread Test → OK class ThreadJob(threading.Thread): def __init__(self, v=""): threading.Thread.__init__(self) def run(self): global stopflag # while True: while not(stopflag): print('hello,stop=',stopflag) time.sleep(1) if stopflag != 0: break # camera capture class ThreadJob2(threading.Thread): def __init__(self, v=""): threading.Thread.__init__(self) def run(self): global stopflag cap = cv2.VideoCapture(0, cv2.CAP_DSHOW) if cap.isOpened() == False: return while not(stopflag): ret, frame = cap.read() name = "video" cv2.namedWindow(name, cv2.WINDOW_NORMAL) cv2.imshow(name,frame) if stopflag != 0: break cap.release() cv2.destroyAllWindows() class Application(tkinter.Frame): def __init__(self, master=None): super().__init__(master) self.pack() self.create_widgets() def create_widgets(self): # capture self.button = tkinter.Button(self) self.button["text"] = u"Camera" self.button["command"] = self.button_func self.button["width"] = 20 self.button.pack() # stop self.btnStop = tkinter.Button(self) self.btnStop["text"] = u"Stop" self.btnStop["command"] = self.button_Stop self.btnStop["width"] = 20 self.btnStop.pack() # Quit self.quit = tkinter.Button(self) self.quit["text"] = u"Quit" self.quit["width"] = 20 self.quit["command"] = self.master.destroy self.quit.pack() # Callbacks def button_func(self): global stopflag stopflag = 0 #t = ThreadJob() # スレッド確認OK t = ThreadJob2() # capture t.start() # スレッドを実行 def button_Stop(self): global stopflag print('stop') stopflag = 1 return # メイン関数 def main(): # Windowの生成 root = tkinter.Tk() root.geometry("200x100") root.title(u"basic1") app = Application(master=root) app.mainloop() pass if __name__ == '__main__': main() ```
teamikl

2021/08/09 04:20 編集

コメントだとインデントの把握が困難なので、 質問文を編集で追記をお願いできますか。 補足で一点 クラスの利用は必須ではなく、フラグをインスタンス変数(self.stopflag)とする場合です。 上のコードでは、グローバル変数なので関数のままでも良く、クラス化部分が冗長(利点を活かせてない利用)になってます。 将来的に、グローバル変数をインスタンス変数に直すのであれば、 クラス利用自体は問題ありません。 ThreadJob2の実装は、多重起動防止が必要なものの、 スレッド利用と中断フラグ導入に関しては、(見た感じ)回答で想定した通りです。 実行は出来てないので、実際のコードの問題は、特定できません。 > ビデオキャプチャーでハングアップしてしまいます。 個別にデバッグしましょう。エラー等はでていませんか? 「tkinter を使わずに」ビデオキャプチャーを扱うコードを書いて、 単体で実行して問題ないかを試してみてください。 tkinter と cv2 両方を同時に使う場合の注意点もありますが、 手順としては、先に単体で動作確認した方が、問題を把握しやすいです。
ysyk77

2021/08/09 23:38

お世話になります。 質問を編集し、コードを追記させて頂きました。 ハングアップはimshow()の画面が表示されるのですが、画像が表示されません。 何か実行しようとしているようで、クルクル回るアイコンが表示されています。 ビデオキャプチャーのコードは別途、動作確認を取っています。 お忙しいところ、申し訳ありませんが、よろしくお願いします。
teamikl

2021/08/11 06:25 編集

> ビデオキャプチャーのコードは別途、動作確認を取っています。 (症状から判断すると) 動作確認を取ったコードと、質問に掲載のコードで異なる点があります。 確認方法: main() の替わりに、ThreadJob2().run() で、メインスレッド上で単体実行できます。 ※ 正しいスレッドの利用方法ではありませんが、動作確認用の用法。 Thread(target=capture) という利用法であれば、capture() で済みます。 問題点: cv2 側のイベントループに相当する処理が無い為に、ウィンドウの内容が更新されてません。 解決方法: while ループ内で cv2.waitKey(10) # 数値は FPS に応じて適切に設定します。(1000ms//100fps = 10 ms) 訂正: wait -> waitKey 注意点: キーボード入力を扱う為、tkinter のキー入力補足との重複が問題になる事があります。 tkinter を併用する場合は、キー入力はtkinter側に委ねた方が良いです。 追記、その他の点: namedWindow は while ループの外で一度のみでも問題ありません。
guest

0

opencvをスレッド上で動作させると、うまく行きませんでした。
色々試した結果、tkinterのafterを使用することで実現できました。

Python3

1#!/usr/bin/python3 2# -*- coding: utf8 -*- 3import tkinter 4import cv2 5 6stopflag = 0 7cap = cv2.VideoCapture 8 9class Application(tkinter.Frame): 10 def __init__(self, master=None): 11 super().__init__(master) 12 self.pack() 13 self.create_widgets() 14 return 15 16 def create_widgets(self): 17 # capture 18 self.button = tkinter.Button(self) 19 self.button["text"] = u"capture" 20 self.button["command"] = self.button_func 21 self.button["width"] = 20 22 self.button.pack() 23 24 # stop2 25 self.btnStop = tkinter.Button(self) 26 self.btnStop["text"] = u"Stop" 27 self.btnStop["command"] = self.button_Stop 28 self.btnStop["width"] = 20 29 self.btnStop.pack() 30 31 # Quit 32 self.quit = tkinter.Button(self) 33 self.quit["text"] = u"Quit" 34 self.quit["width"] = 20 35 self.quit["command"] = self.master.destroy 36 self.quit.pack() 37 return 38 39 40 # Callbacks 41 def button_func(self): 42 global stopflag 43 stopflag = 0 44 self.cap_start() 45 46 def button_Stop(self): 47 global stopflag 48 stopflag = 1 49 return 50 51 def cap_start(self): 52 global stopflag 53 global cap 54 55 cap = cv2.VideoCapture(0, cv2.CAP_DSHOW) 56 stopflag = 0 57 if cap.isOpened() == False: 58 print("camera fault") 59 return 60 print("camera open") 61 self.after(10, self.cap_repeat) 62 63 def cap_repeat(self): 64 global stopflag 65 global cap 66 67 ret, frame = cap.read() 68 name = "video" 69 cv2.namedWindow(name, cv2.WINDOW_NORMAL) 70 cv2.imshow(name,frame) 71 72 if stopflag != 0: 73 cap.release() 74 cv2.destroyAllWindows() 75 print("capture stop!") 76 else: 77 self.after(10, self.cap_repeat) 78 79# メイン関数 80def main(): 81 # Windowの生成 82 root = tkinter.Tk() 83 root.geometry("200x100") 84 root.title(u"base") 85 app = Application(master=root) 86 app.mainloop() 87 88if __name__ == '__main__': 89 main() 90

投稿2021/08/11 00:40

ysyk77

総合スコア4

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

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

teamikl

2021/08/11 06:22 編集

>opencvをスレッド上で動作させると、うまく行きませんでした。 > 色々試した結果、 wait や waitKey 呼び出しを試されましたか? ==== tkinter のタイマーを使う場合は、停止に別のアプローチが取れます。 after()の戻り値を控えておいて、after_cancel() 「tkinter のウィンドウ」にcv2でキャプチャした内容を載せる場合は、(cv2のGUIを使わない) tkinter のタイマーを使う方法が適切ですが、 「cv2のウィンドウ」を用いる場合は、tkinter の GUI との競合する部分がある為 tkinterのタイマーとの併用は、cv2の扱いに注意が必要になる場合があります。(例: cv2.waitKey 等)
ysyk77

2021/08/11 09:19

ご指摘、ありがとうございました。 ネットで情報を探して色々やっている内に、肝心のご指摘の内容の確認が漏れていました。 cv2.waitKey(10) でうまく行きました。 ありがとうございました。 ご回答の内容をベストアンサーにさせて頂きます。
teamikl

2021/08/11 10:23

コメントに誤りがあり、 cv2.wait(0) -> cv2.waitKey(10) と訂正しました。 スレッドについても少し補足があり、cv2のウィンドウのバックエンドがQt の場合、 別スレッドだと終了時に何か警告・エラーが出る事があるみたいです。 (cv2側で問題が報告されてます) なので、この問題を避ける為に別スレッドではなく メインスレッドで(tkinterのタイマーで)の解決策を取る 選択は有るかもしれません。 tkinter のタイマーを使う場合は、 別スレッドの場合と注意点が少し変わってきます。 参考までに、tkinter タイマーで実装する場合は、 グローバル変数stopflag の代わりに - timer_id = after(...) と、afterの戻り値を控えておく - after_cancel(timer_id) でタイマーの実行をキャンセル -※ 終了時に、キャプチャが稼働していれば  確実に release 等の後始末が呼ばれるようにする。
ysyk77

2021/08/12 04:12

お世話になります。 after_cancel()でafterをキャンセル出来ました。 ただ、単純にこれで止めてしまうとimshow()のwindowが残ってしまい、destroyする仕組みが必要でした。 グローバル変数stopflagで止めた方が楽そうでした。 afterのこの様な止め方があるのですね。 勉強になりました。
teamikl

2021/08/12 05:03 編集

問題点を説明し忘れました。 (1) タイマーで実行される関数内でのウィンドウの破棄は、 終了時にタイマーの関数が実行されない場合がある事が問題点です。 (タイミングや操作手順により挙動が変わります) ウィンドウを破棄する等の後始末コードは、 タイマーの外・別の場所にし、確実に実行されるようにした方が良いです。 def stop():   after_cancel(...)   ウィンドウ破棄、等 (2) また、実行環境次第ですが、タイマーで登録した関数が 実行されず残ったままだと、終了時にエラーが出る事があります。 対策として after_cancel が必要 → - after_cancel で止められるので、stopflag 変数の管理は不要になる。 - timer_id (afterの戻り値) でタイマーが稼働中かどうかも判別できます。
ysyk77

2021/08/12 06:30

アドバイスをありがとうございました。 色々試してみます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問