Serve images on the fly in a binary form with Flask
In this post I am going to demonstrate how to serve images from a REST endpoint for a situation when the images are not stored as static files, but generated on the fly given some original form. This use case will be described with application of Flask, OpenCV and NumPy.
A situation when this functionality is needed is when the original images are too large, and their downscaled representations are sometimes required, or when the original format of the images is too heavy to be served via a web service (like BMP). Of course, one solution may be to pre-process the images and serve the target files statically. However, in situations when it is expected to have just occasional requests for the downscaled/converted images, it can be a good idea to implement an on-the-fly handler.
Let’s imagine that we can identify a concrete image using a unique ID, and our REST endpoint has the follwing form:
Further, let there be a Python function
get_image_path that takes the
id (originally supplied via a GET request) and returns the full path of where the image is stored locally. A simplified version of a Flask app realizing the required functionality is presented below (for simplicity, error handling such non-existent resource is omitted). Here, our goal is to read the original image, downscale it, and serve it in a binary form as a PNG image:
import flask import cv2 app = flask.Flask(__name__) def get_image_path(id): # ... build the full path return image_path @app.route('/image/<int:id>') def serve_image(id): # Get the full image path image_path = get_image_path(id) # Read the original image im = cv2.imread(fname, cv2.IMREAD_ANYCOLOR) # Resize the image (here make it 9 times smaller) new_shape = (im.shape // 3, im.shape // 3) im_smaller = cv2.resize(im, new_shape) # Encode the resized image to PNG _, im_bytes_np = cv2.imencode('.png', im_smaller) # Constuct raw bytes string bytes_str = im_bytes_np.tobytes() # Create response given the bytes response = flask.make_response(bytes_str) response.headers.set('Content-Type', 'image/png') return response
The pipeline here is as follows. The original color image is read as a NumPy array. It then undergoes a series of transformations (in our example, resizing). The changed image is then encoded in PNG format with
cv2.imencode. The result of the latter is
(1 x n) NumPy array, which has to be further converted to raw bytes with NumPy’s
tobytes method. Finally, Flask’s
make_response function is used to construct an HTTP response with the image bytes.
You may take a look at similar use cases with some alternative approaches at these StackOvervlow questions: