Thursday, May 8, 2014

Webcam capture with OpenCV and Tkinter: widget.after(...)

So, how to show the webcam using OpenCV and Tkinter?

There seem to be two alternatives, one is using the widget.after(delay, function) method, the other one is multithreading/multiprocessing.

For widget.after option, this is the basic code:
import Tkinter as tk
import cv2
from PIL import Image, ImageTk

width, height = 800, 600
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)

root = tk.Tk()
root.bind('', lambda e: root.quit())
lmain = tk.Label(root)
lmain.pack()

def show_frame():
    _, frame = cap.read()
    frame = cv2.flip(frame, 1)
    cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
    img = Image.fromarray(cv2image)
    imgtk = ImageTk.PhotoImage(image=img)
    lmain.imgtk = imgtk
    lmain.configure(image=imgtk)
    lmain.after(10, show_frame)

show_frame()
root.mainloop()
The after methods are ways of telling Tkinter to call functions after it has processed the events in the 'mainloop'.
When show_frame (Line 25) is called, a new frame is processed and the final call lmain.after(10, show_frame) outside the mainloop, will register 'show_frame' as after method to be called in the loop with the delay in milliseconds passed (10).

Later on, when inside the mainloop (Line 26), there is a check for if there are any 'after' methods at the end of the event processing, in this case it will find 'show_frame' and call it, the method will 'hook' itself to the mainloop, so it will be called semi-recursively.

So, think like this: the 'show_frame' will tell Tkinter mainloop: 'hey! I'm here, call me back in 10 milliseconds', tkinter will do its normal life, processing events, etc, and then after 10 milliseconds will be obedient and will call 'show_frame'. Method runs its code and at the end does the sneaky thing of saying again 'hey!, I'm here, call me...'

So, in each of those calls to the method, we take a frame captured by OpenCV's camera capturer. And that is the way you are able to capture frames and display them in Tkinker.

There is a bonus track also in all this:
   lmain.imgtk = imgtk
In spite of appearances it is not a superfluous piece of code, it is much needed, since the pseudo-recursive nature of the after call, it would garbage-collect the variable imgtk, we have to anchor it somewhere so it does not get deleted. We could also set it as "global" :|

Last word on widget.after: watch out for performance, the trick is on the delays set to the 'after' method. In this simple code if the delay of 10 is changed to 1, you can see the video capture with constant jumps in frames. With the value of 10 or even 100, it works smoothly.

I've got best results with the delay parameter when is set to more or less the time that takes the VideoCapture to take a frame. That is, we are talking about 'FPS' :) .

Refs to understand the after method:
http://effbot.org/tkinterbook/widget.htm#Tkinter.Widget.after-method

A bit more advanced, good read:

No comments:

Post a Comment