Video lags in streaming client

System setup:
IP Camera —> Processing Server (Ubuntu 20.04) —> Display Unit (browser as client)

My processing server works as an inference server and processes the images for face classification using OpenCV and Dlib with Python 3.8 and CUDA. Finally, streams the output using Flask so that I can see it in the browser from anywhere.

Everything works as expected, no lags in the capture or processing. Output video is smooth if I see it from the browser on the same server. However, the output (video) lags and freezes if I open it from other devices (in the same network).

I’m really stuck, tried many diffent things but nothing really helped. Will be grateful if some kind hearted helped to get out of the situation.

My Python coding is as follow:

import cv2
import numpy as np
from flask import Flask
import threading
...
import psycopg2
from psycopg2.pool import ThreadedConnectionPool
...

thread_lock = threading.Lock()
out_cam1 = out_cam2 = None

# create db-pool
db_pool = create_pool_connection_pgsql()


# ----------------------------------------------------------
class FrameCapture:
    def __init__(self, source=0, backend=1800):
        if backend and isinstance(backend, int):
            self.stream = cv2.VideoCapture(source, backend)
        else:
            self.stream = cv2.VideoCapture(source)

        (self.grabbed, self.frame) = self.stream.read()
        self.stopped = False

        # thread initialization
        self.thread = None

    def start(self):
        self.thread = Thread(target=self.update, args=())
        self.thread.daemon = True
        self.thread.start()
        return self

    def update(self):
        while True:
            # if the thread indicator variable is set, stop the thread
            if self.stopped:
                return
            # otherwise, read the next frame from the stream
            (self.grabbed, self.frame) = self.stream.read()

    def read(self):
        # return the frame most recently read
        return self.frame

    def stop(self):
        # indicate that the thread should be stopped
        self.stopped = True


# ----------------------------------------------------------
def get_camera_id():
    global db_pool
    # get list of camera from db
    ...

    
# ----------------------------------------------------------
def camera_source(cam_id):        
    global db_pool
    # get camera source from db
    ...
    
    
# ----------------------------------------------------------
def process_frames(cam_id):
    global thread_lock
    global out_cam1, out_cam2, ...
    
    ...
    cam_cap = FrameCapture(source=camera_source(cam_id)).start()
    
    while True:
        frame = cam_cap.read()
        
        # processing frames here
        ...
        ...
        
        with thread_lock:
            if cam_id == "cam_1":
                out_cam1 = frame.copy()
            elif cam_id == "cam_2":
                out_cam2 = frame.copy()
            ...


# ----------------------------------------------------------
def generate_stream(cam_id):
    global thread_lock
    global out_cam1, out_cam2, ...

    # loop over frames from the output stream
    while True:
        with thread_lock:
            if cam_id == "cam_1":
                if out_cam1 is None:
                    continue
 
               # encode the frame in JPEG format
                (flag, encoded_image) = cv2.imencode(".jpg", out_cam1)
                if not flag:
                    continue

            elif cam_id == "cam_2":
                if out_cam2 is None:
                    continue

                # encode the frame in JPEG format
                (flag, encoded_image) = cv2.imencode(".jpg", out_cam2)
                if not flag:
                    continue
                    
            ...                                                                     

        # yield the output frame in the byte format
        yield b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + encoded_image.tobytes() + b'\r\n'

        
# ----------------------------------------------------------
# feed generator for cam display
@app.route('/cam_feed/', methods=['GET'])
def camera_feed():
    return Response(generate_stream(cam_id=request.args.get('cam_id')),
                    mimetype="multipart/x-mixed-replace; boundary=frame")
        

# ----------------------------------------------------------
# display for camera
@app.route('/<cam_id>/', methods=['GET'])
def camera_display(cam_id):

    return render_template("cam_feed.html", cam_id=cam_id)

    
            
# ----------------------------------------------------------
# check if this is the main thread
if __name__ == '__main__':
    
    # get list of cam-id's
    cam_active = get_camera_id()

    for cam in cam_active:
        cam_thread = threading.Thread(target=process_frames, name=cam, args=(cam,))
        cam_thread.daemon = True
        cam_thread.start()
        sleep(3)

    # start the app
    app.run(host='0.0.0.0',
            port=5000,
            # debug=True,
            debug=False,
            threaded=True,
            use_reloader=False
            )

...

# close db pool connections
if db_pool:
    db_pool.closeall()

think of it – how do you actually want us to help you here ?

you introduced some weird python multitasking lib,
but removed all hints about actual opencv processing

again, as it is now, noone can reproduce your problem

Dear @berak,

Thank you for your response.
To be honest, I’m new in OpenCV community and learning everyday.

Anyway, it’s a large project with multiple modules and lib’s etc. It’s not really possible to include all of them here. I stripped the basic part and added here so that someone may understand the the problem or the issue may be known to him already.

Appreciate your advice.

btw, using flask to stream a webcam seems to be a terrible idea.
(you cant make it “multi-client” this way)

I preferred web client (multi-client) considering remote access. If you have a better idea, kindly suggest. It will be a great help.