Mengintegrasikan openCV 2.x dengan wxPython

sebetulnya topik ini sudah lama ada, namun sepertinya tulisan-tulisan lainnya masih menampilkan kode dengan interface python versi lama. wxPython merupakan pustaka antarmuka grafis (GUI) yang berbasis wxWidget yang sudah dibungkus untuk digunakan dalam kode python. Alasan utama menggunakan pustaka GUI selain dari bawaan openCV adalah keterbatasan pustaka GUI bawaan openCV (tidak ada koleksi widget dan dialog). Selain wxPython bisa juga menggunakan PyQt4 (Qt4 dibungkus untuk python). Namun, saya sedang tidak tertarik menggunakan PyQt karena hambatan lisensi. Oke, kembali ke topik utama mengintegrasikan bungkusan baru openCV. Bungkusan (wrapper) lama openCV sudah mulai tidak digunakan lagi dan tata-cara pemanggilan fungsi openCV di bungkusan yang baru lebih nyaman karena tidak banyak redundansi (misal opencv.cvQueryFrame() menjadi cv.QueryFrame() ).

Kode berikut merupakan contoh integrasi dengan wxPython yang ada di halaman wiki opencv

import wx
import opencv.cv as cv
import opencv.highgui as gui


class CvMovieFrame(wx.Frame):
    TIMER_PLAY_ID = 101
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, -1)

        self.capture = gui.cvCreateCameraCapture(-1)
        frame = gui.cvQueryFrame(self.capture)
        self.SetSize((frame.width, frame.height))
        self.displayPanel = wx.Panel(self, -1)
        cv.cvCvtColor(frame, frame, cv.CV_BGR2RGB)
        self.bmp = wx.BitmapFromBuffer(frame.width, frame.height, frame.imageData)
        self.Bind(wx.EVT_PAINT, self.onPaint)

        self.playTimer = wx.Timer(self, self.TIMER_PLAY_ID)
        wx.EVT_TIMER(self, self.TIMER_PLAY_ID, self.onNextFrame)
        fps = gui.cvGetCaptureProperty(self.capture, gui.CV_CAP_PROP_FPS)

        self.Show(True)
        if fps!=0: self.playTimer.Start(1000/fps)#every X ms
        else: self.playTimer.Start(1000/15)#assuming 15 fps

    def onPaint(self, evt):
        if self.bmp:
            dc=wx.BufferedPaintDC(self.displayPanel, self.bmp)
        evt.Skip()

    def onNextFrame(self, evt):
        frame = gui.cvQueryFrame(self.capture)
        if frame:
            cv.cvCvtColor(frame, frame, cv.CV_BGR2RGB)
            self.bmp.CopyFromBuffer(frame.imageData)
            self.Refresh()
        evt.Skip()

if __name__=="__main__":
    app = wx.App()
    app.RestoreStdio()
    CvMovieFrame(None)
    app.MainLoop()

kode ini kemudian disesuaikan sehingga menjadi

import wx
import cv #cukup 1 import


class CvMovieFrame(wx.Frame):
    TIMER_PLAY_ID = 101
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, -1)

        self.capture = cv.CreateCameraCapture(-1)
        frame = cv.QueryFrame(self.capture)
        self.SetSize((frame.width, frame.height))
        self.displayPanel = wx.Panel(self, -1)
        cv.CvtColor(frame, frame, cv.CV_BGR2RGB)
        self.bmp = wx.BitmapFromBuffer(frame.width, frame.height, frame.tostring()) #imageData sudah tidak ada
        self.Bind(wx.EVT_PAINT, self.onPaint)

        self.playTimer = wx.Timer(self, self.TIMER_PLAY_ID)
        wx.EVT_TIMER(self, self.TIMER_PLAY_ID, self.onNextFrame)
        fps = cv.GetCaptureProperty(self.capture, cv.CV_CAP_PROP_FPS)

        self.Show(True)
        if fps!=0: self.playTimer.Start(1000/fps)#every X ms
        else: self.playTimer.Start(1000/15)#assuming 15 fps

    def onPaint(self, evt):
        if self.bmp:
            dc=wx.BufferedPaintDC(self.displayPanel, self.bmp)
        evt.Skip()

    def onNextFrame(self, evt):
        frame = cv.QueryFrame(self.capture)
        if frame:
            cv.CvtColor(frame, frame, cv.CV_BGR2RGB)
            self.bmp.CopyFromBuffer(frame.tostring())
            self.Refresh()
        evt.Skip()

if __name__=="__main__":
    app = wx.App()
    app.RestoreStdio()
    CvMovieFrame(None)
    app.MainLoop()

setelah dicoba dan berhasil timbul masalah baru (sepertinya hanya di windows) yaitu gambar dari webcam terlihat berkedip (sesekali). walaupun kode di atas sudah menggunakan doublebuffering (wx.BufferedPaintDC) seperti yang dianjurkan oleh wxPython, namun sepertinya tidak mempan kalau dijalankan di Windows (saya coba di Win7). Cara lain untuk menghilangkan kedipan ini ditunjukkan di mailing-list wxPython-users dengan catatan perlu menggunakan pustaka khusus windows yaitu pythonwin32 seperti di kode berikut.

