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

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

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

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

Python 3.x

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

Linux

Linuxは、Unixをベースにして開発されたオペレーティングシステムです。日本では「リナックス」と呼ばれています。 主にWebサーバやDNSサーバ、イントラネットなどのサーバ用OSとして利用されています。 上位500のスーパーコンピュータの90%以上はLinuxを使用しています。 携帯端末用のプラットフォームAndroidは、Linuxカーネル上に構築されています。

Q&A

解決済

1回答

4486閲覧

wx.StaticBitmap.SetBitmap()でbitmapをセットすると空白画像が表示される

amikappa

総合スコア6

OpenCV

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

Python 3.x

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

Linux

Linuxは、Unixをベースにして開発されたオペレーティングシステムです。日本では「リナックス」と呼ばれています。 主にWebサーバやDNSサーバ、イントラネットなどのサーバ用OSとして利用されています。 上位500のスーパーコンピュータの90%以上はLinuxを使用しています。 携帯端末用のプラットフォームAndroidは、Linuxカーネル上に構築されています。

0グッド

0クリップ

投稿2019/09/23 08:05

StaticBitmap.SetBitmap()でbitmapを表示したい

以下の環境にて、wxPythonで動画を表示させるStaticBitmapの拡張クラスを作ろうとしています。
・Python 3.6.8
・wxPython 4.0.6
・OpenCV 4.1.1
・ubuntu 18.06

OpenCVはapp.MainLoop()でループさせる必要があるため、StaticBitmapを継承したクラスを作って、その中で別スレッドを作り、その中でループさせてフレームを読み込み、bitmapに変換して自身に再セットする、といった処理を考えています。

おおむね上手くいったのですが、なぜかbitmapの再セットをSetBitmap()で行うと、画像が表示されず、真っ白になります。

スレッド操作が悪いのか、StaticBitmapの使い方が悪いのか、原因の切り分けができずに困っています。

どなたか、wxPython+OpenCVでStaticBitmapを使って動画再生をしたことがあるかたがいらっしゃったら、アドバイスを頂けると助かります。

該当のソースコード

import wx import cv2 import numpy as np import threading class VideoBitmap(wx.StaticBitmap): def __init__(self, parent, file_name, pos=(0, 0), size=(100, 100)): self.video = cv2.VideoCapture(file_name) self.width = size[0] ret, frame = self.video.read() bmp = self.create_wx_bitmap_from_cv2_image(frame) bmp = self.scale_bitmap_keep_ratio(bmp, 300) super().__init__(parent, wx.ID_ANY, bmp, pos, size, style=0, name='') def create_wx_bitmap_from_cv2_image(self, cv2_image): height, width = cv2_image.shape[:2] cv2_image_rgb = cv2.cvtColor(cv2_image, cv2.COLOR_BGR2RGB) return wx.BitmapFromBuffer(width, height, cv2_image_rgb) def scale_bitmap_keep_ratio(self, bitmap, width): image = wx.ImageFromBitmap(bitmap) rate = bitmap.Height / bitmap.Width height = width * rate image = image.Scale(width, height, wx.IMAGE_QUALITY_HIGH) result = wx.BitmapFromImage(image) return result def video_run(self): frame_count = int(self.video.get(cv2.CAP_PROP_FRAME_COUNT)) frame_rate = int(self.video.get(cv2.CAP_PROP_FPS)) test_bmp = wx.Bitmap('icons/save.png') for i in range(frame_count): is_read, frame = self.video.read() if not is_read: break bmp = self.create_wx_bitmap_from_cv2_image(frame) bmp = self.scale_bitmap_keep_ratio(bmp, 300) self.SetBitmap(bmp) print('%d' % i) if __name__ == "__main__": app = wx.App(False) file_name = "video/video.mp4" frame = wx.Frame(None, wx.ID_ANY, "テスト", size=(800,625)) panel = wx.Panel(frame, size=(800,625)) layout = wx.FlexGridSizer(rows=2, cols=2, gap=(0,0)) panel.SetSizer(layout) video = VideoBitmap(panel, file_name, pos=(10, 10), size=(300, 300)) layout.Add(video) frame.Show(True) thread = threading.Thread(target=video.video_run) thread.start() app.MainLoop()

試したこと

上記コードのメソッド「video_run」内にある「self.SetBitmap(bmp)」がうまく機能していないと思われるため、以下を試しました。
・初期表示の画像(コンストラクタで作成したbmpをself.init_bmpとして保持)を、bmpの代わりにself.SetBitmap(self.init_bmp)として表示してみたが、やはり真っ白になった。
・適当なアイコン画像をwx.Bitmap()で生成してSetBitmapに入れてみたところ、最初の1フレームループの時だけ表示され、2巡目からはやはり真っ白になる。
・self.Update()で即時描画更新を試したが、コアダンプで落ちる。

コンストラクタでセットした画像だけ(つまり初手)は表示されます。

アドバイスを頂けると幸いです。

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

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

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

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

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

guest

回答1

0

ベストアンサー

wxのGUI制御がどこまでマルチスレッドに対応しているか不明なので画面更新をスレッドで行うのはやめたほうがいいのではと思います。

タイマー処理を使うのが現実的かもしれません。

Python

1import wx 2import cv2 3import numpy as np 4import threading 5 6class VideoBitmap(wx.StaticBitmap): 7 def __init__(self, parent, file_name, pos=(0, 0), size=(100, 100)): 8 self.video = cv2.VideoCapture(file_name) 9 self.width = size[0] 10 ret, frame = self.video.read() 11 12 self.frame_bmps = [] 13 self.next_frame_bmp_no = 0; 14 while True: 15 is_read, frame = self.video.read() 16 if not is_read: 17 break 18 bmp = self.create_wx_bitmap_from_cv2_image(frame) 19 bmp = self.scale_bitmap_keep_ratio(bmp, 300) 20 self.frame_bmps.append(bmp) 21 22 super().__init__(parent, wx.ID_ANY, self.frame_bmps[self.next_frame_bmp_no], pos, size, style=0, name='') 23 self.next_frame_bmp_no += 1 24 25 self.timer = wx.Timer(self) 26 self.Bind(wx.EVT_TIMER, self.OnTimer) 27 self.timer.Start(1000/int(self.video.get(cv2.CAP_PROP_FPS))) 28 29 def create_wx_bitmap_from_cv2_image(self, cv2_image): 30 height, width = cv2_image.shape[:2] 31 cv2_image_rgb = cv2.cvtColor(cv2_image, cv2.COLOR_BGR2RGB) 32 return wx.BitmapFromBuffer(width, height, cv2_image_rgb) 33 34 def scale_bitmap_keep_ratio(self, bitmap, width): 35 image = wx.ImageFromBitmap(bitmap) 36 rate = bitmap.Height / bitmap.Width 37 height = width * rate 38 image = image.Scale(width, height, wx.IMAGE_QUALITY_HIGH) 39 result = wx.BitmapFromImage(image) 40 return result 41 42 def OnTimer(self, event): 43 self.SetBitmap(self.frame_bmps[self.next_frame_bmp_no]) 44 self.next_frame_bmp_no += 1 45 self.next_frame_bmp_no %= len(self.frame_bmps) 46 47if __name__ == "__main__": 48 49 app = wx.App(False) 50 51 file_name = "video/video.mp4" 52 53 frame = wx.Frame(None, wx.ID_ANY, "テスト", size=(800,625)) 54 panel = wx.Panel(frame, size=(800,625)) 55 layout = wx.FlexGridSizer(rows=2, cols=2, gap=(0,0)) 56 panel.SetSizer(layout) 57 58 video = VideoBitmap(panel, file_name, pos=(10, 10), size=(300, 300)) 59 layout.Add(video) 60 frame.Show(True) 61 62 #thread = threading.Thread(target=video.video_run) 63 #thread.start() 64 65 app.MainLoop()

