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

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

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

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

Python 3.x

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

Q&A

解決済

2回答

4293閲覧

OpenCV(cv2.imshow)を外部ボタンから停止制御の方法

shin0859

総合スコア15

OpenCV

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

Python 3.x

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

0グッド

0クリップ

投稿2021/06/23 05:41

編集2021/06/23 05:48

"Digital Photo Slide view"なる物を作っていますが、
スライドビューで下記のサイトで凄く気に入ったので
利用したいと思い実装を考えました。
それはcv2.imshowで表示するのですが、
制御する為のframe(コントロール)を利用したCanvasに乗せれないと
サイト調べで理解しました。
そこでframe(コントロール)とcv2.imshowを横並びで表示して、
コントロールで制御と考えますが、
この方法は妥当でしょうか?

妥当として上記を進める場合、exit_buttonで、動画を止めて、cv2.imshowを
cv2.destroyWindow("Slide")と思っていますが、
試行錯誤を試みましたが、正解を見出せないでいます。
どなたか、知恵を貸して頂ければ思います。宜しくお願い致します

自分初心者程度の知識しか有りませんので、
コードを提示して頂ければ助かります。
又利用コードが複雑なため、全コードを提示します。

スライドビューの元サイト Create a Moving Photo Slideshow with Weighted Transitions in OpenCV
https://www.learnpythonwithrune.org/create-a-moving-photo-slideshow-with-weighted-transitions-in-opencv/

関連する前回質疑:https://teratail.com/questions/342676

11.jpg
12.jpg
14.jpg
15.jpg
black.jpg

python

