Flask MJPEG stream has long loading time

151 Views Asked by At

I wrote a class to display images on a small webpage (mainly images captured by a camera, some image processing in between). I worked after Miguel's blog post.
Since few weeks I have an issue that the website sometimes needs a long time to load. To be more presice: the static stuff in the index.html is shown directly, but the images need 30s to 1min to start showing on the website.

I made a smaller example of my class to look for this issue but I cannot discover the problem. Does someone has a tipp for me?

The class:

import flask
import cv2
import multiprocessing as mp
import numpy as np
import time
import select
import sys

## class display
#
# - show images on html site
# - saves images for logging
class display:

    ##constructor
    # - starts separate process for the website
    def __init__(self):
        self.__controleQueue = mp.Queue()
        self.__imageQueue = mp.Queue()
        self.__imageSemaphore = mp.Semaphore(30)
        self.imgdelete = 0

        print("start controle process")
        self.__controleProcess = mp.Process(target=self.__startApp)
        self.__controleProcess.start()
        print("class initialized")


    ## destructor
    def exit(self):
        print("stop process")
        self.__controleQueue.put("exit")
        while True:
            print("exit loop main")
            if self.__controleQueue.get() == "exit done":
                print("process stopped")
                break
            else:
                time.sleep(1)
        return True

    ## website app
    # - called by constructor as separate process
    # - starts website as separate process
    # - waits for message to stop website
    def __startApp(self):
        print("create app")
        self.app = self.__createApp()
        
        ip = "192.168.62.144"
        print("ip for display website: ", ip)

        print("start working process/app")
        self.__workingProcess = mp.Process(target=self.app.run, kwargs={"host":ip, "port":"80", "debug":False, "threaded":True, "use_reloader":False})
        self.__workingProcess.deamon = True
        self.__workingProcess.start()
        print("working process/app started, waiting for commands")

        while True:
            if self.__controleQueue.get() == "exit":
                print("stop working process")
                if self.__workingProcess.is_alive():
                    self.__workingProcess.terminate()
                self.__workingProcess.join(2)
                while self.__workingProcess.is_alive():
                    time.sleep(0.01)
                print("working process stopped")
                self.__controleQueue.put("exit done")
                break
            else:
                time.sleep(0.1)

        return

    ## new image
    # - send new image to display
    #
    # @param image image to show on website
    def newImage(self, image):
        # self.logger.debug("new image")
        # if len(image.shape) > 2:
        #   image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
        # self.__imageQueue.put(image)
        if self.__imageSemaphore.acquire(block=False):
        #   # print("semaphore yes")
            if len(image.shape) > 2:
                image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
            self.__imageQueue.put(image)
        else:
            print("\t\t\t\t\t\t\t\tsemaphore full")
            self.imgdelete += 1
            print("\t\t\t\t\timage delete: ", self.imgdelete)

    ## generater function to prepare new image
    # - receives image from queue
    # - resizes image to defined maximum size
    # - returns encoded image
    def __generate(self):
        print("__generate call")
        fpsCounter = 0
        fpsTime=time.time()
        sleeptime = 0.0001
        while True:
            print("loop \t"+str(fpsCounter)+"\t"+str(time.time()-fpsTime))
            fpsCounter += 1
            if fpsCounter == 3200:
                sleeptime=1
            # if fpsCounter == 1010:
            #   sleeptime=0.0001
            # if fpsCounter == 1000:
            #   sleeptime=1
            time.sleep(sleeptime)
            output = self.__imageQueue.get()
            self.__imageSemaphore.release()
            # output = np.ones((500,500,3), dtype = np.uint8)*255
            cv2.putText(output, "test display "+str(fpsCounter), (0, output.shape[0]-10), cv2.FONT_HERSHEY_SIMPLEX, 1, (255,0,0), 2)

            # self.logger.debug("yes new image")
            flag, frame = cv2.imencode(".jpg", output)
            if not flag:
                print("shit")
                continue

            yield(b'--frame\r\nContent-Type: image/jpeg\r\n\r\n' + frame.tobytes() + b'\r\n')

    ## start website app
    # - create app
    # - setup routes
    def __createApp(self):
        # app = flask.Flask("cam1", template_folder='/home/root/camino/templates')
        app = flask.Flask("test display", static_folder='tests')

        # render html page
        @app.route("/")
        def index():
            return flask.render_template("2index.html")
            # return "Congratulations, it's a web app!"

        @app.route("/video_feed")
        def videoFeed():
            # print("videoFeed: ", self.__getCurrentMemoryUsage(), " MB")
            return flask.Response(self.__generate(), mimetype = "multipart/x-mixed-replace; boundary=frame")
        return app

if __name__ == "__main__":

    disp = display()

    sizex = 1500
    sizey = 100
    image = np.zeros((sizey,sizex,3),dtype=np.uint8)
    row = 0
    col = 0
    step = 10
    change = True
    while True:
        if change:
            image[row:row+step, col:col+step,:] = 255
            col += step
            if col >= sizex:
                col = 0
                row += step
                if row >= sizey:
                    row = 0
                    image = np.zeros((sizey,sizex,3),dtype=np.uint8)
                    print("new image done")

        disp.newImage(image)
        time.sleep(0.02)

        i,o,e = select.select( [sys.stdin], [], [], 0.001)
        # print(row, col, "terminal read", i)
        if i:
            key = sys.stdin.readline().strip()
            print(key)
            if key == 'q':
                break
            if key == 'c':
                change = not change

    disp.exit()
    print("end")

2index.html

<html>
  <head>
    <link rel="icon" href="data:,">
    <title>test display</title>
  </head>
  <body>
    <h1>Welcome to FlaskApp!</h1>
    <h2>2index.html</h2>
    <img src="{{ url_for('videoFeed') }}">
  </body>
</html>

I tried to generate the "image" at different points in the code. It doesn't look like the queue, semaphore, communication between the processes are the problem. Also I figuered out, that (at my pc) the images start showing in the browser at about image nr 3180, but depends on the size of the image. (As said, the headers are showen directly after loading the page.)

I also tried the original example from Miguel (https://github.com/miguelgrinberg/flask-video-streaming) with the 3 images. The same problem occurs but it takes much longer for the first image to show. So I changed the sleeping time between each image from 1s to 10ms, now it takes about 3-4min until the first image is shown.

That's the time measurements of firefox for the GET video_feed. That's the time measurements of firefox for the GET video_feed.

The server gets the "GET /video_feed HTTP/1.1" 200 - directly when loading the website, but it takes the waiting time (here 3,71min) to show the first image.

I should mention that this runs on a compute module, website is called on my pc over local network. As I said before, this used to work well on this system.

0

There are 0 best solutions below