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

Flask如何在不同页面独立传输多路摄像头视频流

Flask多摄像头实时流切换阻塞问题解决方案

问题描述

开发支持多摄像头实时流查看的Flask应用,需求为允许用户在不同页面切换查看2路独立摄像头的实时视频流。
当前单路流实现存在核心故障:视频流启动后,程序永久阻塞在gen_frames()函数的while True循环中,用户无法返回首页选择其他摄像头流。此前尝试threading多线程方案但未掌握实现逻辑,未落地。
核心需求:实现用户返回Flask主页面时自动中断帧生成的死循环。

原有问题代码

import cv2

app = Flask(__name__)

global camera

def gen_frames():
    while True:
        success, frame = camera.read()  # read the camera frame
        if not success:
            break
        else:
            ret, buffer = cv2.imencode('.jpg', frame)
            frame = buffer.tobytes()
            yield (b'--frame\r\n'
                   b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')  # concat frame one by one and show result

@app.route('/video_feed')
def video_feed():
    return Response(gen_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')

@app.route('/Camera<int:x>')
def camera(x):
    global camera
    camera = cv2.VideoCapture(x-1)
    return render_template('cameras.html')

@app.route('/')
def index():
    return render_template('index.html')

配套文件说明

  • index.html:首页,包含跳转至各摄像头页面的超链接
  • cameras.html:单摄像头展示页,包含视频流展示组件与返回首页的超链接

问题根因

  1. 使用全局变量存储摄像头实例,全局状态会被不同请求覆盖,且生命周期和请求解绑,无法在用户断开连接时及时释放
  2. 帧生成循环没有检测客户端连接状态,即使用户已经离开页面、断开了流请求,循环依然会在服务端持续运行
  3. 视频流路由没有和摄像头编号绑定,依赖全局变量传参,切换摄像头时旧流没有退出通道

修正实现

后端代码改动

  • 移除全局摄像头变量,每个流请求独立初始化对应摄像头实例,避免状态冲突
  • 在帧生成循环中增加客户端连接状态检测,用户跳走/断开时立刻退出循环
  • 用finally块保证摄像头资源无论循环因何种原因退出都会被释放
  • 视频流路由直接接收摄像头编号参数,和页面路由绑定,不依赖全局变量传参

修正后完整后端代码:

import cv2
from flask import Flask, Response, render_template, request

app = Flask(__name__)

def gen_frames(camera_id):
    # 每个流请求独立初始化摄像头,不共享全局实例
    cap = cv2.VideoCapture(camera_id)
    try:
        while True:
            # 检测客户端是否已断开连接,断开则直接退出循环
            if request.environ.get('wsgi.input_terminated', False):
                break
            success, frame = cap.read()
            if not success:
                break
            ret, buffer = cv2.imencode('.jpg', frame)
            frame_bytes = buffer.tobytes()
            yield (b'--frame\r\n'
                   b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n')
    finally:
        # 确保资源一定会被释放
        cap.release()

@app.route('/video_feed/<int:camera_id>')
def video_feed(camera_id):
    return Response(
        gen_frames(camera_id),
        mimetype='multipart/x-mixed-replace; boundary=frame'
    )

@app.route('/Camera<int:x>')
def camera_page(x):
    # 将当前摄像头id传给模板,直接绑定对应流地址
    return render_template('cameras.html', current_cam_id=x-1)

@app.route('/')
def index():
    return render_template('index.html')

if __name__ == '__main__':
    app.run(debug=True, threaded=True)

前端模板改动

修改cameras.html中的视频流标签地址,匹配带参数的流路由:

<!-- 摄像头视频展示组件 -->
<img src="{{ url_for('video_feed', camera_id=current_cam_id) }}" width="720" height="480" alt="摄像头实时流">

效果说明

  • 用户点击返回首页、切换其他摄像头页面时,浏览器会自动断开当前的视频流HTTP连接,服务端检测到断开状态后会立刻退出帧生成循环,释放摄像头资源,不会出现永久阻塞
  • 每个流请求持有独立的摄像头实例,不会出现全局变量被覆盖导致的串流、资源抢占问题,两个摄像头可同时被访问
  • 无需复杂的多线程逻辑,基础场景下性能完全满足需求;如果需要支持更高并发,可在这个基础上增加后台帧缓存线程,不用改动核心的连接检测、资源释放逻辑

内容的提问来源于stack exchange,提问作者Thiago Cerqueira

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 02:12:32