import wx 
import win32api 
import win32con 
import sys 
import cv 
sys.path.append('C:\OpenCV2.1\Python2.6\Lib\site-packages') 
class captureTest(wx.Frame): 
    TIMER_PLAY_ID = 101 
    def __init__(self, parent): 
        wx.Frame.__init__(self, parent, -1) 

        def SetCompositeMode(self, on=True): 
            exstyle = win32api.GetWindowLong(self.GetHandle(), win32con.GWL_EXSTYLE) 
            if on: 
                exstyle |= win32con.WS_EX_COMPOSITED 
            else: 
                exstyle &= ~win32con.WS_EX_COMPOSITED 
            win32api.SetWindowLong(self.GetHandle(), win32con.GWL_EXSTYLE, exstyle) 

        SetCompositeMode(self, True) 
        self.capture = cv.CaptureFromCAM(0) 
        capImg = cv.QueryFrame(self.capture) 
        self.SetSize((capImg.width, capImg.height)) 
        self.displayPanel = wx.Panel(self, -1) 
        cv.CvtColor(capImg, capImg, cv.CV_BGR2RGB) 
        self.buildBmp = wx.BitmapFromBuffer(capImg.width, capImg.height, capImg.tostring()) 
        self.Bind(wx.EVT_PAINT, self.onPaint) 
        self.playTimer = wx.Timer(self, self.TIMER_PLAY_ID) 
        wx.EVT_TIMER(self, self.TIMER_PLAY_ID, self.onNextFrame) 
        fps = cv.GetCaptureProperty(self.capture, cv.CV_CAP_PROP_FPS) 
        self.Show(True) 
        if fps!=0: self.playTimer.Start(1000/fps) #every X ms 
        else: self.playTimer.Start(1000/15) #assuming 15 fps 

    def onPaint(self, evt): 
        if self.buildBmp: 
            dc=wx.BufferedPaintDC(self.displayPanel, self.buildBmp) 
        evt.Skip() 

    def onNextFrame(self, evt): 
        capImg = cv.QueryFrame(self.capture) 
        if capImg: 
            cv.CvtColor(capImg, capImg, cv.CV_BGR2RGB) 
            self.buildBmp.CopyFromBuffer(capImg.tostring()) 
            self.Refresh() 
        evt.Skip() 

if __name__=="__main__": 
    app = wx.App() 
    app.RestoreStdio() 
    captureTest(None) 
    app.MainLoop()

Kode tersebut belum saya coba, karena di komputer yang saya gunakan belum terpasang pustaka pythonwin32. Setelah meneruskan membaca sambil mencoba ternyata di diskusi tersebut juga ada jawabannya yaitu tidak menggunakan Timer tetapi memanfaatkan event EVT_IDLE. kode akhirnya seperti ini (catatan tambahan, saya mengabaikan frame-per-second dari data video):

import wx
import cv


class CvMovieFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, -1)

        self.capture = cv.CreateCameraCapture(-1)
        
        frame = cv.QueryFrame(self.capture)
        self.SetSize((frame.width, frame.height))
        self.displayPanel = wx.Panel(self, -1)
        cv.CvtColor(frame, frame, cv.CV_BGR2RGB)
        self.bmp = wx.BitmapFromBuffer(frame.width, frame.height, frame.tostring())
        self.Show(True)
        
        self.Bind(wx.EVT_IDLE, self.onIdle)

    def onIdle(self, event):
        self.nextFrame()
        event.RequestMore()

    def nextFrame(self):
        img = cv.QueryFrame(self.capture)
        cv.CvtColor(img, img, cv.CV_BGR2RGB)
        self.bmp.CopyFromBuffer(img.tostring())
        dc = wx.ClientDC(self.displayPanel)
        dc.DrawBitmap(self.bmp, 0, 0, False)
        

if __name__=="__main__":
    app = wx.App()
    app.RestoreStdio()
    CvMovieFrame(None)
    app.MainLoop()

Kode di atas ternyata cukup memuaskan. tidak ada lagi kedipan ketika menggambar frame baru.

8 comments

  1. Mamik · Maret 23, 2012

    Thank’s Mas..
    btw, Python+OpenCv ini menurut saya kok agak lemot ya….
    terutama setelah penambahan button dan menu… jadi agak lama Prosesnya

    • pebbie · Maret 26, 2012

      ada contoh kodenya?

      • Mamik · Maret 26, 2012

        saya kombinasikan denga face detection yang sy dapat dari internet.
        kode nya tak taruh sini Mas https://gist.github.com/2203182
        Mohon Bantuannya🙂

  2. pebbie · Maret 26, 2012

    haarcascade memang lambat. apalagi kalau ditambah integrasi dengan wx diproses di tiap frame.
    kalau mau deteksi tiap frame agak susah kecuali klo multithread, tapi mengingat di python nggak ada true-multithread tampaknya sulit.

    paling alternatifnya deteksinya cukup sekali, selanjutnya di-track pakai template matching ke daerah sekitar frame sebelumnya.

  3. Ping-balik: Simple Face Tracking with OpenCV (Python) « GAIBlog
  4. Ilham Akbar (@ilhamije) · Maret 26, 2013

    wah .. thanks banget mas.
    Saya baru belajar dan bermain di OpenCV. Tutorial ini sangat membantu. Waktu saya jadi efisien🙂

Tinggalkan Balasan

Isikan data di bawah atau klik salah satu ikon untuk log in:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s