Sunday, June 8, 2014

Face detection with OpenCV, Tkinter and multiprocessing: Performace

So it works: code for face detection using multiple processes in parallel with Tkinter; but what about performance?

Dramatis personæ:
- 21773 is the 'Master' process that creates the pipes and two child processes:
- 21774 plays as Tkinter.
- 21777 role is OpenCv, camera capture and face detection.

Activity with face detection and showing the capture in the gui:



CPU is 73% more or less, you can notice the system is working hard.


Activity without showing the frames in the gui:



Overall CPU reduces to aprox. 64%.
Master (21773 ) process spikes up to 60% (from 26%) --> NOK
Tkinter (21774) reduces usage from 22% to 6%. --> OK
OpenCv (21773) does not vary --> OK

Something is not right here, Master should not be working harder if there are no frames to put in the gui.
Most probably its loop runs more than it needs to.


Activity without the capture process, that is, only the gui running and the controller:



Minimal activity --> OK

Ideas to improve performance:
- Show webcam only in gray colours.
- Reduce resolution, these tests are capturing at 640x480
- Reduce the after() call frequency, so there are less updates requested to Tkinter.



Wednesday, June 4, 2014

Webcam capture with OpenCV and multiprocessing, gui crossroads.

So, right now I have a basic version of Behave with multiprocessing and I ask myself: is Tkinter the right choice?

Good comparative with different Python guis:
http://www.pythoncentral.io/introduction-python-gui-development/

Options
http://wxpython.org/what.php
http://zetcode.com/gui/pyqt4/
http://kivy.org/
http://www.cosc.canterbury.ac.nz/greg.ewing/python_gui/
Behave needs a lightweight gui, simple and flexible, since there is nothing out there that gives much more than what I have, I'll continue tinkering.

About the 90's look of Tkinter, it seems you can give Tkinter a more modern one:
https://docs.python.org/dev/library/tkinter.ttk.html#module-tkinter.ttk

But with Python 3.x, maybe this is a good moment for me to move away from my comfort zone, 2.6 and 2.7 ?


Sunday, June 1, 2014

Webcam capture with OpenCV, Tkinter and multiprocessing: parallel processes for webcam and gui.

This post shows how to use multiprocessing to create two processes working in parallel, one for OpenCV that does webcam capture, and the other with Tkinter to show the frames captured by the first.

This is the module called 'proc_capturer.py' with OpenCV code, capturing frames from the webcam and putting them in the queue.
def cam_loop(the_q, event):
    import cv2
    width, height = 800, 600
    cap = cv2.VideoCapture(0)
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)

    while True:
        _ , img = cap.read()
        if img is not None:
            img = cv2.flip(img, 1)
            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGBA)
            the_q.put(img)
            event.set()

And the Tkinter's module 'proc_tk_gui.py', starting the gui, receiving the frames from the queue and processing them in a hand-made 'mainloop':
def gui_mainloop(the_q, event):
    import Tkinter as tk
    from PIL import Image, ImageTk

    class TkGui(tk.Tk):
        def __init__(self):
            tk.Tk.__init__(self, None)

            self.parent = None

            self.lmain = tk.Label(self)

            self.lmain.pack()

        def update_frame(self, img):
            img = Image.fromarray(img)
            imgtk = ImageTk.PhotoImage(image=img)
            self.lmain.imgtk = imgtk
            self.lmain.configure(image=imgtk)
            self.update()

    gui = TkGui()

    while True:
        event.wait()
        img = the_q.get()
        gui.update_frame(img)


Finally, your 'multiprocess-orchestrator' module:
import multiprocessing
from proc_tk_gui import gui_mainloop
from proc_capturer import cam_loop

if __name__ == '__main__':
    logger = multiprocessing.log_to_stderr()
    logger.setLevel(multiprocessing.SUBDEBUG)

    q_frames_captured = multiprocessing.Queue(1)
    e_frame_captured = multiprocessing.Event()

    p_cap = multiprocessing.Process(target=cam_loop,args=(q_frames_captured, e_frame_captured))
    p_gui = multiprocessing.Process(target=gui_mainloop,args=(q_frames_captured, e_frame_captured))

    try:
        p_cap.start()
        p_gui.start()

        p_cap.join()
        p_gui.join()

    except KeyboardInterrupt:
        p_cap.terminate()
        p_gui.terminate()

I like the idea of having a master module with the multiprocessing logic, and two independent parallel processes only linked with queues and event handlers.
This could develop into a more finite-state machine approach for my project, behave.

Thanks for this good inspiration:
http://www.briansimulator.org/docs/examples-multiprocessing_multiple_runs_with_gui.html


Edit:
It seems from comments in forums that doing your own update loop for tkinter is not really a good thing. The problem is that even though you can force Tkinter to "refresh the window", when it comes to handle events like mouse or button clicking without the mainloop(): you-are-fried. So, mainloop version of 'proc_tk_gui.py' using my dreaded "tkinter after method":
def gui_mainloop(the_q, event):
    import Tkinter as tk
    from PIL import Image, ImageTk

    class TkGui(tk.Tk):
        def __init__(self):
            tk.Tk.__init__(self, None)

            self.parent = None

            self.lmain = tk.Label(self)

            self.lmain.pack()

        def update_frame(self, the_q, the_e):
            img = the_q.get()
            img = Image.fromarray(img)
            imgtk = ImageTk.PhotoImage(image=img)
            self.lmain.imgtk = imgtk        # remember you need to anchor the image!!!
            self.lmain.configure(image=imgtk)
            self.lmain.after(10, self.update_frame, the_q, the_e)

    gui = TkGui()
    gui.update_frame(img, event)
    gui.mainloop()