As discussed in Chapter 2, Handling Cameras, Files and GUIs, our object-oriented design allows us to easily swap OpenCV's HighGUI window manager for another window manager, such as Pygame. To do so, we just need to subclass our managers.WindowManager
class
and override four methods: createWindow()
, show()
, destroyWindow()
, and processEvents()
. Also, we need to import some new dependencies.
To proceed, we need the managers.py
file from Chapter 2, Handling Cameras, Files, and GUIs and the utils.py
file from Chapter 4, Tracking Faces with Haar Cascades. From utils.py
, we only need one function,
isGray()
, which we implemented in Chapter 4, Tracking Faces with Haar Cascades. Let's edit managers.py
to add the following imports:
import pygame import utils
Also in managers.py
, somewhere after our WindowManager
implementation, we want to add our new subclass called PygameWindowManager
:
class PygameWindowManager(WindowManager): def createWindow(self): pygame.display.init() pygame.display.set_caption(self._windowName) self._isWindowCreated = True def show(self, frame): # Find the frame's dimensions in (w, h) format. frameSize = frame.shape[1::-1] # Convert the frame to RGB, which Pygame requires. if utils.isGray(frame): conversionType = cv2.COLOR_GRAY2RGB else: conversionType = cv2.COLOR_BGR2RGB rgbFrame = cv2.cvtColor(frame, conversionType) # Convert the frame to Pygame's Surface type. pygameFrame = pygame.image.frombuffer( rgbFrame.tostring(), frameSize, 'RGB') # Resize the window to match the frame. displaySurface = pygame.display.set_mode(frameSize) # Blit and display the frame. displaySurface.blit(pygameFrame, (0, 0)) pygame.display.flip() def destroyWindow(self): pygame.display.quit() self._isWindowCreated = False def processEvents(self): for event in pygame.event.get(): if event.type == pygame.KEYDOWN and \ self.keypressCallback is not None: self.keypressCallback(event.key) elif event.type == pygame.QUIT: self.destroyWindow() return
Note that we are using two Pygame modules: pygame.display
and pygame.event
.
A window is created by calling pygame.display.init()
and destroyed by calling pygame.display.quit()
.
Repeated calls to display.init()
have no effect, as Pygame is intended for single-window applications only. The Pygame window has a drawing surface of type pygame.Surface
. To get a reference to this Surface
, we can call pygame.display.get_surface()
or
pygame.display.set_mode()
. The latter function modifies the Surface
entity's properties before returning it. A Surface
entity has a blit()
method, which takes, as arguments, another Surface
and a coordinate pair where the latter Surface
should be "blitted" (drawn) onto the first. When we are done updating the window's Surface
for the current frame, we should display it by calling pygame.display.flip()
.
Events, such as keypresses
, are polled by calling
pygame.event.get()
, which returns the list of all events that have occurred since the last call. Each event is of type pygame.event.Event
and has the property type
, which indicates the category of an event such as pygame.KEYDOWN
for keypresses and pygame.QUIT
for the window's Close button being clicked. Depending on the value of type
, an Event
entity may have other properties, such as key
(an ASCII key code) for the KEYDOWN
events.
Relative to the base WindowManager
that
uses HighGUI, PygameWindowManager
incurs some overhead cost by converting between OpenCV's image format and Pygame's Surface
format of each frame. However, PygameWindowManager
offers normal window closing behavior, whereas the base WindowManager
does not.