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

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

ただいまの
回答率

89.24%

wxPythonにおいて、USBカメラの画像表示をしたい

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 3,512
退会済みユーザー

退会済みユーザー

前提・実現したいこと

ここ一週間くらい、teratailの皆様の力を借りながら、python上で動く動画や画像を表示できるGUIを作っています。画像は何とか表示でき、画像とパネルとでアスペクト比が合っていないときには黒帯をつけてアスペクト比を合わせるところまで来ました。

発生している問題

- OpenCV上の画像をwxPythonに表示させる段階で画像がおかしくなる
次の段階としてWEBカメラの写真を表示できるようにしている段階です。メニューバーからカメラモードに切り替えその後USBカメラから画像を取り込み、OpenCV上の画像をパネル上に表示させるところでこけています。

パネル上に表示される写真
※上の画像には本当は壁が写っているはずなのですが、真っ黒の画像の上の部分にちょろちょろと白い粒が並んでいる状態になってしまっています。

- カメラ使用中にラグがひどくなる
カメラモードにすると、GUIの操作が滞るほどにラグ(0.2fps程度)が発生します。CUI+OpenCVのサンプル(while trueループ内でimshowを繰り返すもの)はほとんどラグがなく、同じくらいのレベル(3 fps以上)を目標にしています。


長いコードでごめんなさい

import wx
import os
import sys
import cv2
import numpy as np

GUI_IMAGE_X = 240
GUI_IMAGE_Y = 180
TIMER_MS = 1000

class MyFileDropTarget(wx.FileDropTarget):
    def __init__(self, panel):
        wx.FileDropTarget.__init__(self)
        self.panel = panel

    def OnDropFiles(self, x, y, filenames):
        self.panel.Release_camera()
        for filepath in filenames:
            extention =  "." + str(str(filenames).split(".")[-1:][-1])[:-2]
            wildcard = [".jpg",".jpeg",".png",".bmp",".mpg",".mpeg",".mp2",".mp4",".avi"]
            if extention in wildcard:
                self.panel.updateImage_from_file(filepath)
                self.panel.updateText(filepath)
self.panel.GetTopLevelParent().setstatusbarTXT(filepath)
            else:
                self.panel.updateImage_from_file("")
                self.panel.GetTopLevelParent().setstatusbarTXT("Not supported file.")
        return True

