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

大气层煮月亮 2024-08-11 11:03:02 阅读 53

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

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

Flask后端代码

<code>'''

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')code>

# 处理函数

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前端代码 

<!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">code>

<head>

<link rel="stylesheet" type="text/css" href="/static/css/cljc.css">code>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />code>

<meta name="viewport" content="width=device-width, initial-scale=1.0">code>

<meta http-equiv="X-UA-Compatible" content="IE=edge">code>

<title>车流监测</title>

<style type="text/css">code>

body {

margin:0;

padding:0;

font-size:12px;

}

</style>

</head>

<body>

<div class="header">code>

<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>code>

</div>

<div class="colmask threecol">code>

<div class="colmid">code>

<div class="colleft">code>

<div class="col1">code>

<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">code>

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

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

<video allowfullscreen="allowfullscreen" class="c4195" src="/static/img/video2.webm" controls="controls"></video>code>

</div> -->

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

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

</div>

</div>

</div>

<div class="col2"> code>

<div class="BoxConTop">当前帧数</div>code>

<input type="text" id="frameNumber" name="frameNumber" readonly style="margin-top: -100px;">code>

<div class="BoxConTop">当前车辆数</div>code>

<input type="text" id="vehicleNumber" name="vehicleNumber" readonly style="margin-top: -100px;">code>

<div class="BoxConTop">车流量峰值</div>code>

<input type="text" id="peakTraffic" name="peakTraffic" readonly style="margin-top: -100px;">code>

</div>

<div class="col3">code>

<p>北京时间</p>

<input type="text" id="timeShow" name="timeWarning" readonly>code>

<p>安全预警栏</p>

<input type="text" id="safetyWarning" name="safetyWarning" readonly>code>

</div>

</div>

</div>

</div>

</div>

<div class="footer">code>

<form id="upload-form">code>

<input type="file" id="video" name="video" accept="video/*">code>

<button type="submit">上传视频</button>code>

</form>

</div>

<script src="https://cdn.socket.io/4.0.1/socket.io.min.js"></script>code>

<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)中打开,这样看起来比较清晰。 

video_path = './static/Videos/视频1.mp4'

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

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

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

二、前后端代码结合讲解

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

@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

<div class="footer">code>

<form id="upload-form">code>

<input type="file" id="video" name="video" accept="video/*">code>

<button type="submit">上传视频</button>code>

</form>

</div>

↓ 

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

@app.route('/video_feed')

def video_feed():

return Response(generate_frames(),

mimetype='multipart/x-mixed-replace; boundary=frame')code>

# 处理函数

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的格式并以字节流的方式返回。

# !!!!将每一帧转换为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')

yield (b'--frame\r\n'b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')

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

<div class="htmlpage-row">code>

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

</div>

前端HTML:“接收来自后端路由video_feed()函数传过来的参数。”后端FLASK:“yield()通过video_feed()函数发送数据给前端。”



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。