スレッドを使わなければならない場合(メモリ節約やMainスレッドをできるだけMainLoopにいさせたい場合)はwx.PostEventを使用する方法もあると思います。
スレッド側でbmp生成まで行った後、そのデータをイベントの付加データとしてメインスレッドに投げて、メインスレッドで描画更新をするという流れになります。(その場合、フレーム間隔をどうするかは別途考える必要あり)

参考記事:マルチスレッドでwxPython

投稿2019/09/23 16:46

編集2019/09/24 13:13
nomuken

総合スコア1627

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

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

amikappa

2019/09/24 03:29

スレ主です。さっそく丁寧なレスを頂き、ありがとうございました。 今、会社にいるので試せませんが、帰宅したらさっそく試してみようと思います。 wxPythonがマルチスレッドに対応していないかもしれないとは思わなかったので、目からウロコでした。ありがとうございます。 試したら、また返信しますね。
amikappa

2019/09/24 12:57

スレ主です。試したところ、ご指南いただいたコードでうまくいきました。 やはり、wxPythonはマルチスレッドだと正常に動作しない(画像表示の更新ができない?)ようです。 しかし、コンストラクタで全フレームをBMPにすると、メモリの消費量がすさまじいことになったので、PostEventでの実装も検討してみようと思います。 非常に貴重なご意見を頂き、ありがとうございました。 突破口が見えたので、ひとまず安心できました。
amikappa

2019/09/25 11:25

スレ主です。 ご指南いただいたコードを少々改造しました。 ・コンストラクタで動画全部をBMP変換してバッファリングするのでなく、Timerイベント内で1枚づつ読み込んでBMP変換する この方法だと、私のオンボロパソコン(Atomノートにlubuntuを入れて無理やり使っている)だと変換処理が間に合わず、絵が出ないことがありましたので、Timerイベントの最初でTimerをOFFにし、変換が修理したらまたONにするよう工夫しています。 これでフレームレートが落ちますが、ちゃんと再生できました。パソコンが普通に速ければフレームレート落ちもしないと思います。 ソースを貼ります。 どなたか、同様のお悩みの方の参考になれば幸いです。 *** import wx import cv2 import numpy as np class VideoBitmap(wx.StaticBitmap): def __init__(self, parent, file_name, pos=(0, 0), size=(100, 100)): self.video = cv2.VideoCapture(file_name) self.width = size[0] ret, frame = self.video.read() is_read, frame = self.video.read() bmp = self.create_wx_bitmap_from_cv2_image(frame) bmp = self.scale_bitmap_keep_ratio(bmp, 300) super().__init__(parent, wx.ID_ANY, bmp, pos, size, style=0, name='') self.count = 0 self.timer = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.OnTimer) self.interval = 1000/int(self.video.get(cv2.CAP_PROP_FPS)) self.timer.Start(self.interval) def create_wx_bitmap_from_cv2_image(self, cv2_image): height, width = cv2_image.shape[:2] cv2_image_rgb = cv2.cvtColor(cv2_image, cv2.COLOR_BGR2RGB) return wx.Bitmap.FromBuffer(width, height, cv2_image_rgb) def scale_bitmap_keep_ratio(self, bitmap, width): image = bitmap.ConvertToImage() rate = bitmap.Height / bitmap.Width height = width * rate image = image.Scale(width, height, wx.IMAGE_QUALITY_HIGH) result = wx.Bitmap(image) return result def OnTimer(self, event): self.timer.Stop() is_read, frame = self.video.read() if not is_read: return bmp = self.create_wx_bitmap_from_cv2_image(frame) bmp = self.scale_bitmap_keep_ratio(bmp, 300) self.SetBitmap(bmp) self.count += 1 print('%d' % self.count) self.timer.Start(self.interval) if __name__ == "__main__": app = wx.App(False) file_name = "video/video.mp4" frame = wx.Frame(None, wx.ID_ANY, "テスト", size=(800,625)) panel = wx.Panel(frame, size=(800,625)) layout = wx.FlexGridSizer(rows=2, cols=2, gap=(0,0)) panel.SetSizer(layout) video = VideoBitmap(panel, file_name, pos=(10, 10), size=(300, 300)) layout.Add(video) frame.Show(True) app.MainLoop() ***
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問