Skip to content

Stream OpenCV Video Capture to flask server

Greeting, I was working on a drone project, I wanted to take stream from my drone process it on my laptop and give a command based on processing, I was using the flask framework from the same.

Currently, as the first step I want to take the stream from drone and PUT it to the flask server and view it on the flask website, not doing the processing part right now.

I PUT the video to server after compressing it into jpg and using base 64 to encode it and then finally use json.dumps() and then requests.put() it.

On the server side in flask server program I get its using request.json, use json.loads(), but I am not clear what to do next.

I am not experienced enough with flask, web development and with limited experience and knowledge made the programs, but it returns error 405 on the flask program.

Here are the programs

flask server

import base64
import json

from flask import Flask, make_response, render_template, request

app = Flask(__name__)


def getFrames(img):
    pass


@app.route('/video', methods=['POST', 'GET'])
def video():
    if request.method == 'PUT':
        load = json.loads(request.json)
        imdata = base64.b64decode(load['image'])
        respose = make_response(imdata.tobytes())
        return respose


@app.route('/')
def index():
    return render_template('index.html')


@app.route('/cmd')
def cmd():
    pass


if __name__ == "__main__":
    app.run(debug=True)

index.html

<!DOCTYPE html>
<html>

<head>
    <title>Video Stream</title>
</head>
<body>
    <h1>
        Live Stream
    </h1>
    <div>
        <img src="{{ url_for('video') }}" width="50%">
    </div>
</body>
</html>

drone program

import base64
import json
import requests
import cv2

cap = cv2.VideoCapture(1)

ip = ''                   #url returned by the flask program

while True:
    success, img = cap.read()
    cv2.imshow("OUTPUT", img)
    _, imdata = cv2.imencode('.JPG', img)
    jStr = json.dumps({"image": base64.b64encode(imdata).decode('ascii')})
    requests.put(url=(ip + '/video'), data=jStr)
    if cv2.waitKey(1) == 27:
        break

Any help is highly appreciated!!!

Answer

You don’t have to convert to base64 and use JSON. it can be simpler and faster to send JPG directly as raw bytes

And to make it simpler I would use /upload to send image from drone to server, and /video to send image to users.

import requests
import cv2

cap = cv2.VideoCapture(0)

while True:
    success, img = cap.read()

    if success:    
        cv2.imshow("OUTPUT", img)
        
         _, imdata = cv2.imencode('.JPG', img)

        print('.', end='', flush=True)

        requests.put('http://127.0.0.1:5000/upload', data=imdata.tobytes())
    
    # 40ms = 25 frames per second (1000ms/40ms), 
    # 1000ms = 1 frame per second (1000ms/1000ms)
    # but this will work only when `imshow()` is used.
    # Without `imshow()` it will need `time.sleep(0.04)` or `time.sleep(1)`

    if cv2.waitKey(40) == 27:  # 40ms = 25 frames per second (1000ms/40ms) 
        break

cv2.destroyAllWindows()
cap.release()

Now flask. This part is not complete.

It gets image from drone and keep in global variable. And when user open page then it loads single image from /video

from flask import Flask, make_response, render_template, request

app = Flask(__name__)

frame = None   # global variable to keep single JPG

@app.route('/upload', methods=['PUT'])
def upload():
    global frame
    
    # keep jpg data in global variable
    frame = request.data
    
    return "OK"

@app.route('/video')
def video():
    if frame:
        return make_response(frame)
    else:
        return ""

@app.route('/')
def index():
    return 'image:<br><img src="/video">'

if __name__ == "__main__":
    app.run(debug=True)

At this moment it can display only one static image. It needs to send it as motion-jpeg


EDIT:

Version which sends motion-jpeg so you see video.

It works correctly with Chrome, Microsoft Edge and Brave (all use chrome engine).

Problem makes Firefox. It hangs and tries to load image all time. I don’t know what is the real problem but if I add time.sleep() then it can solve problem.

from flask import Flask, Response, render_template_string, request
import time

app = Flask(__name__)

frame = None   # global variable to keep single JPG, 
               # at start you could assign bytes from empty JPG

@app.route('/upload', methods=['PUT'])
def upload():
    global frame
    
    # keep jpg data in global variable
    frame = request.data
    
    return "OK"

def gen():
    while True:
        yield (b'--framern'
               b'Content-Type: image/jpegrn'
               b'rn' + frame + b'rn')
        time.sleep(0.04) # my Firefox needs some time to display image / Chrome displays image without it 
                         # 0.04s = 40ms = 25 frames per second 

        
@app.route('/video')
def video():
    if frame:
        # if you use `boundary=other_name` then you have to yield `b--other_namern`
        return Response(gen(), mimetype='multipart/x-mixed-replace; boundary=frame')
    else:
        return ""

@app.route('/')
def index():
    return 'image:<br><img src="/video">'
    #return render_template_string('image:<br><img src="{{ url_for("video") }}">')

if __name__ == "__main__":
    app.run(debug=True)#, use_reloader=False)

Server may runs users in separated threads on processes and sometimes it may not share frame between users. If I use use_reloader=False then I can stop sending to /upload and this stops video in browser, and later I can start again sending to /upload and browser again displays stream (without reloading page). Without use_reloader=False browser doesn’t restart video and it needs to reload page. Maybe it will need to use flask.g to keep frame. Or /upload will have to save frame in file or database and /video will have to read frame from file or database.