【Python】采用OpenCV和Flask来进行网络图像推流的低延迟高刷FPS方法(项目模板)

【Python】采用OpenCV和Flask来进行网络图像推流的低延迟高刷FPS方法(项目模板)

gitee项目模板:
网络图像推流项目模板(采用OpenCV和Flask来进行网络图像推流的低延迟高刷FPS方法)

前文:
【最简改进】基于OpenCV-Python+Flask的人脸检测网络摄像头图像输出(将本地图像转为网络URL地址,可输出带识别框的图像)

文章目录

  • 高刷方式
  • 网络线程
  • 视频线程
  • 整体代码
  • 附录:列表的赋值类型和py打包
    • 列表赋值
      • BUG复现
      • 代码改进
      • 优化
      • 总结
    • py打包

高刷方式

首先 在前文中 我们用OpenCV获取的图像转为bytes类型 然后发送给flask端网页进行图像推流 但由于OpenCV和网络部分都会占用系统资源 所以FPS不高 亦或是延迟较高
尤其是在树莓派等系统资源不够多的系统上运行时 延时非常明显
另外 还可能进行人脸识别、手势识别等功能 所以延迟进一步提高
但采用多线程的方式 可以在以上满足所有条件的情况下 将延时控制在150ms内 切FPS能达到30
在这里插入图片描述
以上还是手机端浏览效果 服务端是由树莓派本身建立的
手机用的自带的浏览器 本身就有一定延迟

网络线程

网络线程其实就一个 就是flask的app.run函数

def start_server():web_app.run(host='0.0.0.0', port=local_post)thread_send = threading.Thread(target=start_server)
thread_send.setDaemon(True)
thread_send.start() 

而图像推流依靠以下函数:

def send_img():global cam_imgglobal pause_all_flagwhile pause_all_flag == 0:image = cv2.imencode('.jpg', cam_img)[1].tobytes()yield (b'--frame\r\n'b'Content-Type: image/jpeg\r\n\r\n' + image + b'\r\n')if pause_all_flag == 1:breakreturn @web_app.route('/video_feed')
def video_feed():return Response(send_img(), mimetype='multipart/x-mixed-replace; boundary=frame')

另外 表单提取和推送也很简单

@web_app.route('/', methods=['GET', 'POST'])
def index():  global kill_all_flagglobal pause_all_flagglobal command_strif request.method == 'POST':command_str = str(request.form.get('WEB_COMMAND'))if kill_all_flag == 0 and pause_all_flag == 0:command_jugg()elif kill_all_flag == 0 and pause_all_flag == 1:print(command_str)if command_str == "继续程序":pause_all_flag = 0print("Continue")else:print("程序已暂停,继续请按键")else:print("程序已终止,请重启程序")now_today = time.time()time_hour = time.localtime(now_today).tm_hourtime_min = time.localtime(now_today).tm_minif time_hour < 10:time_hour = "0"+str(time_hour)if time_min < 10:time_min = "0"+str(time_min)local_time_str = str(time.localtime(now_today).tm_year)+"-"+str(time.localtime(now_today).tm_mon)+"-"+str(time.localtime(now_today).tm_mday)+" "+str(time_hour)+":"+str(time_min)data = {'当前时间:': [ local_time_str]}return render_template('index.html',data_dict=data)

这三个部分其实是共用一个线程的

视频线程

视频线程的话 如果有检测、识别等部分 可以单独列一个线程 这里推荐使用最纯粹的视频获取线程作为一个单独的线程:
这里我是放在主线程里

def img_main():global pause_all_flagglobal kill_all_flagglobal cam_img while True:time.sleep(0.1)while pause_all_flag==0:cam_img = cv2.flip(cv2_cap.read()[1],1)cv2.imshow("http://"+local_ip+":"+str(local_post)+"/ (img: video_feed)",cam_img)# 展示图像            if pause_all_flag == 1:pause_all_flag = 1print("暂停程序")cv2.destroyAllWindows()breakif cv2.waitKey(10) == 27:   # 通过esc键退出摄像kill_all_flag = 1pause_all_flag = 1print("结束程序")cv2.destroyAllWindows()breakif kill_all_flag == 1:breakcv2_cap.release()print("全部退出")return 

几乎是感觉不到延迟的:
在这里插入图片描述

整体代码

import cv2
from flask import Flask, render_template, Response, request
import threading
import socket
import timeglobal pause_all_flag
pause_all_flag = 0
global kill_all_flag
kill_all_flag = 0global command_str
command_str = Nonelocal_post = 1212
while True:try:web_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)web_socket.connect(("8.8.8.8",80))local_ip = str(web_socket.getsockname()[0])web_socket.close()print("Network Enable")breakexcept:print("Network Error...")time.sleep(5)web_app = Flask(__name__)cv2_cap = cv2.VideoCapture(1)  # 开启摄像头global cam_img
ok, cam_img = cv2_cap.read()  # 读取摄像头图像
if ok is False:print('无法读取到摄像头1!')cv2_cap.release()cam_img=Nonecv2_cap = cv2.VideoCapture(0)  # 开启摄像头ok, cam_img = cv2_cap.read()  # 读取摄像头图像if ok is False:pause_all_flag = 1kill_all_flag = 1print('无法读取到摄像头0!')try:high=cam_img.shape[0]width=cam_img.shape[1]
except:pause_all_flag = 1kill_all_flag = 1def command_jugg():global kill_all_flagglobal pause_all_flagglobal command_strprint(command_str)command_str = None@web_app.route('/', methods=['GET', 'POST'])
def index():  global kill_all_flagglobal pause_all_flagglobal command_strif request.method == 'POST':command_str = str(request.form.get('WEB_COMMAND'))if kill_all_flag == 0 and pause_all_flag == 0:command_jugg()elif kill_all_flag == 0 and pause_all_flag == 1:print(command_str)if command_str == "继续程序":pause_all_flag = 0print("Continue")else:print("程序已暂停,继续请按键")else:print("程序已终止,请重启程序")now_today = time.time()time_hour = time.localtime(now_today).tm_hourtime_min = time.localtime(now_today).tm_minif time_hour < 10:time_hour = "0"+str(time_hour)if time_min < 10:time_min = "0"+str(time_min)local_time_str = str(time.localtime(now_today).tm_year)+"-"+str(time.localtime(now_today).tm_mon)+"-"+str(time.localtime(now_today).tm_mday)+" "+str(time_hour)+":"+str(time_min)data = {'当前时间:': [ local_time_str]}return render_template('index.html',data_dict=data)def img_main():global pause_all_flagglobal kill_all_flagglobal cam_img while True:time.sleep(0.1)while pause_all_flag==0:cam_img = cv2.flip(cv2_cap.read()[1],1)cv2.imshow("http://"+local_ip+":"+str(local_post)+"/ (img: video_feed)",cam_img)# 展示图像            if pause_all_flag == 1:pause_all_flag = 1print("暂停程序")cv2.destroyAllWindows()breakif cv2.waitKey(10) == 27:   # 通过esc键退出摄像kill_all_flag = 1pause_all_flag = 1print("结束程序")cv2.destroyAllWindows()breakif kill_all_flag == 1:breakcv2_cap.release()print("全部退出")return def send_img():global cam_imgglobal pause_all_flagwhile pause_all_flag == 0:image = cv2.imencode('.jpg', cam_img)[1].tobytes()yield (b'--frame\r\n'b'Content-Type: image/jpeg\r\n\r\n' + image + b'\r\n')if pause_all_flag == 1:breakreturn @web_app.route('/video_feed')
def video_feed():return Response(send_img(), mimetype='multipart/x-mixed-replace; boundary=frame')def start_server():web_app.run(host='0.0.0.0', port=local_post)def main():thread_send = threading.Thread(target=start_server)thread_send.setDaemon(True)thread_send.start() img_main()time.sleep(1)print("已退出所有程序")return if __name__ == "__main__":    main()
<html><!--meta http-equiv="refresh" content="5"--> <head><title>OpenCV网络控制系统</title></head>    <body><h1>OpenCV网络控制系统</h1><form action="/" method="post" style="float:left"><p><input type="submit" style="font-size:100px" name="WEB_COMMAND" value="刷新网页"></p>	<p><table>{% for k,v in data_dict.items() %}<tr><td>{{k}}</td><td>{{v[0]}}</td><td>{{v[1]}}</td><td>{{v[2]}}</td><td>{{v[3]}}</td><td>{{v[4]}}</td></tr>{% endfor %}</table>   </p> </form>        <img src="{{ url_for('video_feed') }}" height="540" style="float:left"> </body>
</html>

