Flask如何在后端实时处理视频帧在前端展示

怎么样在前端->选择视频文件->点击上传视频后->后端实时分析上传的视频->在前端展示后端分析结果(视频,文本)

咱们先看整看整体代码,有个大概的印象。

Flask后端代码

python 复制代码
'''
cljc车流检测Demo
'''
from pytz import timezone
from datetime import datetime

# 用于上传保存视频
@app.route('/upload', methods=['POST'])
def upload_video():
    VIDEOS_FOLDER = 'static\Videos'
    video = request.files['video']
    video.save(os.path.join(VIDEOS_FOLDER, '视频1.mp4'))
    return jsonify({'message': 'Video uploaded and saved successfully'}), 200

# 用于把后端处理的视频帧数据传输到前端展示
@app.route('/video_feed')
def video_feed():
    return Response(generate_frames(),
                    mimetype='multipart/x-mixed-replace; boundary=frame')

# 处理函数
def generate_frames():
    frame_Num = 1                                   
    max_cars = 0
    video_path = './static/Videos/视频1.mp4' # 必须固定一个位置,用于存放上传的视频文件
    cap = cv2.VideoCapture(video_path)
    fps = cap.get(cv2.CAP_PROP_FPS)              
    carsCascade = cv2.CascadeClassifier("car_rear.xml")  
    while cap.isOpened():
        success, frame = cap.read()
        if not success:
            break   
        cv2.putText(frame,"second:"+str(round(frame_Num/fps,2))+"s",
                        (0,100),cv2.FONT_HERSHEY_SIMPLEX,1,(255,0,255),1)
        cars = carsCascade.detectMultiScale(frame[700:1280,0:720], 1.2,minSize=(10,10))   
        for (x, y, w, h) in cars:  # 遍历所有汽车的区域
            cv2.rectangle(frame[700:1280,0:720], (x, y), (x + w, y + h), (0, 0, 255), 2)  

        # !!!!将每一帧转换为JPEG格式并以字节流方式返回,!!!!!
        ret, buffer = cv2.imencode('.jpg', frame)
        frame = buffer.tobytes()
        frame_Num+=1
        yield (b'--frame\r\n'b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')

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

HTML前端代码

html 复制代码
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
	<link rel="stylesheet" type="text/css" href="/static/css/cljc.css">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">

<title>车流监测</title>
<style type="text/css">
body {
margin:0;
padding:0;
font-size:12px;
}

</style>
</head>
<body>
<div class="header">
    <div><style>* { box-sizing: border-box; } body {margin: 0;}
.c14101{padding:10px;
text-align:center;
font-weight:600;
color:#ffffff;
font-family:"Microsoft Yahei", sans-serif;
background-image:url(/static/img/bg3.jpg);
background-repeat:repeat;
background-position:left top;
background-attachment:scroll;
background-size:auto;}</style>
<div id="ib1w" class="c14101">车流监测</div></div>
</div>
<div class="colmask threecol">
<div class="colmid">
<div class="colleft">
<div class="col1">
<div>
    <style>* {
 box-sizing: border-box; 
} body {margin: 0;}
.c14101{padding:10px;
text-align:center;
font-weight:600;
color:#ffffff;
font-family:"Microsoft Yahei", sans-serif;
background-image:url(/static/img/bg1.jpg);
background-repeat:repeat;
background-position:left top;background-attachment:scroll;
background-size:auto;}
.htmlpage-row{display:table;
padding-top:10px;
padding-right:10px;
padding-bottom:10px;
padding-left:10px;
width:100%;}
.htmlpage-cell{
width:8%;
display:table-cell;height:75px;}
.c4195{height:400px;width:950px;
text-align:center;
position:relative;}
@media (max-width: 768px){
.htmlpage-cell{width:100%;display:block;}}

.footer {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 50; /* 设置合适的高度 */
}

#upload-form {
    display: flex;
    flex-direction: column;
    /* align-items: center; */
}

</style>

<div class="htmlpage-row">
    <!-- <div class="htmlpage-cell"></div> -->
    <!-- <div class="htmlpage-cell">
        <video allowfullscreen="allowfullscreen" class="c4195" src="/static/img/video2.webm" controls="controls"></video>
    </div> -->
    <img src="{{ url_for('video_feed') }}" width="600px" height=590px">

    <!-- <div class="htmlpage-cell"></div> -->
</div>

</div>
</div>

<div class="col2"> 
    <div class="BoxConTop">当前帧数</div>
    <input type="text" id="frameNumber" name="frameNumber" readonly style="margin-top: -100px;">
    <div class="BoxConTop">当前车辆数</div>
    <input type="text" id="vehicleNumber" name="vehicleNumber" readonly style="margin-top: -100px;">
    <div class="BoxConTop">车流量峰值</div>
    <input type="text" id="peakTraffic" name="peakTraffic" readonly style="margin-top: -100px;">
</div>

<div class="col3">
    <p>北京时间</p>
    <input type="text" id="timeShow" name="timeWarning" readonly>
    <p>安全预警栏</p>
    <input type="text" id="safetyWarning" name="safetyWarning" readonly>