1import tkinter as tk 2from tkinter import ttk 3import tkinter 4import os 5from PIL import Image, ImageTk 6import cv2 7import glob 8import random 9import threading 10 11class Application(tk.Frame): 12 def __init__(self, master): 13 super().__init__(master) 14 master.title("Digital Photo Slide view") 15 master.geometry("125x480+70+70") 16 ttk.Style().configure("TP.TFrame", background="gainsboro") 17 self.main_frame = ttk.Frame(master=master, style="TP.TFrame", width=125, height=480) 18 self.main_frame.pack() 19 20 # load_button 21 self.load_btn = tk.Button(self.main_frame, text="load_button", font=("", 12), bg="green", fg="white", relief=tk.FLAT) 22 self.load_btn.place(x=10, y=150) 23 24 # exit_button 25 self.exit_btn = tk.Button(self.main_frame, text="exit_button", font=("", 12), bg="green", fg="white", relief=tk.FLAT) 26 self.exit_btn.place(x=10, y=250) 27 28 # top_button 29 self.top_btn = tk.Button(self.main_frame, text="top_button", font=("", 12), bg="green", fg="white", relief=tk.FLAT) 30 self.top_btn.place(x=10, y=350) 31 32 def PhotoView(self): 33 self.filelist=['./photo\11.jpg', './photo\12.jpg', './photo\14.JPG', './photo\15.JPG'] 34 # Cl:class 35 self.Cl = PhotoImageStack(self.filelist) 36 self.preImage = self.Cl.get_image() 37 self.Cl.add_image() 38 self.nowImage = self.Cl.get_image() 39 self.Cl.add_image() 40 41# ????????????????????????????????????????????????????????????????????????? 42 self.update_timer = self.preImage 43 self.roop_image() 44 45 46 def roop_image(self): 47 load_nextimage = threading.Thread(target=self.Cl.add_image) 48 # print(load_nextimage) 49 if (load_nextimage): 50 for i in range(100): #100回繰り返す 重なりタイム 51 alpha = i/100 52 beta = 1.0 - alpha 53 # 画像を重ね合わせる cv2.addWeighted(src1, alpha, src2, beta, gamma) 54 dst = cv2.addWeighted(self.nowImage.get_frame(), alpha, self.preImage.get_frame(), beta, 0.0) 55 self.update_timer =cv2.imshow("Slide", dst) 56 cv2.moveWindow("Slide", 195, 70) # Window表示位置指定 57 cv2.waitKey(10) 58 59 for _ in range(300): #300回繰り返す 表示タイム 60 self.update_timer =cv2.imshow("Slide", self.nowImage.get_frame()) 61 cv2.waitKey(10) 62 63 self.preImage = self.nowImage 64 self.nowImage = self.Cl.get_image() 65 load_nextimage = threading.Thread(target=self.Cl.add_image) 66 # load_nextimage.start() 67 self.update_timer = load_nextimage.start() 68 self.roop_image() 69 70 else: 71 print("else") 72 self.update_timer = None 73 def stop(self): 74 print("stop") 75 # if self.update_timer(): 76 # self.update_timer = None 77 # cv2.destroyWindow("Slide") 78 79# ///// 元ソース ///////////////////////////////////////////////////////// 80# 81# while True: 82# for i in range(100): #100回繰り返す 重なりタイム 83# alpha = i/100 84# beta = 1.0 - alpha 85# # 画像を重ね合わせる cv2.addWeighted(src1, alpha, src2, beta, gamma) 86# dst = cv2.addWeighted(self.nowImage.get_frame(), alpha, self.preImage.get_frame(), beta, 0.0) 87# cv2.imshow("Slide", dst) 88# cv2.moveWindow("Slide", 180, 70) # Window表示位置指定 89# if cv2.waitKey(10) == ord('q'): #Esc key 90# return 91# 92# for _ in range(300): #300回繰り返す 表示タイム 93# cv2.imshow("Slide", self.nowImage.get_frame()) 94# cv2.imshow("Slide", self.nowImage.get_frame()) 95# if cv2.waitKey(10) == ord('q'): #Esc key 96# return 97# 98# self.preImage = self.nowImage 99# self.nowImage = self.Cl.get_image() 100# load_nextimage = threading.Thread(target=self.Cl.add_image) 101# load_nextimage.start() 102# print(load_nextimage) 103# 104# ////////////////////////////////////////////////////////////////////////////// 105 106 107class PhotoImage(): 108 def __init__(self, filename): 109 size = 480 110 time = 500 111 self.filename = filename 112 self.shifted = 0.0 113 img = cv2.imread(filename) 114 # OpenCVで画像の縦と横のサイズを求める 115 height, width, _ = img.shape 116 if width < height: 117 n_height = int(height*size/width) 118 o_width = size 119 self.img = cv2.resize(img, (o_width, n_height)) 120 self.shift = n_height - size 121 self.shift_height = True 122 else: 123 n_width = int(width*size/height) 124 o_height = size 125 self.shift = n_width - size 126 self.img = cv2.resize(img, (n_width, o_height)) 127 self.shift_height = False 128 self.delta_shift = self.shift/time 129 130 131 def get_frame(self): 132 size = 480 133 if self.shift_height: 134 roi = self.img[int(self.shifted):int(self.shifted) + size, :, :] 135 else: 136 roi = self.img[:, int(self.shifted):int(self.shifted) + size, :] 137 self.shifted += self.delta_shift 138 if self.shifted > self.shift: 139 self.shifted = self.shift 140 if self.shifted < 0: 141 self.shifted = 0 142 return roi 143 144 145class PhotoImageStack: 146 def __init__(self, filelist, size=3): 147 self.stack = [] 148 self.cnt = (len(filelist)) 149 150 # 降順配列のまま 151 self.ar_file = filelist 152 153 # 配列先頭にblack.jpgを挿入する 154 self.ar_file.insert(0, './photo/black.jpg') 155 156 self.i = 0 157 filename = self.ar_file[self.i] 158 self.i += 1 159 if self.i == self.cnt +1 : self.i = 0 160 self.stack.append((filename, PhotoImage(filename))) 161 # Lock used for accessing the stack 162 self.stack_lock = threading.Lock() 163 self.add_image_lock = threading.Lock() 164 165 def get_image(self): 166 self.stack_lock.acquire() 167 filename, img = self.stack.pop() 168 print(f"Get image {filename} (stack size:{len(self.stack)})") 169 self.stack_lock.release() 170 return img 171 172 def add_image(self): 173 self.add_image_lock.acquire() 174 filename = self.ar_file[self.i] 175 self.i += 1 176 if self.i == self.cnt +1 : self.i = 0 177 178 self.stack_lock.acquire() 179 while any(item[0] == filename for item in self.stack): 180 filename = self.ar_file[self.i] 181 self.i += 1 182 if self.i == self.cnt +1 : self.i = 0 183 184 self.stack_lock.release() 185 img = PhotoImage(filename) 186 self.stack_lock.acquire() 187 self.stack.append((filename, img)) 188 print(f"Add image {filename} (stack size: {len(self.stack)})") 189 self.stack_lock.release() 190 self.add_image_lock.release() 191 192def main(): 193 root = tk.Tk() 194 app = Application(master=root) 195 196# ?????????????????????????????????????? 197 def exit_close(): # 全て閉じる 198 print("exit_close") 199 cv2.destroyWindow("Slide") 200 # app.stop() 201 202 def exit_top(): # Topへ 回答対象外 203 print("exit_top") 204 cv2.destroyWindow("Slide") 205 # app.stop() 206# ?????????????????????????????????????? 207 208 app.load_btn.config(command=app.PhotoView) 209 app.exit_btn.config(command=exit_close) 210 app.top_btn.config(command=exit_top) 211 212 root.mainloop() 213 214if __name__ == '__main__': 215 main()

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

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

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

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

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

