Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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()
like image 558
Arastelion Avatar asked Feb 05 '23 01:02

Arastelion


2 Answers

Changing the frame generator worked for me:

yield (b'--frame\r\n'
       b'Content-Type:image/jpeg\r\n'
       b'Content-Length: ' + f"{len(frame)}".encode() + b'\r\n'
       b'\r\n' + frame + b'\r\n')
like image 176
Rubén Salas Avatar answered Feb 06 '23 14:02

Rubén Salas


I'm new to this but it's definitely a multithreading issue, as it only happens sometimes when I reload the page. Easy fix:

camera.py

import cv2, threading

lock = threading.Lock()
camIpLink = 'http://user:[email protected]/with/video/footage'
cap = cv2.VideoCapture(camIpLink)

def getFrame():
    global cap
    with lock:
        while True:
            try:
                return bytes(cv2.imencode('.jpg', cap.read()[1])[1])
            except:
                print("Frame exception")
                cap.release()
                cap = cv2.VideoCapture(camIpLink)

server.py

from camera import getFrame
def gen():
    while True:
        frame = getFrame()
        try:
            yield (b'--frame\r\n'
                    b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
        except:
            print("Yield Exception")
            return

@app.route("/cam", methods=['GET']) # todo authentication
def camVideoFootage():
    return Response(gen(),
                    mimetype='multipart/x-mixed-replace; boundary=frame')

I did some error handling through trial and error. Hope that helps!

like image 27
Powereleven Avatar answered Feb 06 '23 14:02

Powereleven