Search code examples
pythonopencvflaskmjpeg

Reading MJPEG stream from Flask server with OpenCV (Python)


I'm generating a MJPEG stream using Flask and flask-restful. For reasons, I want to catch this stream in another Python program, for which I use OpenCV(3). Problem is that the first frame that is requested comes in well. On the other hand, the second frame that is requested (after a delay) is not received properly, and throws the error:

[mpjpeg @ 0000017a86f524a0] Expected boundary '--' not found, instead found a line of 82 bytes

Multiple times.

I believe this happens because the boundary of a frame is set manually. I will put the offending code below.

MJPEG Stream generation:

## Controller for the streaming of content.
class StreamContent(Resource):
    @classmethod
    def setVis(self, vis):
        self.savedVis = vis

    def get(self):
        return Response(gen(VideoCamera(self.savedVis)),
                        mimetype='multipart/x-mixed-replace; boundary=frame')


## Generate a new VideoCamera and stream the retrieved frames.    
def gen(camera):
    frame = camera.getFrame()
    while frame != None:
        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n')
        time.sleep(0.07)
        frame = camera.getFrame()

## Allows for the reading of video frames.
class VideoCamera(object):
    def __init__(self, vis):
        #object we retrieve the frame from.
        self.vis = vis

    ## Get the current frame.
    def getFrame(self):
        image = self.vis.mat_frame_with_overlay
        # We are using Motion JPEG, but OpenCV defaults to capture raw images,
        # so we must encode it into JPEG in order to correctly display the
        # video/image stream.
        ret, jpeg = cv2.imencode('.jpg', image)
        return jpeg.tobytes()

MJPEG Stream retrieval:

"""
Get a single frame from the camera.
"""        
class Image(Resource):
    def get(self):
        camera = VideoCamera()
        return Response(camera.getSingleFrame(), mimetype='image/jpeg')

"""
Contains methods for retrieving video information from a source.
"""
class VideoCamera(object):
    def __del__(self):
        self.video.release()

    @classmethod
    def setVideo(self, video):
        self.video = video

    ## Get the current frame.
    def getSingleFrame(self):
        self.startVideoFromSource(self.video)
        ret, image = self.video.read()
        time.sleep(0.5)
        ret, image = self.video.read()
        # We are using Motion JPEG, but OpenCV defaults to capture raw images,
        # so we must encode it into JPEG in order to correctly display the
        # video/image stream.
        ret, jpeg = cv2.imencode('.jpg', image)
        self.stopVideo()
        return jpeg.tobytes()

    def stopVideo(self):
        self.video.release()

Solution

  • For Anabad (And others):

    Oof, it's been a while for this problem. If I remember correctly, the short answer is: NO, I was never able to get this to work properly.

    The camera is accessed by multiple programs this way at the same time (when a request is send to the API more than once, multiple threads start reading the camera) which the camera cannot handle. The best way to handle this properly (in my opinion), is to read the camera in a separate class on it's own thread, and use a observer pattern for the API. Every time a new request comes in from a client to read the camera, an observer will send the new frames once they become available.

    This solves the problem of the camera being accessed by multiple class instances/threads, which is the reason why this would not work. Get around this problem and it should work fine.