guest

回答2

0

ベストアンサー

それはcv2.imshowで表示するのですが、
制御する為のframe(コントロール)を利用したCanvasに乗せれないと
サイト調べで理解しました。

前提について、
「imshowで表示されるウィンドウは」直接 Canvas に載せらせません。
(方法はあるが、プラットフォーム依存)ですが、

しかし、表示したいのが画像であれば、画像は Canvas に描画可能です。
データ形式の変換が必要ですが、PIL を使っているのでこの点は問題が無く、
tkinter に組み込むのであれば、スレッドを使わない方法もあります。
懸念は、cv2 の他の部分でしょうか。

そこでframe(コントロール)とcv2.imshowを横並びで表示して、
コントロールで制御と考えますが、 この方法は妥当でしょうか?

もし、現状の方針(スレッドを使う)で実装するならそうなります。

スレッド間の停止制御には threading.Event が使えますが、
まずは先にスレッドの使い方を見直した方が良いです。(大幅な変更が必用)

  • スレッドを使う場合は、tkitner のスレッド、cv2 のスレッドの2つに別けます。

 これは、双方のライブラリがイベントループを持つため、
イベントループが競合し、どちらか片方がフリーズするのを回避する為です。
一般的な構成では、2つのスレッドを使う場合、
片方では tkinter しか扱わず、もう片方で cv2 のみ扱うようにします。
※ cv2 のバックエンド次第では、メインスレッドで使わないといけないような制限がある事もあるかもしれません。
その場合は、tkinterをサブスレッドで使うか、マルチプロセスにする。

  • cv2.waitKey 等は入力所得が競合する為、何らかの対策が必要です。

 → tkinterでキー入力を拾い、別スレッドへ通知


解説: 2つのスレッドで実装する

問題点:
Lockで排他制御する場合、GUI等のイベント駆動プログラミングでは
ロック獲得によるブロックが発生すると、ウィンドウのフリーズに繋がる為、
避ける傾向があります。

解決策: 代案としては、プログラムの構造自体を
リソース毎に担当のスレッドを決めて、そのリソースは担当のスレッドからしか操作しないように、
もし外部から操作したい場合は、queue 等を用いてスレッド間の通信で行う、とします。
今回のケースであれば、対象のリソースは tkinter と cv2 の2つに別ける。

イメージ説明

起動時

  • Sub(cv2): 準備後に、tkinter起動を待機 tk_started.wait()
  • Main(tk): tkinter を起動
  • Main(tk): イベントループを開始。tk_started.set() で Subスレッドの待機を解除
  • Sub(cv2): イベントループ開始。スライドショー

停止処理

  • Main(tk): shared.pause = True
  • Sub(cv2): c2.waitKey(1) を呼び続けるループに入る(イベントループ継続の為)

終了処理

  • Main(tk): shared.running = sharead.pause = False (停止状態解除と稼働フラグをオフに)
  • Sub(cv2): フラグがオフになったことにより、イベントループを抜けて終了

訂正: ボタンによる別スレッドの停止のデモ (threading.Event の使い方サンプル)

投稿2021/06/24 10:32

編集2021/06/25 04:17
teamikl

総合スコア8664

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

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

shin0859

2021/06/24 13:21

teamikl様 いつもアドバイス有り難うございます 自分の知識では、理解出来ない回答を頂きましたので、サンプル動作等でしばらく考えたいとおもいます。 取り敢えずコメントお礼いたします
teamikl

2021/06/25 03:09 編集

