Alongside
RequestsUtils.py
and ImageSearchSession.py
, let's create another file called ResizeUtils.py
with the following import statements:
import numpy import cv2
For display in a GUI, images often must be resized. One popular mode of resizing is called aspect fill. This means that we want to preserve the image's aspect ratio while changing its larger dimension (width for a landscape image or height for a portrait image) to a certain value. OpenCV does not directly provide this resizing mode but it does provide a function, cv2.resize
, which accepts an image, target dimensions, and optional arguments including an interpolation method. We can write our own function, cvResizeAspectFill
, which accepts an image, maximum size, and preferred interpolation methods for upsizing and downsizing the images. It determines the appropriate arguments for cv2.resize
and passes them along. Here is the implementation:
def cvResizeAspectFill(src, maxSize, upInterpolation=cv2.INTER_LANCZOS4, downInterpolation=cv2.INTER_AREA): h, w = src.shape[:2] if w > h: if w > maxSize: interpolation=downInterpolation else: interpolation=upInterpolation h = int(maxSize * h / float(w)) w = maxSize else: if h > maxSize: interpolation=downInterpolation else: interpolation=upInterpolation w = int(maxSize * w / float(h)) h = maxSize dst = cv2.resize(src, (w, h), interpolation=interpolation) return dst
For a description of the interpolation methods that OpenCV supports, refer to the official documentation at http://docs.opencv.org/modules/imgproc/doc/geometric_transformations.html#resize. For upsizing, we default to cv2.INTER_LANCZOS4
, which produces sharp results. For downsizing, we default on cv2.INTER_AREA
, which produces moiré free results. (Moiré is an artifact that makes parallel lines or concentric curves look like crosshatching when they are sharpened at certain magnifications.)
Now, let's create another file called WxUtils.py
with the following import statements:
import numpy import cv2 import wx
OpenCV and wxPython use different image formats, so we will implement a conversion function, wxBitmapFromCvImage
. OpenCV stores color channels in a BGR order, whereas wxPython expects an RGB order. We can use an OpenCV function, cv2.cvtColor
, to reformat the image data accordingly. Then, we can use a wxPython function, wx.BitmapFromBuffer
, to read the reformatted data into a wxPython bitmap, which we return. Here is the implementation:
def wxBitmapFromCvImage(image): image = cv2.cvtColor(image, cv2.cv.CV_BGR2RGB) h, w = image.shape[:2] bitmap = wx.BitmapFromBuffer(w, h, image) return bitmap
At the time of writing, wx.BitmapFromBuffer
suffers from a platform-specific bug such that it fails on Raspberry Pi (specifically, Raspbian). As a workaround, we can do a less efficient, two-step conversion using the functions wx.ImageFromBuffer
and wx.BitmapFromImage
. Here is some code to check whether we are running on Raspbian Pi (based on the CPU model) and to implement our wxBitmapFromCVImage
function appropriately:
# Try to determine whether we are on Raspberry Pi. IS_RASPBERRY_PI = False try: with open('/proc/cpuinfo') as f: for line in f: line = line.strip() if line.startswith('Hardware') and \ line.endswith('BCM2708'): IS_RASPBERRY_PI = True break except: pass if IS_RASPBERRY_PI: def wxBitmapFromCvImage(image): image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) h, w = image.shape[:2] wxImage = wx.ImageFromBuffer(w, h, image) bitmap = wx.BitmapFromImage(wxImage) return bitmap else: def wxBitmapFromCvImage(image): image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) h, w = image.shape[:2] # The following conversion fails on Pi. bitmap = wx.BitmapFromBuffer(w, h, image) return bitmap
We have one more utility module to make. Let's create a file, PyInstallerUtils.py
, with import statements for the os
and sys
modules from Python's standard library:
import os import sys
When we bundle our application using PyInstaller, the paths to the resources will change. Thus, we need a function that correctly resolves paths regardless of whether our application has been bundled or not. Let's add a function, pyInstallerResourcePath
, which resolves a given path relative to the app directory (the '_MEIPASS'
attribute) or, failing that, the current working directory ('.'
). This is implemented as follows:
def pyInstallerResourcePath(relativePath): basePath = getattr(sys, '_MEIPASS', os.path.abspath('.')) return os.path.join(basePath, relativePath)
Our utilities modules are done now and we can move on to implement the frontend of the Luxocator.