You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多线程共享frame_buffer异常求助:Flask+YOLOv5流推送问题

Flask + YOLOv5 视频流同步问题修复方案

核心问题分析

  1. 误用线程锁:queue.Queue是Python内置的线程安全队列,内部已实现同步锁机制,手动添加threading.Lock会引发锁竞争,破坏队列原生线程安全特性,导致帧同步异常。
  2. 生成器逻辑缺陷:get_stream作为视频流生成器仅执行一次帧获取就终止,无法持续输出新帧,网页只能加载单帧画面。
  3. 前端逻辑冗余:multipart/x-mixed-replace协议支持单次请求持续接收帧流,前端无需用setInterval重复刷新,重复请求会导致流中断和资源浪费。
  4. 队列阻塞无容错: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 20:21:10