附录:列表的赋值类型和py打包

列表赋值

BUG复现

闲来无事写了个小程序 代码如下:

# -*- coding: utf-8 -*-
"""
Created on Fri Nov 19 19:47:01 2021@author: 16016
"""a_list = ['0','1','2','3','4','5','6','7','8','9','10','11','12','13','14','15']
#print(len(a_list))
#b_list = ['','','','','','','','','','','','','','','','']
c_list = [[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]]
#for i in range(16):
if len(a_list):for j in range(16):a_list[j]=str(a_list[j])+'_'+str(j)print("序号:",j)print('a_list:\n',a_list)c_list[j]=a_listprint('c_list[0]:\n',c_list[0])print('\n')
#        b_list[j]=a_list[7],a_list[8]
#        print(b_list[j])# 写入到Excel:
#print(c_list,'\n')    

我在程序中 做了一个16次的for循环 把列表a的每个值后面依次加上"_"和循环序号
比如循环第x次 就是把第x位加上_x 这一位变成x_x 我在输出测试中 列表a的每一次输出也是对的
循环16次后列表a应该变成[‘0_0’, ‘1_1’, ‘2_2’, ‘3_3’, ‘4_4’, ‘5_5’, ‘6_6’, ‘7_7’, ‘8_8’, ‘9_9’, ‘10_10’, ‘11_11’, ‘12_12’, ‘13_13’, ‘14_14’, ‘15_15’] 这也是对的

同时 我将每一次循环时列表a的值 写入到空列表c中 比如第x次循环 就是把更改以后的列表a的值 写入到列表c的第x位
第0次循环后 c[0]的值应该是[‘0_0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’, ‘10’, ‘11’, ‘12’, ‘13’, ‘14’, ‘15’] 这也是对的
但是在第1次循环以后 c[0]的值就一直在变 变成了c[x]的值
相当于把c_list[0]变成了c_list[1]…以此类推 最后得出的列表c的值也是每一项完全一样
我不明白这是怎么回事
我的c[0]只在第0次循环时被赋值了 但是后面它的值跟着在改变

如图:
在这里插入图片描述
第一次老出bug 赋值以后 每次循环都改变c[0]的值 搞了半天都没搞出来
无论是用appen函数添加 还是用二维数组定义 或者增加第三个空数组来过渡 都无法解决

代码改进

后来在我华科同学的指导下 突然想到赋值可以赋的是个地址 地址里面的值一直变化 导致赋值也一直变化 于是用第二张图的循环套循环深度复制实现了

代码如下:

# -*- coding: utf-8 -*-
"""
Created on Fri Nov 19 19:47:01 2021@author: 16016
"""a_list = ['0','1','2','3','4','5','6','7','8','9','10','11','12','13','14','15']
#print(len(a_list))
#b_list = ['','','','','','','','','','','','','','','','']
c_list = [[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]]
#for i in range(16):
if len(a_list):for j in range(16):a_list[j]=str(a_list[j])+'_'+str(j)print("序号:",j)print('a_list:\n',a_list)for i in range(16):c_list[j].append(a_list[i])print('c_list[0]:\n',c_list[0])print('\n')
#        b_list[j]=a_list[7],a_list[8]
#        print(b_list[j])# 写入到Excel:
print(c_list,'\n')    

解决了问题

在这里插入图片描述

优化

第三次是请教了老师 用copy函数来赋真值

代码如下:

# -*- coding: utf-8 -*-
"""
Created on Fri Nov 19 19:47:01 2021@author: 16016
"""a_list = ['0','1','2','3','4','5','6','7','8','9','10','11','12','13','14','15']
#print(len(a_list))
#b_list = ['','','','','','','','','','','','','','','','']
c_list = [[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]]
#for i in range(16):
if len(a_list):for j in range(16):a_list[j]=str(a_list[j])+'_'+str(j)print("序号:",j)print('a_list:\n',a_list)c_list[j]=a_list.copy()print('c_list[0]:\n',c_list[0])print('\n')
#        b_list[j]=a_list[7],a_list[8]
#        print(b_list[j])# 写入到Excel:
#print(c_list,'\n')    

同样能解决问题
在这里插入图片描述
最后得出问题 就是指针惹的祸!

a_list指向的是个地址 而不是值 a_list[i]指向的才是单个的值 copy()函数也是复制值而不是地址

如果这个用C语言来写 就直观一些了 难怪C语言是基础 光学Python不学C 遇到这样的问题就解决不了

C语言yyds Python是什么垃圾弱智语言

总结

由于Python无法单独定义一个值为指针或者独立的值 所以只能用列表来传送
只要赋值是指向一个列表整体的 那么就是指向的一个指针内存地址 解决方法只有一个 那就是将每个值深度复制赋值(子列表内的元素提取出来重新依次连接) 或者用copy函数单独赋值

如图测试:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
部分代码:

# -*- coding: utf-8 -*-
"""
Created on Sat Nov 20 16:45:48 2021@author: 16016
"""def text1():A=[1,2,3]B=[[],[],[]]for i in range(len(A)):A[i]=A[i]+iB[i]=Aprint(B)def text2():A=[1,2,3]B=[[],[],[]]A[0]=A[0]+0B[0]=Aprint(B)A[1]=A[1]+1B[1]=Aprint(B)A[2]=A[2]+2B[2]=Aprint(B)if __name__ == '__main__':text1()print('\n')text2()

py打包

Pyinstaller打包exe(包括打包资源文件 绝不出错版)

依赖包及其对应的版本号

PyQt5 5.10.1
PyQt5-Qt5 5.15.2
PyQt5-sip 12.9.0

pyinstaller 4.5.1
pyinstaller-hooks-contrib 2021.3

Pyinstaller -F setup.py 打包exe

Pyinstaller -F -w setup.py 不带控制台的打包

Pyinstaller -F -i xx.ico setup.py 打包指定exe图标打包

打包exe参数说明:

-F:打包后只生成单个exe格式文件;

-D:默认选项,创建一个目录,包含exe文件以及大量依赖文件;

-c:默认选项,使用控制台(就是类似cmd的黑框);

-w:不使用控制台;

-p:添加搜索路径,让其找到对应的库;

-i:改变生成程序的icon图标。

如果要打包资源文件
则需要对代码中的路径进行转换处理
另外要注意的是 如果要打包资源文件 则py程序里面的路径要从./xxx/yy换成xxx/yy 并且进行路径转换
但如果不打包资源文件的话 最好路径还是用作./xxx/yy 并且不进行路径转换

def get_resource_path(relative_path):if hasattr(sys, '_MEIPASS'):return os.path.join(sys._MEIPASS, relative_path)return os.path.join(os.path.abspath("."), relative_path)

而后再spec文件中的datas部分加入目录
如:

a = Analysis(['cxk.py'],pathex=['D:\\Python Test\\cxk'],binaries=[],datas=[('root','root')],hiddenimports=[],hookspath=[],hooksconfig={},runtime_hooks=[],excludes=[],win_no_prefer_redirects=False,win_private_assemblies=False,cipher=block_cipher,noarchive=False)

而后直接Pyinstaller -F setup.spec即可

如果打包的文件过大则更改spec文件中的excludes 把不需要的库写进去(但是已经在环境中安装了的)就行

这些不要了的库在上一次编译时的shell里面输出
比如:
在这里插入图片描述

在这里插入图片描述
然后用pyinstaller --clean -F 某某.spec

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/654361.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

伊恩·斯图尔特《改变世界的17个方程》相对论笔记

它告诉我们什么&#xff1f; 物质包含的能量等于其质量乘以光速的平方。 为什么重要&#xff1f; 光的速度很快&#xff0c;它的平方绝对是一个巨大的数。1千克的物质释放出的能量相当于史上最大的核武器爆炸所释放能量的约40%。一系列相关的方程改变了我们对空间、时间、物质和…

Prompt Learning 的几个重点paper

Prefix Tuning: Prefix-Tuning: Optimizing Continuous Prompts for Generation 在输入token之前构造一段任务相关的virtual tokens作为Prefix&#xff0c;然后训练的时候只更新Prefix部分的参数&#xff0c;PLM中的其他参数固定。针对自回归架构模型&#xff1a;在句子前面添…

vue 使用echarts-gl实现3d旋转地图

之前也有使用过echarts开发项目中涉及到的地图功能&#xff0c;当时使用geo来实现地图轮廓&#xff0c;看上去有种3d的感觉。最近闲来无事看了一份可视化大屏的UI设计图&#xff0c;感觉3d旋转地图挺好玩的&#xff0c;今天就来尝试实现下。 首先安装下echarts和echarts-gl依赖…

MyBatis框架-配置解析

文章目录 Mybatis配置解析核心配置文件environments 环境配置transactionManager 事务管理器dataSource 数据源mappers 映射器Mapper文件Properties优化类型别名&#xff08;typeAliases&#xff09;setting类型处理器&#xff08;typeHandlers&#xff09;对象工厂&#xff08…

shell脚本——条件语句

目录 一、条件语句 1、test命令测试条件表达式 2、整数数值比较 3、字符串比较 4、逻辑测试&#xff08;短路运算&#xff09; 5、双中括号 二、if语句 1、 分支结构 1.1 单分支结果 1.2 双分支 1.3 多分支 2、case 一、条件语句 条件测试&#xff1a;判断某需求是…

1002. HarmonyOS 开发问题:鸿蒙 OS 技术特性是什么?

1002. HarmonyOS 开发问题&#xff1a;鸿蒙 OS 技术特性是什么? 硬件互助&#xff0c;资源共享 分布式软总线 分布式软总线是多种终端设备的统一基座&#xff0c;为设备之间的互联互通提供了统一的分布式通信能力&#xff0c;能够快速发现并连接设备&#xff0c;高效地分发…

计算机网络——网络层(2)

计算机网络——网络层&#xff08;2&#xff09; 小程一言专栏链接: [link](http://t.csdnimg.cn/ZUTXU) 网络层——控制平面概述路由选择转发表路由协议路由信息的交换小结 路由选择算法常见的路由选择算法距离矢量路由算法工作原理优缺点分析 链路状态路由算法基本工作原理优…

【详解】贪吃蛇游戏----下篇(完整源码)

目录 引入&#xff1a; 本片文章目的&#xff1a; 整个游戏的实现流程图如下&#xff1a; 游戏实现 GameRun PrintHelpInfo Pause NextIsFood printSnake EatFood NoFood KillByWall KillBySelf GameRun GameEnd 总代码&#xff1a; &#xff08;1&#xff09…

pcie基础知识

文章目录 总线PCIEPCIE对应版本速率pcie拓扑linux查看pcie设备PCIE配置空间BAR&#xff08;基地址寄存器&#xff09; 总线 什么是总线 总线就是电脑内部交互的通道。 最开始CPU连接声卡或者网卡用的是不同接口&#xff0c;比如你声卡坏了&#xff0c;换一个声卡&#xff0c;接…

