Before we can make use of the designed image filter effects in an interactive way, we need to set up the main script and design a GUI application.
To run the application, we will turn to the chapter1.py.
script, which we will start by importing all the necessary modules:
We will also have to import a generic GUI layout (from gui
) and all the designed image effects (from filters
):
OpenCV provides a straightforward way to access a computer's webcam or camera device. The following code snippet opens the default camera ID (0
) of a computer using cv2.VideoCapture
:
On some platforms, the first call to cv2.VideoCapture
fails to open a channel. In that case, we provide a workaround by opening the channel ourselves:
In order to give our application a fair chance to run in real time, we will limit the size of the video stream to 640 x 480 pixels:
Note
If you are using OpenCV 3, the constants that you are looking for might be called cv3.CAP_PROP_FRAME_WIDTH
and cv3.CAP_PROP_FRAME_HEIGHT
.
Then the capture
stream can be passed to our GUI application, which is an instance of the FilterLayout
class:
The only thing left to do now is design the said GUI.
The FilterLayout
GUI will be based on a generic, plain layout class called BaseLayout
, which we will be able to use in subsequent chapters as well.
The BaseLayout
class is designed as an
abstract base class. You can think of this class as a blueprint or recipe that will apply to all the layouts that we are yet to design—a skeleton class, if you will, that will serve as the backbone for all of our future GUI code. In order to use abstract classes, we need the following import
statement:
We also include some other modules that will be helpful, especially the wx
Python module and OpenCV (of course):
The class is designed to be derived from the blueprint or skeleton, that is, the wx.Frame
class. We also mark the class as abstract by adding the __metaclass__
attribute:
Later on, when we write our own custom layout (FilterLayout
), we will use the same notation to specify that the class is based on the BaseLayout
blueprint (or skeleton) class, for example, in class FilterLayout(BaseLayout):
. But for now, let's focus on the BaseLayout
class.
An abstract class has at least one abstract method. An abstract method is akin to specifying that a certain method must exist, but we are not sure at that time what it should look like. For example, suppose BaseLayout
contains a method specified as follows:
Then any class deriving from it, such as FilterLayout
, must specify a fully fleshed-out implementation of a method with that exact signature. This will allow us to create custom layouts, as you will see in a moment.
But first, let's proceed to the GUI constructor.
The BaseLayout
constructor accepts an ID (-1
), a title string ('Fun with Filters
'), a video capture object, and an optional argument that specifies the number of frames per second. Then, the first thing to do in the constructor is try and read a frame from the captured object in order to determine the image size:
We will use the image size to prepare a buffer that will store each video frame as a bitmap, and to set the size of the GUI. Because we want to display a bunch of control buttons below the current video frame, we set the height of the GUI to self.imgHeight+20
:
We then provide two methods to initialize some more parameters and create the actual layout of the GUI:
The video stream of the webcam is handled by a series of steps that begin with the _init_base_layout
method. These steps might appear overly complicated at first, but they are necessary in order to allow the video to run smoothly, even at higher frame rates (that is, to counteract flicker).
The wxPython
module works with events and callback methods. When a certain event is triggered, it can cause a certain class method to be executed (in other words, a method can bind to an event). We will use this mechanism to our advantage and display a new frame every so often using the following steps:
- We create a timer that will generate a
wx.EVT_TIMER
event whenever 1000./fps milliseconds have passed: - Whenever the timer is up, we want the
_on_next_frame
method to be called. It will try to acquire a new video frame: - The
_on_next_frame
method will process the new video frame and store the processed frame in a bitmap. This will trigger another event, wx.EVT_PAINT
. We want to bind this event to the _on_paint
method, which will paint the display the new frame:
The _on_next_frame
method grabs a new frame and, once done, sends the frame to another method, __process_frame
, for further processing:
The processed frame (frame
) is then stored in a bitmap buffer (self.bmp
):
Calling Refresh
triggers the aforementioned wx.EVT_PAINT
event, which binds to _on_paint
:
The paint method then grabs the frame from the buffer and displays it:
The creation of the generic layout is done by a method called _create_base_layout
. The most basic layout consists of only a large black panel that provides enough room to display the video feed:
In order for the layout to be extendable, we add it to a vertically arranged wx.BoxSizer
object:
Next, we specify an abstract method, _create_custom_layout
, for which we will not fill in any code. Instead, any user of our base class can make their own custom modifications to the basic layout:
Then, we just need to set the minimum size of the resulting layout and center it:
Now we are almost done! If we want to use the BaseLayout
class, we need to provide code for the three methods that were left blank previously:
_init_custom_layout
: This is where we can initialize task-specific parameters_create_custom_layout
: This is where we can make task-specific modifications to the GUI layout_process_frame
: This is where we perform task-specific processing on each captured frame of the camera feed
At this point, initializing the image filters is self-explanatory, as it only requires us to instantiate the corresponding classes:
To customize the layout, we arrange a number of radio buttons horizontally, one button per image effect mode:
Here, the style=wx.RB_GROUP
option makes sure that only one of these radio buttons can be selected at a time.
To make these changes take effect, pnl
needs to be added to list of existing panels:
The last method to be specified is _process_frame
. Recall that this method is triggered whenever a new camera frame is received. All that we need to do is pick the right image effect to be applied, which depends on the radio button configuration. We simply check which of the buttons is currently selected and call the corresponding render method:
Don't forget to return the processed frame:
And we're done!
Here is the result: