多线程共享frame_buffer异常求助:Flask+YOLOv5流推送问题
Flask + YOLOv5 视频流同步问题修复方案
核心问题分析
- 误用线程锁:
queue.Queue是Python内置的线程安全队列,内部已实现同步锁机制,手动添加threading.Lock会引发锁竞争,破坏队列原生线程安全特性,导致帧同步异常。 - 生成器逻辑缺陷:
get_stream作为视频流生成器仅执行一次帧获取就终止,无法持续输出新帧,网页只能加载单帧画面。 - 前端逻辑冗余:
multipart/x-mixed-replace协议支持单次请求持续接收帧流,前端无需用setInterval重复刷新,重复请求会导致流中断和资源浪费。 - 队列阻塞无容错:
frame_buffer.get()默认无限阻塞等待帧,若检测线程异常,会导致流线程挂起无响应。
修复后的完整代码
import cv2 import time from flask import Flask, Response from flask_socketio import SocketIO, emit import threading import detect import queue app = Flask(__name__) socketio = SocketIO(app) # 移除多余的threading.Lock,Queue本身线程安全 frame_buffer = queue.Queue(maxsize=10) def update_frame(image): """将编码后的帧写入缓冲区""" ret, buffer = cv2.imencode('.jpg', image, [cv2.IMWRITE_JPEG_QUALITY, 80]) if ret: try: if frame_buffer.full(): frame_buffer.get_nowait() # 移除最旧帧,避免队列溢出 frame_buffer.put(buffer.tobytes(), block=False) print(f"帧已写入缓冲区,当前队列大小: {frame_buffer.qsize()}") except queue.Full: print("缓冲区已满,丢弃当前帧") else: print("警告:帧编码失败") # 根据检测帧率调整sleep,避免过度占用CPU time.sleep(0.03) def get_stream(): """持续生成视频流的生成器""" while True: try: # 超时等待帧,避免无限阻塞 frame_data = frame_buffer.get(timeout=5) yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame_data + b'\r\n') except queue.Empty: # 队列空时返回空帧或提示帧 empty_frame = cv2.imencode('.jpg', cv2.zeros((480,640,3), dtype='uint8'))[1].tobytes() yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + empty_frame + b'\r\n') print("缓冲区为空,等待帧输入...") @app.route('/') def index(): return '''<html> <head> <title>实时检测流</title> </head> <body> <h1>YOLOv5 实时检测流</h1> <!-- 移除前端定时刷新,依赖multipart协议自动更新 --> <img id="stream" src="/video_feed" width="640" height="480"> </body> </html>''' @app.route('/video_feed') def video_feed(): return Response(get_stream(), mimetype='multipart/x-mixed-replace; boundary=frame') @socketio.on('connect') def handle_connect(): print('客户端已连接') emit('stream_start', broadcast=True) @socketio.on('disconnect') def handle_disconnect(): print('客户端已断开连接') if __name__ == '__main__': # 启动YOLOv5检测线程,确保detect.runDetect内部会调用update_frame传入检测后的帧 cam_thread = threading.Thread(target=detect.runDetect, daemon=True) cam_thread.start() # 启动Flask服务,开启多线程支持 app.run(host='0.0.0.0', debug=False, threaded=True)
关键修改说明
- 移除手动锁:删除
frame_lock相关代码,依赖queue.Queue原生线程安全特性。 - 生成器循环输出:
get_stream添加while True循环,持续从队列获取帧并输出,确保流不会中断。 - 队列超时处理:使用
frame_buffer.get(timeout=5)避免线程无限阻塞,超时后返回空帧提示。 - 简化前端逻辑:删除定时刷新脚本,让
multipart/x-mixed-replace协议自动更新画面。 - 优化帧编码:添加JPEG质量参数,平衡画质和传输速度;设置线程为守护线程,确保主进程退出时自动终止。
内容的提问来源于stack exchange,提问作者Nizar Shehayeb
相关产品推荐
相关产品推荐