class DnDPanel(wx.Panel):

    def __init__(self, parent):
        wx.Panel.__init__(self, parent=parent)
        file_drop_target = MyFileDropTarget(self)
        img = wx.Image(GUI_IMAGE_X,GUI_IMAGE_Y)
        self.imageCtrl = wx.StaticBitmap(self, wx.ID_ANY,wx.Bitmap(img))
        self.fileTextCtrl = wx.TextCtrl(self,style=wx.TE_MULTILINE|wx.HSCROLL|wx.TE_READONLY)
        self.SetDropTarget(file_drop_target)
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(self.imageCtrl, 1, wx.ALL, 5)
        sizer.Add(self.fileTextCtrl, 1, wx.EXPAND|wx.ALL, 5)
        self.SetSizer(sizer)
        self.Fit()
        self.timer = wx.Timer(self)

    def cam_check(self):
        capture = cv2.VideoCapture(0)
        if capture.isOpened() is False:
            self.timer.Stop()
            capture.release()
            return False
        else:
            capture.release()
            return True

    def updateText(self, text):
        #Overwtite a text
        self.fileTextCtrl.SetValue(text)

    def Camera_mode(self):
        # Connection check
        if self.cam_check() == False:
            self.updateText("ERROR: Camera is not connected.")
            return False

        # Start timer event
        self.Bind(wx.EVT_TIMER,self.On_CamTimer)
        self.timer.Start(TIMER_MS)

    def On_CamTimer(self,event):
        cap = cv2.VideoCapture(0)

        #Auto toggle camera mode
        ret, frame = cap.read()
        if ret == False:
            self.timer.Stop()
            capture.release()
            return False
        self.updateImage_Entity(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
        cap.release()

    def updateImage_Entity(self, cv2image):
        # Calculate aspect rat
        mag_X = GUI_IMAGE_X / cv2image.shape[1]
        mag_Y = GUI_IMAGE_Y / cv2image.shape[0]

        if mag_X < mag_Y:
            img = cv2.resize(cv2image, None, fx = mag_X, fy = mag_X)
        else:
            img = cv2.resize(cv2image, None, fx = mag_Y, fy = mag_Y)

        # X-axis
        diff_X = GUI_IMAGE_X - img.shape[1]
        diff_X_start = round(diff_X/2)
        diff_X_end = diff_X_start + img.shape[1]

        # Y-axis
        diff_Y = GUI_IMAGE_Y - img.shape[0]
        diff_Y_start = round(diff_Y/2)
        diff_Y_end = diff_Y_start + img.shape[0]

        # combine
        bg_image = np.zeros((GUI_IMAGE_Y, GUI_IMAGE_X, 3), np.uint8)

bg_image[diff_Y_start:diff_Y_end,diff_X_start:diff_X_end] = img

        # BGR --> RGB
        img = cv2.cvtColor(bg_image, cv2.COLOR_BGR2RGB)

        #cv2.imshow("END",img )
        #cv2.waitKey(0)
        #cv2.destroyAllWindows()

        # Convert
        img = wx.Bitmap.FromBuffer(img.shape[1], img.shape[0], img)

        self.imageCtrl.SetBitmap(img)

    def updateImage_from_file(self, fileaddress):

        if not os.path.exists(fileaddress):
            self.updateText("File not found.")
            self.GetTopLevelParent().setstatusbarTXT("Please drop/load a image file.")
            return False
        self.updateImage_Entity(cv2.imread(fileaddress , cv2.IMREAD_COLOR))

    def Release_camera(self):
        self.timer.Stop()
        self.updateText("Camera is released.")
        self.cam_check()

class DnDFrame(wx.Frame):
    def __init__(self):
        self.window = wx.Frame.__init__(self, parent=None, title="GUI test", style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)

        self.InitMenu()
        self.panel = DnDPanel(self)
        self.Fit()
        self.Show()

    # Initialize
    def InitMenu(self):

        menubar = wx.MenuBar()
        fileMenu = wx.Menu()
        f_item1 = fileMenu.Append(wx.ID_ANY, '&Load file(L)', 'Load image file')
        f_item2 = fileMenu.Append(wx.ID_ANY, '&Camera(C)', 'Load image from USB camera')
        fileMenu.AppendSeparator()
        f_item3 = fileMenu.Append(wx.ID_ANY, '&Quit(Q)', 'Quit application')
        menubar.Append(fileMenu, '&File(F)')
        helpMenu = wx.Menu()
        h_item1 = helpMenu.Append(wx.ID_ANY, '&About(A)', 'About this program')
        menubar.Append(helpMenu, '&Help(H)')
        self.SetMenuBar(menubar)
        self.Bind(wx.EVT_MENU, self.OnLoad, f_item1)
        self.Bind(wx.EVT_MENU, self.OnCamera, f_item2)
        self.Bind(wx.EVT_MENU, self.OnQuit, f_item3)
        self.Bind(wx.EVT_MENU, self.OnAbout, h_item1)
        self.Centre()
        self.CreateStatusBar()
        self.setstatusbarTXT("Please drop/load a image file.")
        self.Show(True)

    def OnLoad(self, event):
        self.panel.Release_camera()
        self.dirname = ''
        wildcard = "Supported media files (*.jpg,*.jpeg,*.png,*.bmp))|*.jpg;*.jpeg;*.png;*.bmp"
        dlg = wx.FileDialog(self, "Please select a file.", self.dirname,defaultFile="", wildcard=wildcard,style= wx.FD_OPEN )
        if dlg.ShowModal() == wx.ID_OK:
            self.filename = dlg.GetFilename()
            self.dirname = dlg.GetDirectory()
            fileaddress = self.dirname + "\\" + self.filename
            if not os.path.exists(fileaddress):
                self.panel.updateText("File notot found.")
                self.setstatusbarTXT("Please drop/load a image file.")
                self.panel.updateImage_from_file("")
            else:
                with open(os.path.join(self.dirname, self.filename), 'r') as f:
                    self.panel.updateText(fileaddress)
                    self.panel.updateImage_from_file(fileaddress)
                    self.setstatusbarTXT(fileaddress)
        else:
            self.panel.updateText("")
            self.setstatusbarTXT("Please drop/load a image file.")
        dlg.Destroy()

    def setstatusbarTXT(self, msg):
        self.SetStatusText(msg)

    def OnQuit(self,event):
        self.panel.Release_camera()
        self.Destroy()

    def OnCamera(self, event):
        self.panel.updateText("Camera mode")
        self.setstatusbarTXT("Camera mode")

        self.panel.Camera_mode()

    def OnAbout(self, event):
        self.panel.Release_camera()
        about_window=About_window(self,"About this program")

        try:
            about_window.ShowModal()
        finally:
            about_window.Destroy()
        return True

class About_window(wx.Dialog):

    def __init__(self, parent,title):
        wx.Dialog.__init__(self, parent, title=title)
        self.InitUI()

    def InitUI(self):
        pnl = wx.Panel(self)
        vbox = wx.BoxSizer(wx.VERTICAL)
        self.text = wx.TextCtrl(pnl, size = (-1,100),style = wx.TE_MULTILINE|wx.TE_READONLY)
        self.btn1 = wx.Button(pnl, label = "OK")
        self.Bind(wx.EVT_BUTTON,self.OnClick, self.btn1)

        vbox.Add(self.text, proportion = 1, flag = wx.EXPAND|wx.ALIGN_CENTRE)
        vbox.Add(self.btn1, proportion = 0.1, flag = wx.ALIGN_RIGHT)
        pnl.SetSizer(vbox)
        self.Show(True)

        try:
            f = open(README_FILE , 'r')
            data = f.read()
            self.text.SetValue(data)

        # Except IOError as e:
        except:
            self.text.SetValue("\""  + README_FILE + "\" is not appropriate.")

    def OnClick(self, e):
        self.Destroy()

if __name__ == "__main__":
    app = wx.App(False)
    frame = DnDFrame()
    app.MainLoop()

試したこと

コメントアウトしてありますが、updateImage_Entityの部分で処理の段階ごとにimshowをして動作確認をしました。それでも、GUIに表示させるところでコケています。通常の画像は表示出来てカメラは黒塗り白ごま画像になってしまて行き詰っています。

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

Win10(64bit)
Python 3.5.3(64bit)    
wxPython 4.0.0a1 (Phoenix)

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

+1

一部のみカバー、かつ具体性に欠ける内容なんですが、ラグや低fpsに関してはGUIメインループのスレッド内でリアルタイム系の処理を入れてるからじゃないかなと思いました。

wxではありませんが私も過去にGUIアプリの音声再生でプツプツ切れるという問題に悩まされたことがあり、その時は音声再生のスレッドをGUIのメインループとは別に立てることで回避しました。

よってカメラ系の処理を別スレッドに外出しして、GUIのメインループとは非同期でやり取りするようにすれば少なくともGUIの操作が滞る事はなくなると思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/05/14 22:38

    Youhei様

    行き詰っている中、解決につながるアイデアを頂ありがとうございます。
    特に、以前の改善事例のおかげで問題の全貌が見えてきた感じがしてきました。

    私も、そういえば、Pythonとは関係のないところでシングルスレッドのスクリプトで高速化するために、一部の処理を別のスクリプトに丸投げして並行して走らせる、ということをしていました。
    頂いたヒントを元に修正したいと思います。

    キャンセル

check解決した方法

0

5/19朝追記
GUIを落としてからまだ黒窓が残ってしまっています

粗削りですが…

import wx
import os
import cv2
import numpy as np
import threading
import sys

GUI_IMAGE_X = 240

GUI_IMAGE_Y = 180
CAMMODE = False

########################################################################
class DnDPanel(wx.Panel):

    #----------------------------------------------------------------------
    def __init__(self, parent):
        wx.Panel.__init__(self, parent=parent)

        # Image box
        img = wx.Image(GUI_IMAGE_X,GUI_IMAGE_Y)

        # Statc bitmap
        self.imageCtrl = wx.StaticBitmap(self, wx.ID_ANY,wx.Bitmap(img))

        # Text box
        self.fileTextCtrl = wx.TextCtrl(self,style=wx.TE_MULTILINE|wx.HSCROLL|wx.TE_READONLY)

        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(self.imageCtrl, 1, wx.ALL, 5)
        sizer.Add(self.fileTextCtrl, 1, wx.EXPAND|wx.ALL, 5)
        self.SetSizer(sizer)
        self.Fit()

    #----------------------------------------------------------------------
    def updateText(self, text):
        #Overwtite a text
        self.fileTextCtrl.SetValue(text)

        #Add a text
        #self.fileTextCtrl.WriteText(text)

    #----------------------------------------------------------------------
    def updateImage_Entity(self, cv2image):

        # Calculate aspect rat
        mag_X = GUI_IMAGE_X / cv2image.shape[1]
        mag_Y = GUI_IMAGE_Y / cv2image.shape[0]

        if mag_X < mag_Y:
            img = cv2.resize(cv2image, None, fx = mag_X, fy = mag_X)
        else:
            img = cv2.resize(cv2image, None, fx = mag_Y, fy = mag_Y)

        # Calculate aspect ratio diff
        # X-axis
        diff_X = GUI_IMAGE_X - img.shape[1]
        diff_X_start = round(diff_X/2)
        diff_X_end = diff_X_start + img.shape[1]

        # Y-axis
        diff_Y = GUI_IMAGE_Y - img.shape[0]
        diff_Y_start = round(diff_Y/2)
        diff_Y_end = diff_Y_start + img.shape[0]
        #print("[(%s,%s) : (%s,%s)]"%(diff_X_start,diff_Y_start,diff_X_end,diff_Y_end))

        # Create modified picture
        bg_image = np.zeros((GUI_IMAGE_Y, GUI_IMAGE_X, 3), np.uint8)

        bg_image[diff_Y_start:diff_Y_end,diff_X_start:diff_X_end] = img
        # BGR --> RGB
        img = cv2.cvtColor(bg_image, cv2.COLOR_BGR2RGB)

        # Convert color array to bitmap image
        img = wx.Bitmap.FromBuffer(img.shape[1], img.shape[0], img)

        #Static bitmap
        self.imageCtrl.SetBitmap(img)


########################################################################
class CameraControl(threading.Thread):

    def __init__(self,parent):
        threading.Thread.__init__(self)

        # Prepare camera mode
        self.panelText = DnDPanel(parent).updateText
        self.panelImage =  DnDPanel(parent).updateImage_Entity

    #----------------------------------------------------------------------
    def run(self):
        print("RUN時のスレッド数 : " + str(threading.activeCount()))
        global CAMMODE

        while CAMMODE == True :
            # Get camera information : "0" is device numer
            cap = cv2.VideoCapture(0)
            ret, frame = cap.read()
            while ret == False:
                # Get camera information : "0" is device numer
                cap = cv2.VideoCapture(0)
                ret, frame = cap.read()
                self.panelText("ERROR: Camera is not working.")

            while ret == True:
                cv2.waitKey(1)
                ret, frame = cap.read()
                self.panelText("Camera mode")
                # Load an image to entity from file
                self.panelImage(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))

                # 複数スレッド化がうまくいていない
                if threading.activeCount() == 1:
                    sys.exit("うまくいっていない")

########################################################################
class DnDFrame(wx.Frame):
    #----------------------------------------------------------------------
    def __init__(self):
        self.window = wx.Frame.__init__(self, parent=None, title="GUI test", style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)

        self.InitMenu()
        self.panel = DnDPanel(self)
        self.Fit()

        self.cc = CameraControl(self)
        self.cc.setDaemon(True)
        self.cc.start()

        self.Show()

    #----------------------------------------------------------------------
    # Initialize
    def InitMenu(self):
        # Menubar
        menubar = wx.MenuBar()
        fileMenu = wx.Menu()
        f_item2 = fileMenu.Append(wx.ID_ANY, '&Camera(C)', 'Load image from USB camera')
        fileMenu.AppendSeparator()
        f_item3 = fileMenu.Append(wx.ID_ANY, '&Quit(Q)', 'Quit application')
        menubar.Append(fileMenu, '&File(F)')

        self.SetMenuBar(menubar)

        # Event call definition
        self.Bind(wx.EVT_MENU, self.OnCamera, f_item2)
        self.Bind(wx.EVT_MENU, self.OnQuit, f_item3)

        # Frame definition
        self.Centre()

        # Status bar
        self.CreateStatusBar()
        self.setstatusbarTXT("")

        self.Show(True)

    #----------------------------------------------------------------------
    # Event hander: set status text
    def setstatusbarTXT(self, msg):
        self.SetStatusText(msg)

    #----------------------------------------------------------------------
    # Event: Quit
    def OnQuit(self,event):
        #self.Destroy()
        sys.exit()

    #----------------------------------------------------------------------
    # Event: Camera
    def OnCamera(self, event):
        global CAMMODE
        CAMMODE = True
        self.panel.updateText("Camera mode")
        self.setstatusbarTXT("Camera mode")

        self.cc = CameraControl(self)
        self.cc.setDaemon(True)
        self.cc.start()

#----------------------------------------------------------------------
if __name__ == "__main__":
    app = wx.App(False)
    frame = DnDFrame()
    app.MainLoop()

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

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

  • ただいまの回答率 89.24%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる