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

Flask后端目标检测结果无法在浏览器实时显示求助

解决方案:浏览器实时展示目标检测结果+动态ROI

一、核心问题拆解

你当前的核心痛点是后端检测结果无法推送到前端实时展示,以及缺少动态ROI的交互逻辑。以下是针对CPU运行场景的快速落地方案,适配你的项目交付时间要求。


二、后端修改:实现检测帧的实时推送

基于Flask构建帧流生成器,将带检测框的视频帧以multipart/x-mixed-replace格式推送到前端,替代仅在IDLE输出结果的逻辑。

示例后端代码

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

# 初始化Flask应用
app = Flask(__name__)
# 创建上传目录(确保有读写权限)
os.makedirs('./uploads', exist_ok=True)

# 替换为你的目标检测模型加载/推理逻辑
def detect_objects(frame, roi_coords=None):
    # 示例:用OpenCV自带的人脸检测模拟目标检测(替换成你的模型)
    face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    
    if roi_coords:
        x1, y1, x2, y2 = roi_coords
        roi_gray = gray[y1:y2, x1:x2]
        faces = face_cascade.detectMultiScale(roi_gray, 1.1, 4)
        # 将ROI内的检测框映射回原帧
        for (x, y, w, h) in faces:
            cv2.rectangle(frame, (x1+x, y1+y), (x1+x+w, y1+y+h), (0,255,0), 2)
    else:
        faces = face_cascade.detectMultiScale(gray, 1.1, 4)
        for (x, y, w, h) in faces:
            cv2.rectangle(frame, (x,y), (x+w,y+h), (0,255,0), 2)
    return frame

# 生成实时视频流
def generate_frames(video_path, roi_coords=None):
    cap = cv2.VideoCapture(video_path)
    # CPU优化:降低帧分辨率减少计算量
    target_width, target_height = 640, 480
    
    while cap.isOpened():
        success, frame = cap.read()
        if not success:
            break
        
        # 缩放帧(CPU性能优化)
        frame = cv2.resize(frame, (target_width, target_height))
        # 执行目标检测
        frame = detect_objects(frame, roi_coords)
        
        # 编码为JPEG格式推送到前端
        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')
    cap.release()

# 视频流接口
@app.route('/video_feed')
def video_feed():
    video_path = request.args.get('video_path')
    roi_str = request.args.get('roi')
    roi_coords = tuple(map(int, roi_str.split(','))) if roi_str else None
    return Response(generate_frames(video_path, roi_coords),
                    mimetype='multipart/x-mixed-replace; boundary=frame')

# 视频上传接口
@app.route('/upload', methods=['POST'])
def upload_video():
    video_file = request.files['video']
    save_path = f'./uploads/{video_file.filename}'
    video_file.save(save_path)
    return {'video_path': save_path}

# 前端页面路由
@app.route('/')
def index():
    return render_template('index.html')

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

三、前端实现:上传+ROI选择+实时展示

在Flask的templates目录下创建index.html,实现视频上传、动态ROI框选、实时结果展示的交互逻辑。

示例前端代码

<!DOCTYPE html>
<html>
<head>
    <title>目标检测实时展示</title>
    <style>
        #video-container {
            position: relative;
            width: 640px;
            height: 480px;
            border: 2px solid #333;
            margin: 20px 0;
        }
        #roi-box {
            position: absolute;
            border: 2px dashed #ff4444;
            cursor: move;
            display: none;
            background-color: rgba(255,68,68,0.1);
        }
        .btn-group {
            margin-bottom: 10px;
        }
        button {
            padding: 8px 16px;
            margin-right: 10px;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <div class="btn-group">
        <input type="file" id="video-upload" accept="video/*">
        <button id="start-btn">开始分析</button>
        <button id="select-roi-btn">选择ROI</button>
        <button id="clear-roi-btn">清除ROI</button>
    </div>
    <div id="video-container">
        <img id="video-feed" src="" width="640" height="480">
        <div id="roi-box"></div>
    </div>

    <script>
        let videoPath = '';
        let roiCoords = null;
        let isSelectingROI = false;
        let startPos = {x:0, y:0};
        const container = document.getElementById('video-container');
        const roiBox = document.getElementById('roi-box');

        // 视频上传逻辑
        document.getElementById('video-upload').addEventListener('change', async (e) => {
            const file = e.target.files[0];
            const formData = new FormData();
            formData.append('video', file);
            const res = await fetch('/upload', {method: 'POST', body: formData});
            const data = await res.json();
            videoPath = data.video_path;
        });

        // 启动分析逻辑
        document.getElementById('start-btn').addEventListener('click', () => {
            if (!videoPath) {
                alert('请先上传视频');
                return;
            }
            let feedUrl = `/video_feed?video_path=${videoPath}`;
            if (roiCoords) {
                feedUrl += `&roi=${roiCoords.join(',')}`;
            }
            document.getElementById('video-feed').src = feedUrl;
        });

        // ROI选择逻辑
        document.getElementById('select-roi-btn').addEventListener('click', () => {
            isSelectingROI = !isSelectingROI;
            roiBox.style.display = isSelectingROI ? 'block' : 'none';
        });

        // 清除ROI
        document.getElementById('clear-roi-btn').addEventListener('click', () => {
            roiCoords = null;
            roiBox.style.display = 'none';
            isSelectingROI = false;
        });

        // 鼠标事件处理ROI框选
        container.addEventListener('mousedown', (e) => {
            if (!isSelectingROI) return;
            startPos.x = e.offsetX;
            startPos.y = e.offsetY;
            roiBox.style.left = `${startPos.x}px`;
            roiBox.style.top = `${startPos.y}px`;
            roiBox.style.width = '0px';
            roiBox.style.height = '0px';
        });

        container.addEventListener('mousemove', (e) => {
            if (!isSelectingROI) return;
            const endX = e.offsetX;
            const endY = e.offsetY;
            const width = Math.abs(endX - startPos.x);
            const height = Math.abs(endY - startPos.y);
            const left = Math.min(startPos.x, endX);
            const top = Math.min(startPos.y, endY);
            roiBox.style.left = `${left}px`;
            roiBox.style.top = `${top}px`;
            roiBox.style.width = `${width}px`;
            roiBox.style.height = `${height}px`;
        });

        container.addEventListener('mouseup', () => {
            if (!isSelectingROI) return;
            roiCoords = [
                Math.min(startPos.x, startPos.x + parseInt(roiBox.style.width)),
                Math.min(startPos.y, startPos.y + parseInt(roiBox.style.height)),
                Math.max(startPos.x, startPos.x + parseInt(roiBox.style.width)),
                Math.max(startPos.y, startPos.y + parseInt(roiBox.style.height))
            ];
        });
    </script>
</body>
</html>

四、CPU运行优化建议

由于你使用CPU运行,需降低计算负载避免卡顿:

  • 降低视频帧分辨率(代码中已设置640×480,可根据需求调整)
  • 减少检测频率:比如每2帧执行一次检测,跳过中间帧
  • 替换为轻量级模型:如YOLOv5n、YOLOv8n,比大模型速度提升数倍

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 21:00:34