Trying to display multiple streams with Opencv and Flask

Viewed 3612

I'm trying to capture two rtsp streams with opencv and then present them with a simple flask server. I can show the two streams together when just using opencv, but when I try to display it through flask it just picks either stream and shows it twice.

Here's the original creators blog

Here is my flask code:

#!/usr/bin/env python
from importlib import import_module
import os
from flask import Flask, render_template, Response

# import camera driver
'''
if os.environ.get('CAMERA'):
    Camera = import_module('camera_' + os.environ['CAMERA']).Camera
else:
    from camera import Camera
'''
#
from camera_opencv import Camera1, Camera2


app = Flask(__name__)


@app.route('/')
def index():
    """Video streaming home page."""
    return render_template('index.html')


def gen(camera):
    """Video streaming generator function."""
    while True:
        frame = camera.get_frame()
        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')


@app.route('/video_feed1')
def video_feed1():
    """Video streaming route. Put this in the src attribute of an img tag."""
    return Response(gen(Camera1()),
                    mimetype='multipart/x-mixed-replace; boundary=frame')

@app.route('/video_feed2')
def video_feed2():
    """Video streaming route. Put this in the src attribute of an img tag."""
    return Response(gen(Camera2()),
                    mimetype='multipart/x-mixed-replace; boundary=frame')


if __name__ == '__main__':
    app.run(host='0.0.0.0', threaded=True, port=8888)

Here's the camera_opencv file

import cv2
from base_camera import BaseCamera


class Camera1(BaseCamera):
    video_source = 0


    @staticmethod
    def set_video_source(source):
        Camera1.video_source = source

    @staticmethod
    def frames():
        camera = cv2.VideoCapture(Camera1.video_source)
        if not camera.isOpened():
            raise RuntimeError('Could not start camera.')

        while True:
            # read current frame
            _, img = camera.read()

            # encode as a jpeg image and return it
            yield cv2.imencode('.jpg', img)[1].tobytes()

class Camera2(BaseCamera):
    video_source = 1

    @staticmethod
    def set_video_source(source):
        Camera2.video_source = source

    @staticmethod
    def frames():
        camera = cv2.VideoCapture(Camera2.video_source)
        if not camera.isOpened():
            raise RuntimeError('Could not start camera.')

        while True:
            # read current frame
            _, img = camera.read()

            # encode as a jpeg image and return it
            yield cv2.imencode('.jpg', img)[1].tobytes()

Base camera file

import time
import threading
try:
    from greenlet import getcurrent as get_ident
except ImportError:
    try:
        from thread import get_ident
    except ImportError:
        from _thread import get_ident


class CameraEvent(object):
    """An Event-like class that signals all active clients when a new frame is
    available.
    """
    def __init__(self):
        self.events = {}

    def wait(self):
        """Invoked from each client's thread to wait for the next frame."""
        ident = get_ident()
        if ident not in self.events:
            # this is a new client
            # add an entry for it in the self.events dict
            # each entry has two elements, a threading.Event() and a timestamp
            self.events[ident] = [threading.Event(), time.time()]
        return self.events[ident][0].wait()

    def set(self):
        """Invoked by the camera thread when a new frame is available."""
        now = time.time()
        remove = None
        for ident, event in self.events.items():
            if not event[0].isSet():
                # if this client's event is not set, then set it
                # also update the last set timestamp to now
                event[0].set()
                event[1] = now
            else:
                # if the client's event is already set, it means the client
                # did not process a previous frame
                # if the event stays set for more than 5 seconds, then assume
                # the client is gone and remove it
                if now - event[1] > 5:
                    remove = ident
        if remove:
            del self.events[remove]

    def clear(self):
        """Invoked from each client's thread after a frame was processed."""
        self.events[get_ident()][0].clear()


class BaseCamera(object):
    thread = None  # background thread that reads frames from camera
    frame = None  # current frame is stored here by background thread
    last_access = 0  # time of last client access to the camera
    event = CameraEvent()

    def __init__(self):
        """Start the background camera thread if it isn't running yet."""
        if BaseCamera.thread is None:
            BaseCamera.last_access = time.time()

            # start background frame thread
            BaseCamera.thread = threading.Thread(target=self._thread)
            BaseCamera.thread.start()

            # wait until frames are available
            while self.get_frame() is None:
                time.sleep(0)

    def get_frame(self):
        """Return the current camera frame."""
        BaseCamera.last_access = time.time()

        # wait for a signal from the camera thread
        BaseCamera.event.wait()
        BaseCamera.event.clear()

        return BaseCamera.frame

    @staticmethod
    def frames():
        """"Generator that returns frames from the camera."""
        raise RuntimeError('Must be implemented by subclasses.')

    @classmethod
    def _thread(cls):
        """Camera background thread."""
        print('Starting camera thread.')
        frames_iterator = cls.frames()
        for frame in frames_iterator:
            BaseCamera.frame = frame
            BaseCamera.event.set()  # send signal to clients
            time.sleep(0)

            # if there hasn't been any clients asking for frames in
            # the last 10 seconds then stop the thread
            if time.time() - BaseCamera.last_access > 10:
                frames_iterator.close()
                print('Stopping camera thread due to inactivity.')
                break
        BaseCamera.thread = None

Index.html

<html>
  <head>
    <title>Video Streaming Demonstration</title>
  </head>
  <body>
    <h1>Video Streaming Demonstration</h1>
    <img src="{{ url_for('video_feed1') }}">

    <img src="{{ url_for('video_feed2') }}">
  </body>
</html>
2 Answers

So I kind of managed to make a hackey workaround. For whatever reason I could not resolve, the app just couldn't handle multiple streams individually.

So I changed the camera class and added multiple sources to it and used numpy.hstack(()) to merge both the frames together thus returning one unique stream.

Will be very grateful if someone could help out here as my method is not at all scalable.

import cv2
from base_camera import BaseCamera
import numpy as np


class Camera(BaseCamera):
    video_source1 = 0
    video_source2 = 1

    @staticmethod
    def set_video_source(sources):
        Camera.video_source1 = sources[0]
        Camera.video_source2 = sources[1]

    @staticmethod
    def frames():
        camera1 = cv2.VideoCapture(Camera.video_source1)
        camera2 = cv2.VideoCapture(Camera.video_source2)
        if not (camera1.isOpened() or camera2.isOpened()):
            raise RuntimeError('Could not start camera.')

        while True:
            # read current frame
            _, img1 = camera1.read()
            _, img2 = camera2.read()
            img1 = cv2.resize(img1, (704, 396))
            img2 = cv2.resize(img2, (704, 396))
            img = np.hstack((img1, img2))

            # encode as a jpeg image and return it
            yield cv2.imencode('.jpg', img)[1].tobytes()

According to the blog, I use two generator and send the image to the index.html. And I can see tow streaming.

def generate2():
    # it is a generator
    global outputFrame2, lock

    while True:
        with lock:
            if outputFrame is None:
                continue

            (flag, encodedImage2) = cv2.imencode(".jpg", outputFrame2)
            if not flag:
                continue

        yield(b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + 
            bytearray(encodedImage2) + b'\r\n')

And here is videofeed2

@app.route("/video_feed2")
def video_feed2():
    return Response(generate2(),
        mimetype = "multipart/x-mixed-replace; boundary=frame")
Related