二叉搜索树操作题目:二叉搜索树中的插入操作

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;二叉搜索树中的插入操作 出处&#xff1a;701. 二叉搜索树中的插入操作 难度 3 级 题目描述 要求 给定二叉搜索…

C# 一个快速读取写入操作execl的方法封装

这里封装了3个实用类ExcelDataReaderExtensions&#xff0c;ExcelDataSetConfiguration&#xff0c;ExcelDataTableConfiguration和一个实用代码参考&#xff1a; using ExcelDataReader; using System; using System.Collections.Generic; using System.Linq; using System.T…

别再做“背锅侠”!软件测试工程师被开发吐槽,如何应对?

作为一名软件测试工程师&#xff0c;我们的角色可以算是“战场上的后勤”&#xff0c;战役的胜败和所有团队人员都息息相关。但是难免碰到战役失败后&#xff0c;很多团队互相推脱的局面&#xff0c;而测试人员就是所有团队中的弱势群体&#xff0c;自然是首当其冲的背锅侠&…

扫雷游戏(C语言)

目录 一、前言&#xff1a; 二、游戏规则&#xff1a; 三、游戏前准备 四、游戏实现 1、打印菜单 2、初始化棋盘 3、打印棋盘 4、布置雷 5、排雷 五、完整代码 一、前言&#xff1a; 用C语言完成扫雷游戏对于初学者来说&#xff0c;难度并不是很大&#xff0c;而且通…

一份轴承振动数据集摘引 - XJTU-SY2019

1.原始引用 我第一次看到这个数据集是在知乎&#xff1a; XJTU-SY数据集轴承故障诊断 - 知乎XJTU-SY数据集包含了3种工况下的15个滚动轴承的全寿命周期振动信号&#xff0c;且明确标注了每个轴承的失效部位&#xff0c;相关论文如下&#xff1a;[1]雷亚国,韩天宇,王彪,李乃鹏…

DS:经典算法OJ题(1)

创作不易&#xff0c;友友们给个三连呗&#xff01;&#xff01; 本文为经典算法OJ题练习&#xff0c;大部分题型都有多种思路&#xff0c;每种思路的解法博主都试过了&#xff08;去网站那里验证&#xff09;是正确的&#xff0c;大家可以参考&#xff01;&#xff01; 一、移…

常用芯片学习——LM2596芯片

LM2596 3A降压型稳压器 使用说明 LM2596开关电压调节器是降压型电源管理单片集成电路&#xff0c;能够输出最大3A的驱动电流&#xff0c;同时具有很好的线性和负载调节特性。芯片按照输出版本可分为四种&#xff0c;分别是3.3V、5V、12V、ADJ&#xff08;可调版本&#xff09…

一文读懂Python中的映射

python中的反射功能是由以下四个内置函数提供&#xff1a;hasattr、getattr、setattr、delattr&#xff0c;改四个函数分别用于对对象内部执行&#xff1a;检查是否含有某成员、获取成员、设置成员、删除成员。 获取成员: getattr class Foo:def __init__(self, name, age):se…

【command】使用nr简化npm run命令

参考文章 添加 alias nrnpm run通过alias启动命令可以帮助我们节省运行项目输入命令的时间 $ cd ~ $ vim .bash_profile $ source ~/.bashrc

应急响应-流量分析

在应急响应中&#xff0c;有时需要用到流量分析工具&#xff0c;。当需要看到内部流量的具体情况时&#xff0c;就需要我们对网络通信进行抓包&#xff0c;并对数据包进行过滤分析&#xff0c;最常用的工具是Wireshark。 Wireshark是一个网络封包分析软件。网络封包分析软件的…

isctf---crypto

夹里夹气 可以发现是摩斯密码 得到flag easy_rsa nc连接 rsa_d nc连接 计算d 七七的欧拉 task import gmpy2 import libnum from crypto.Util.number import *flagbISCTF{*************} mbytes_to_long(flag)plibnum.generate_prime(1024) elibnum.generate_prime(51…