</div>

</div>
</div>
</div>
</div>
<div class="footer">
    <form id="upload-form">
        <input type="file" id="video" name="video" accept="video/*">
        <button type="submit">上传视频</button>
    </form>
</div>
<script src="https://cdn.socket.io/4.0.1/socket.io.min.js"></script>
<script>
    var socket = io();

    socket.on('update_data', function(data) {
        document.getElementById('frameNumber').value = data.frame_Num;
        document.getElementById('vehicleNumber').value = data.car_number;
        document.getElementById('peakTraffic').value = data.max_cars;
        document.getElementById('timeShow').value = data.timeShow;
        document.getElementById('safetyWarning').value = data.safetyWarning;
    });
</script>
</body>
<script>
    document.getElementById('upload-form').addEventListener('submit', async (e) => {
        e.preventDefault();
        const formData = new FormData(e.target);
        const response = await fetch('/upload', { method: 'POST', body: formData });
        const data = await response.json();
        console.log(data.message);
    });
    document.getElementById('play-btn').addEventListener('click', () => {
        const videoFrame = document.getElementById('video-frame');
        const source = new EventSource('/play');
        source.onmessage = (event) => {
            videoFrame.src = event.data;
        };
    });
</script>
</html>

代码可以创建一个.py文件,在编译软件(vscode,pycharm)中打开,这样看起来比较清晰。

python 复制代码
video_path = './static/Videos/视频1.mp4' 

必须固定一个位置,这个位置是upload路由从前端传输过来视频数据,保存的地址,上面是处理函数**generate_frames()**里面读取的步骤。

下面是保存的步骤,在**upload_video()**路由:

python 复制代码
 video.save(os.path.join(VIDEOS_FOLDER, '视频1.mp4')) #固定保存视频的地方,

二、前后端代码结合讲解

实现上传视频并保存在指定路径的功能!

python 复制代码
@app.route('/upload', methods=['POST'])
def upload_video():
    VIDEOS_FOLDER = 'static\Videos'
    video = request.files['video']
    video.save(os.path.join(VIDEOS_FOLDER, '视频1.mp4')) #固定保存视频的地方,
    return jsonify({'message': 'Video uploaded and saved successfully'}), 200
html 复制代码
<div class="footer">
    <form id="upload-form">
        <input type="file" id="video" name="video" accept="video/*">
        <button type="submit">上传视频</button>
    </form>
</div>

传输在后端处理好的视频帧数据到前端展示

python 复制代码
@app.route('/video_feed')
def video_feed():
    return Response(generate_frames(),
                    mimetype='multipart/x-mixed-replace; boundary=frame')
# 处理函数
def generate_frames():
    frame_Num = 1                                   
    max_cars = 0
    video_path = './static/Videos/视频1.mp4' # 必须固定一个位置,用于存放上传的视频文件
    cap = cv2.VideoCapture(video_path)
    fps = cap.get(cv2.CAP_PROP_FPS)              
    carsCascade = cv2.CascadeClassifier("car_rear.xml")  
    while cap.isOpened():
        success, frame = cap.read()
        if not success:
            break   
        cv2.putText(frame,"second:"+str(round(frame_Num/fps,2))+"s",
                        (0,100),cv2.FONT_HERSHEY_SIMPLEX,1,(255,0,255),1)
        cars = carsCascade.detectMultiScale(frame[700:1280,0:720], 1.2,minSize=(10,10))   
        for (x, y, w, h) in cars:  # 遍历所有汽车的区域
            cv2.rectangle(frame[700:1280,0:720], (x, y), (x + w, y + h), (0, 0, 255), 2)  

        # !!!!将每一帧转换为JPEG格式并以字节流方式返回,!!!!!
        ret, buffer = cv2.imencode('.jpg', frame)
        frame = buffer.tobytes()
        frame_Num+=1
        yield (b'--frame\r\n'b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')

这是最关键的,其余都是处理函数。最后这一步将每一帧数据转换为了JPEG的格式并以字节流的方式返回。

python 复制代码
        # !!!!将每一帧转换为JPEG格式并以字节流方式返回,!!!!!
        # 1.转换为JPEG格式
        ret, buffer = cv2.imencode('.jpg', frame)
        # 2.转换为字节流
        frame = buffer.tobytes()
        # 3.通过路由函数发送给前端
        yield (b'--frame\r\n'b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
python 复制代码
yield (b'--frame\r\n'b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')

通过上面这个方法yield(),把转换好的图像以字节流的方式传送给前端,那么前端如何接收到呢?这很简单,

html 复制代码
<div class="htmlpage-row">

    <img src="{{ url_for('video_feed') }}" width="600px" height=590px">

</div>
  • 前端HTML:"接收来自后端路由video_feed()函数传过来的参数。"
  • 后端FLASK:"yield()通过video_feed()函数发送数据给前端。"
相关推荐
腾讯TNTWeb前端团队6 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰9 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪9 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪9 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy10 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom10 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom11 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom11 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom11 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom11 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试