Ankit
Ankit

Reputation: 359

How to stream video (motion jpeg) using falcon server?

Desired output

Input: Camera feed using OpenCV or from a REST camera url. (Not a concern for this question)

Output: Streaming jpeg images after doing some OpenCV processing


So far I have done the following based on Falcon tutorial

Input: Image file as a POST request

Output: GET request endpoint with the path to the image

import mimetypes
import os
import re
import uuid
import cv2
import io
import falcon
from falcon import media
import json
import msgpack

class Collection(object):

    def __init__(self, image_store):
        self._image_store = image_store

    def on_get(self, req, resp):
        # TODO: Modify this to return a list of href's based on
        # what images are actually available.
        doc = {
            'images': [
                {
                    'href': '/images/1eaf6ef1-7f2d-4ecc-a8d5-6e8adba7cc0e.png'
                }
            ]
        }

        resp.data = msgpack.packb(doc, use_bin_type=True)
        resp.content_type = falcon.MEDIA_MSGPACK
        resp.status = falcon.HTTP_200

    def on_post(self, req, resp):
        name = self._image_store.save(req.stream, req.content_type)
        # Unnecessary Hack to read the saved file in OpenCV
        image = cv2.imread("images/" + name)
        new_image = do_something_with_image(image)
        _ = cv2.imwrite("images/" + name, new_image)
        resp.status = falcon.HTTP_201
        resp.location = '/images/' + name


class Item(object):

    def __init__(self, image_store):
        self._image_store = image_store

    def on_get(self, req, resp, name):
        resp.content_type = mimetypes.guess_type(name)[0]
        resp.stream, resp.stream_len = self._image_store.open(name)


class ImageStore(object):

    _CHUNK_SIZE_BYTES = 4096
    _IMAGE_NAME_PATTERN = re.compile(
        '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.[a-z]{2,4}$'
    )

    def __init__(self, storage_path, uuidgen=uuid.uuid4, fopen=io.open):
        self._storage_path = storage_path
        self._uuidgen = uuidgen
        self._fopen = fopen

    def save(self, image_stream, image_content_type):
        ext = mimetypes.guess_extension(image_content_type) # Issue with this code, Not returning the extension so hard coding it in next line
        ext = ".jpg"
        name = '{uuid}{ext}'.format(uuid=self._uuidgen(), ext=ext)
        image_path = os.path.join(self._storage_path, name)

        with self._fopen(image_path, 'wb') as image_file:
            while True:
                chunk = image_stream.read(self._CHUNK_SIZE_BYTES)
                if not chunk:
                    break

                image_file.write(chunk)

        return name

    def open(self, name):
        # Always validate untrusted input!
        if not self._IMAGE_NAME_PATTERN.match(name):
            raise IOError('File not found')

        image_path = os.path.join(self._storage_path, name)
        stream = self._fopen(image_path, 'rb')
        stream_len = os.path.getsize(image_path)
        return stream, stream_len


def create_app(image_store):
    api = falcon.API()
    api.add_route('/images', Collection(image_store))
    api.add_route('/images/{name}', Item(image_store))
    api.add_sink(handle_404, '')
    return api


def get_app():
    storage_path = os.environ.get('LOOK_STORAGE_PATH', './images')
    image_store = ImageStore(storage_path)
    return create_app(image_store)

The response is something like this:

HTTP/1.1 201 Created

Connection: close

Date: Mon, 03 Dec 2018 13:08:14 GMT

Server: gunicorn/19.7.1

content-length: 0

content-type: application/json; charset=UTF-8

location: /images/e69a83ee-b369-47c3-8b1c-60ab7bf875ec.jpg

There are 2 problems with the above code:

  1. I first get the data stream and save it in a file and then read it in OpenCV to do some other operations which is pretty overkill and should be easily fixed but I don't know how
  2. This service does not stream the JPGs. All I have a GET request url which I can open in browser to see the image which is not ideal

So, how can I read req.stream data as numpy array? And more importantly what changes I need to make to stream images from this service?

P.S. Apologies for a long post

Upvotes: 2

Views: 1142

Answers (1)

Ankit
Ankit

Reputation: 359

I have found a solution which works perfectly fine. For more on this check out this beautiful code.

def gen(camera):
    while True:
        image = camera.get_frame()
        new_image = do_something_with_image(image)
        ret, jpeg = cv2.imencode('.jpg', new_image)
        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n\r\n' + jpeg.tobytes() + b'\r\n\r\n')


class StreamResource(object):
    def on_get(self, req, resp):
        labeled_frame = gen(VideoCamera())
        resp.content_type = 'multipart/x-mixed-replace; boundary=frame'
        resp.stream = labeled_frame

def get_app():
    api = falcon.API()
    api.add_route('/feed', StreamResource())
    return api

Upvotes: 4

Related Questions