Flask MJPEG流加载耗时过长问题排查求助
问题:网页摄像头图像加载延迟严重
参考Miguel的博客实现了一个在网页显示摄像头捕获图像的类(包含图像处理步骤),近期出现网站加载异常:index.html中的静态内容能直接显示,但图像需要30秒到1分钟才开始展示。
编写了简化版代码排查,但仍未找到问题根源,相关代码如下:
Python 代码
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(" semaphore full") self.imgdelete += 1 print(" image 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 "+str(fpsCounter)+" "+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")
HTML 代码(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>
已做排查
- 测试排除了队列、信号量、进程间通信的问题
- 本地测试发现浏览器大约在第3180张图像时开始显示,该数值与图像大小相关
- 测试Miguel的原始示例,将图像间隔从1秒改为10ms后,首次图像显示需3-4分钟
- Firefox请求日志显示服务器立即收到
GET /video_feed请求,但需等待数分钟才返回首帧
服务运行在计算模块上,通过本地网络访问,此前系统运行正常,恳请提供排查思路。
内容的提问来源于stack exchange,提问作者chillking
相关产品推荐
相关产品推荐