多分、そのまま適応は出来ません。 現状のコードが、画像ひとつにつき2スレッド?みたいになってるので、 まずはスレッドは合計で2つ(メインでtkinter+サブでcv2)の形を目指してください。 質問のコードを軌道修正ではなく、参考元のコード (process) がベースなら 少ない変更で出来るかもしれません。 event = threading.Event() thread = threading.Thread(target=process, args=(event,), daemon=True) thread.start() この後にメインスレッドで tkinter のコード。 cv2 は process() 関数内からしか使わないようにします。
shin0859

2021/06/25 01:37 編集

teamikl様  サンプルコードを提示頂き大変有難うございます filenamesを設定しましたら、完璧な動作でした。 コードを見ると、またまたちんぷんかんぷんでした。 コード自体も短くなってる? 長く掛かりそうですが、いろいろ調べて少しでも理解したいと思います。 取り急ぎ、お礼のコメントととして、有難うございました。 しばらく 開けておきます。
teamikl

2021/06/25 04:06

画像の場所はスレッドへ引数で渡すようにしたり、 工夫できる余地はありますね。その辺は極力元コードを保ったままです。 一点訂正で、threading.Event での待機では、 cv2 のイベントループが止まってしまう為、 回答のサンプルコード(event.waitを使う)では、不都合がありそうでした。 誤誘導になりそうなので、この部分は取り下げます。 質問のコードのスレッドやLock使い方についても、いろいろあるのですが、 大まかな方針にだけ触れると、 Lock での排他制御 --> tkinter と cv2 の操作が混在している為。(※コードが複雑になりやすい) Thread を別けると不要。MainThread: tkinter, SubThread: cv2 という構造にします。 - process() 内では cv2 のコードのみ。tkinter は直接使わない。 - main() 内では、tkinter のコードのみ。cv2は直接使わない。 Lock を避けるのは、イベントループ内でブロックされると、 ウィンドウが応答なしになる為です。 ---- cv2側の処理は、スライドショーが停止中でも、 イベントループは稼働し続ける必要があります。 while pause: # スライドショー停止中、このループを繰り返す   cv2.waitKey(1) # イベントループを稼働させるために必要 イベントループが動いてないとウィンドウが応答なしになります。
guest

0

teamikl様
その後の体調不良のため、コメント返しが遅くなり申し訳有りませんでした。(画面凝視で、めまいが暫く続きまして...)
本題ですが、その後の作図を交えた解説等、流れは大体理解できましたが、その一つ一つは理解不能と言うか 自分には及ばない高い知識の回答だと理解しました。(Websaite等で発信した方が、貴重な知識が多くの方に広まりそうですが...)
自分に於いては2021/06/25付け提示サンプルが、思い通りの動作なのでそれをベースに次に進めたいと思います。いろいろ有難うございました。

投稿2021/07/04 06:52

編集2021/07/04 13:58
shin0859

総合スコア15

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

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

teamikl

2021/07/05 11:29

「全体像の把握」が大切なので、大まかな流れが解れば大丈夫。 個別の項目については、カテゴリで言うと - イベント駆動型プログラミング (GUI) - 並行プログラミング。マルチスレッド、同期・排他制御 辺りの内容です。 プログラミング・パラダイムと呼ばれる分類で、 それぞれに、プログラミングの際の作法や注意点等があります。 今回の例で言えば、全体の流れ(図で説明したかった点)は、 "tkinter" と "cv2" の2つの「イベント駆動」のプログラムを マルチスレッドで「平行に扱う」、といった組み合わせです。 書籍等でも、それぞれ個別に章割いて説明される位の分量のある内容なので 普通に学習しても、それなりに時間は掛かると思います。焦らず御自身のペースで進めて下さい。 オフトピですが、めまい対策には、目に優しいプログラミング環境作り コーディングでも改善できる部分があって、一例を紹介すると 私の場合は、Python では self が大量にあると、疲れやすい傾向があったので、 エディタでの表示環境では self を薄くして目立たないように工夫したり、 逆に self を目立つ色に設定してる場合は、self を省く方向にコードをリファクタリングする等 自分でコードを書くときは極力 self を参照する頻度を下げる方法をとってます。 綺麗なコードは脳への負担も少なく、理解もしやすくなるはず。 ---- cv2 は関係なくなりますが、 スライドショーのアニメーションをtkinterに移植しました。 https://replit.com/@MiKLTea/TkSlideShow#main.py
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問