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

Django后端实现OpenCV浮点型帧速率流式传输至HTML页面求助

解决Django流式传输OpenCV浮点帧率到Chart.js的问题

核心问题分析

视频流依赖multipart/x-mixed-replace格式传输二进制帧,但浮点帧率是文本数据,直接套用该格式会因格式不匹配导致传输失效。

可行解决方案

方案1:分离视频流与帧率数据流

分别搭建两个独立视图:一个维持原视频流功能,另一个用Server-Sent Events(SSE)推送实时帧率数据(SSE天生适合文本/JSON的实时单向推送)。

后端代码修改

  1. 帧率推送视图(SSE)
from django.http import StreamingHttpResponse
import time
import cv2

def stream_fps(request):
    def gen_fps():
        cap = cv2.VideoCapture(0)  # 替换为你的视频源(文件/摄像头)
        prev_time = time.time()
        frame_count = 0
        while cap.isOpened():
            ret, frame = cap.read()
            if not ret:
                break
            # 计算实际处理帧率(比CAP_PROP_FPS更准确)
            frame_count += 1
            current_time = time.time()
            if current_time - prev_time >= 0.5:
                fps = frame_count / (current_time - prev_time)
                frame_count = 0
                prev_time = current_time
                # SSE格式要求:data: 内容\n\n
                yield f"data: {round(fps, 2)}\n\n"
            time.sleep(0.01)
        cap.release()
    
    response = StreamingHttpResponse(gen_fps(), content_type='text/event-stream')
    response['Cache-Control'] = 'no-cache'
    response['Connection'] = 'keep-alive'
    return response
  1. 原视频流视图保持不变
    确保video_feed类继续返回multipart/x-mixed-replace格式的视频帧流。

前端代码修改

用JS监听SSE流,实时更新Chart.js数据:

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<canvas id="fpsChart" width="400" height="200"></canvas>
<script>
    // 初始化Chart.js
    const ctx = document.getElementById('fpsChart').getContext('2d');
    const fpsChart = new Chart(ctx, {
        type: 'line',
        data: {
            labels: [],
            datasets: [{
                label: '实时帧率',
                data: [],
                borderColor: 'rgb(75, 192, 192)',
                tension: 0.1
            }]
        },
        options: {
            scales: {
                y: {
                    beginAtZero: true,
                    max: 60
                }
            }
        }
    });

    // 监听SSE流
    const eventSource = new EventSource('/stream_fps/');
    eventSource.onmessage = function(event) {
        const fps = parseFloat(event.data);
        const now = new Date().toLocaleTimeString();
        fpsChart.data.labels.push(now);
        fpsChart.data.datasets[0].data.push(fps);
        // 只保留最近20个数据点,避免图表过载
        if (fpsChart.data.labels.length > 20) {
            fpsChart.data.labels.shift();
            fpsChart.data.datasets[0].data.shift();
        }
        fpsChart.update();
    };

    eventSource.onerror = function(error) {
        console.error('帧率流连接异常:', error);
        eventSource.close();
    };
</script>

方案2:在视频流中嵌入帧率数据

若不想拆分流,可在每个视频帧的HTTP头中添加帧率信息,前端解析头信息提取数据。

后端修改帧率生成逻辑

def gen_speed(cap):
    prev_time = time.time()
    frame_count = 0
    while True:
        ret, frame = cap.read()
        if not ret:
            break
        # 计算实际帧率
        frame_count += 1
        current_time = time.time()
        fps = frame_count / (current_time - prev_time) if current_time - prev_time > 0 else 0
        if current_time - prev_time >= 0.5:
            frame_count = 0
            prev_time = current_time
        # 编码帧为JPEG
        ret, buffer = cv2.imencode('.jpg', frame)
        frame_bytes = buffer.tobytes()
        # 构造带自定义头的multipart响应
        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n'
               f'X-FPS: {round(fps, 2)}\r\n\r\n' + frame_bytes + b'\r\n')

前端解析视频流头信息

<img id="video-feed" />
<canvas id="fpsChart" width="400" height="200"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
    const fpsChart = new Chart(document.getElementById('fpsChart').getContext('2d'), {
        type: 'line',
        data: { labels: [], datasets: [{ label: '实时帧率', data: [], borderColor: 'rgb(75, 192, 192)' }] },
        options: { scales: { y: { beginAtZero: true, max: 60 } } }
    });

    async function parseVideoStream() {
        const response = await fetch('/video_feed/');
        const reader = response.body.getReader();
        const decoder = new TextDecoder();
        let buffer = '';

        while (true) {
            const { done, value } = await reader.read();
            if (done) break;
            buffer += decoder.decode(value, { stream: true });
            
            const frames = buffer.split('--frame\r\n');
            buffer = frames.pop();
            
            for (const frame of frames) {
                if (!frame) continue;
                const [headerPart, contentPart] = frame.split('\r\n\r\n');
                const headers = headerPart.split('\r\n');
                let fps = null;
                for (const header of headers) {
                    if (header.startsWith('X-FPS:')) {
                        fps = parseFloat(header.split(': ')[1]);
                        break;
                    }
                }
                // 更新图表
                if (fps) {
                    const now = new Date().toLocaleTimeString();
                    fpsChart.data.labels.push(now);
                    fpsChart.data.datasets[0].data.push(fps);
                    if (fpsChart.data.labels.length > 20) {
                        fpsChart.data.labels.shift();
                        fpsChart.data.datasets[0].data.shift();
                    }
                    fpsChart.update();
                }
                // 更新视频显示
                const blob = new Blob([contentPart], { type: 'image/jpeg' });
                document.getElementById('video-feed').src = URL.createObjectURL(blob);
            }
        }
    }

    parseVideoStream();
</script>

关键注意事项

  • 用SSE时,开发环境需确保Django服务器支持流式响应(比如用Gunicorn,避免依赖默认开发服务器的有限流式支持)。
  • 实际帧率计算建议用时间差统计,而非直接调用cap.get(cv2.CAP_PROP_FPS),后者返回的是视频文件的预设帧率,无法反映实时处理性能。

内容的提问来源于stack exchange,提问作者Aizen-ofc

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 13:10:42