OpenCV图像处理——Python开发中OpenCV视频流的多线程处理方式
前言
在做视觉类项目中,常常需要在Python环境下使用OpenCV读取本地的还是网络摄像头的视频流,之后再调入各种模型,如目标分类、目标检测,人脸识别等等。如果使用单线程处理,很多时候会出现比较严重的时延,如果算力吃紧,模型推理所占用的更长的话,这种延迟感会更加明显,会出现卡帧的现象。在这种情况下,往往要把代码从单线程改为了多线程,即单独用一个线程实时捕获视频帧,主线程在需要时从子线程拷贝最近的帧使用即可。
单线程处理视频流时,如果目标检测模型较大或者任务复杂,会影响处理速度。而使用多线程,让视频捕获和目标检测分别在各自的线程中运行,能够更充分地利用 CPU 的多核心处理能力,提高整体的处理效率和实时性。
在实时视频处理中,特别是涉及到深度学习模型推理这种计算密集型的任务时,多线程确实能够带来显著的性能提升。通过将视频捕获和处理分开,可以避免由于处理时间过长而导致的帧丢失或延迟。
一、多线程
在 Python 中,可以使用 threading 模块来实现多线程。下面是一个简单的例子,演示了如何在 Python 中创建和使用多线程:
1、导入 threading 模块
首先导入 Python 的 threading
模块,它提供了多线程编程所需的功能。
import threading
2、创建线程执行函数
定义一个函数,作为线程的执行体。这个函数将会在每个线程中运行。在函数内编写希望线程执行的代码逻辑。
def my_function():# Your code herepass
3、创建线程对象
使用 threading.Thread()
创建一个线程对象,将目标函数指定为刚才定义的函数,并传入所需参数。
my_thread = threading.Thread(target=my_function, args=(arg1, arg2)) # 传入参数 args
4、动线程
使用 start()
方法启动线程。
my_thread.start()
5、等待线程执行完成
使用 join()
方法等待线程执行完毕。这会让主线程等待子线程的结束。
my_thread.join()
6、示例
以下是一个简单的示例,演示了如何使用多线程:
import threading
import time# 线程执行体函数
def print_numbers():for i in range(5):print(f"Child Thread: {i}")time.sleep(1)# 创建线程对象
thread = threading.Thread(target=print_numbers)# 启动线程
thread.start()# 主线程中的其他操作
for i in range(5):print(f"Main Thread: {i}")time.sleep(0.5)# 等待子线程执行结束
thread.join()print("Main Thread exiting...")
print_numbers()
函数是子线程的执行体,在子线程中打印数字。主线程在启动子线程后,会同时进行自己的任务。最后通过 join()
方法等待子线程结束。
二、视频处理
一般视频处理代码分为两部分:从相机读取下一个可用帧以及对帧进行图像处理,例如把图像送到Yolov5目标检测模型进行检测。
在没有多线程的程序中,按顺序读取下一帧并进行处理。程序等待下一帧可用,然后对其进行必要的处理。读取帧所需的时间主要取决于请求、等待下一个视频帧,并将其从相机传输到内存的时间。无论是在 CPU 还是 GPU 上,对视频帧进行计算所需的时间都占据了视频处理所花费的大部分时间。
然而,在具有多线程的程序中,读取下一帧并对其进行处理不需要按顺序进行。当一个线程执行读取下一帧的任务时,主线程可以使用 CPU 或 GPU 来处理最后一个读取的帧。通过这种方式,这两个任务可以重叠执行,从而减少读取和处理帧的总时间。
1.视频单线程处理
# importing required libraries
import cv2
import time# opening video capture stream
vcap = cv2.VideoCapture(0)
if vcap.isOpened() is False :print("[Exiting]: Error accessing webcam stream.")exit(0)
fps_input_stream = int(vcap.get(5))
print("FPS of webcam hardware/input stream: {}".format(fps_input_stream))
grabbed, frame = vcap.read() # reading single frame for initialization/ hardware warm-up# processing frames in input stream
num_frames_processed = 0
start = time.time()
while True :grabbed, frame = vcap.read()if grabbed is False :print('[Exiting] No more frames to read')break# adding a delay for simulating time taken for processing a frame delay = 0.03 # delay value in seconds. so, delay=1 is equivalent to 1 second time.sleep(delay) num_frames_processed += 1cv2.imshow('frame' , frame)key = cv2.waitKey(1)if key == ord('q'):break
end = time.time()# printing time elapsed and fps
elapsed = end-start
fps = num_frames_processed/elapsed
print("FPS: {} , Elapsed Time: {} , Frames Processed: {}".format(fps, elapsed, num_frames_processed))# releasing input stream , closing all windows
vcap.release()
cv2.destroyAllWindows()
2.视频多线程处理
# importing required libraries
import cv2
import time
from threading import Thread # library for implementing multi-threaded processing# defining a helper class for implementing multi-threaded processing
class WebcamStream :def __init__(self, stream_id=0):self.stream_id = stream_id # default is 0 for primary camera # opening video capture stream self.vcap = cv2.VideoCapture(self.stream_id)if self.vcap.isOpened() is False :print("[Exiting]: Error accessing webcam stream.")exit(0)fps_input_stream = int(self.vcap.get(5))print("FPS of webcam hardware/input stream: {}".format(fps_input_stream))# reading a single frame from vcap stream for initializing self.grabbed , self.frame = self.vcap.read()if self.grabbed is False :print('[Exiting] No more frames to read')exit(0)# self.stopped is set to False when frames are being read from self.vcap stream self.stopped = True# reference to the thread for reading next available frame from input stream self.t = Thread(target=self.update, args=())self.t.daemon = True # daemon threads keep running in the background while the program is executing # method for starting the thread for grabbing next available frame in input stream def start(self):self.stopped = Falseself.t.start()# method for reading next frame def update(self):while True :if self.stopped is True :breakself.grabbed , self.frame = self.vcap.read()if self.grabbed is False :print('[Exiting] No more frames to read')self.stopped = Truebreak self.vcap.release()# method for returning latest read frame def read(self):return self.frame# method called to stop reading frames def stop(self):self.stopped = True# initializing and starting multi-threaded webcam capture input stream
webcam_stream = WebcamStream(stream_id=0) # stream_id = 0 is for primary camera
webcam_stream.start()# processing frames in input stream
num_frames_processed = 0
start = time.time()
while True :if webcam_stream.stopped is True :breakelse :frame = webcam_stream.read()# adding a delay for simulating time taken for processing a frame delay = 0.03 # delay value in seconds. so, delay=1 is equivalent to 1 second time.sleep(delay) num_frames_processed += 1cv2.imshow('frame' , frame)key = cv2.waitKey(1)if key == ord('q'):break
end = time.time()
webcam_stream.stop() # stop the webcam stream# printing time elapsed and fps
elapsed = end-start
fps = num_frames_processed/elapsed
print("FPS: {} , Elapsed Time: {} , Frames Processed: {}".format(fps, elapsed, num_frames_processed))# closing all windows
cv2.destroyAllWindows()
上面的代码创建了一个 WebcamStream
类,其中包含了多线程读取相机帧的逻辑。在主循环中,它仍然以顺序方式处理每个帧,但是读取帧的线程是在后台运行的。
但上面的代码提升速度的同时,还有以下有改进的问题:
-
处理多个帧的逻辑: 代码在主循环中每次处理帧时都有一个固定的延迟
time.sleep(delay)
,这并不真实地模拟出帧处理的时间。应该考虑记录每个帧的时间戳,并在处理完帧后计算帧处理的实际时间。 -
多线程下的帧处理: 虽然视频流读取部分在单独的线程中,但是主循环仍然是顺序执行的,它等待每个帧进行处理。在多线程环境中,也许值得考虑在单独的线程中对帧进行处理。
-
内存和资源管理: 确保在程序退出时释放所有资源,特别是在多线程环境中,需要小心确保线程的安全退出。
-
代码结构和注释: 为了更好地可读性和维护性,添加一些注释来解释每个函数和方法的作用,以及代码块的意图。
3.多线程代码优化
-
去除固定延迟的处理方式: 代码在处理每一帧时都有固定的延迟。考虑使用实际帧处理时间的方法,而不是使用固定的延迟。这可以通过记录每个帧的时间戳来实现。
-
并行处理视频帧: 在主线程中按顺序处理每一帧。在多线程环境下,可以考虑使用多个线程并行处理视频帧,以加快处理速度。
-
资源释放: 在程序结束时,确保释放所有资源。这包括在适当的时候关闭视频流、终止线程等。
import cv2
import time
from threading import Threadclass WebcamStream:def __init__(self, stream_id=0):self.stream_id = stream_idself.vcap = cv2.VideoCapture(self.stream_id)if not self.vcap.isOpened():print("[Exiting]: Error accessing webcam stream.")exit(0)self.fps_input_stream = int(self.vcap.get(cv2.CAP_PROP_FPS))print("FPS of webcam hardware/input stream: {}".format(self.fps_input_stream))self.grabbed, self.frame = self.vcap.read()if not self.grabbed:print('[Exiting] No more frames to read')exit(0)self.stopped = Falseself.t = Thread(target=self.update, args=())self.t.daemon = Trueself.t.start()def update(self):while not self.stopped:grabbed, frame = self.vcap.read()if not grabbed:print('[Exiting] No more frames to read')self.stopped = Truebreakself.frame = framedef read(self):return self.framedef stop(self):self.stopped = Trueself.t.join()self.vcap.release()webcam_stream = WebcamStream(stream_id=0)
num_frames_processed = 0
start = time.time()
while True:frame = webcam_stream.read()if webcam_stream.stopped:breakdelay = 0.03time.sleep(delay)num_frames_processed += 1cv2.imshow('frame', frame)key = cv2.waitKey(1)if key == ord('q'):break
end = time.time()
webcam_stream.stop()
elapsed = end - start
fps = num_frames_processed / elapsed
print("FPS: {} , Elapsed Time: {} , Frames Processed: {}".format(fps, elapsed, num_frames_processed))
cv2.destroyAllWindows()
相关文章:
OpenCV图像处理——Python开发中OpenCV视频流的多线程处理方式
前言 在做视觉类项目中,常常需要在Python环境下使用OpenCV读取本地的还是网络摄像头的视频流,之后再调入各种模型,如目标分类、目标检测,人脸识别等等。如果使用单线程处理,很多时候会出现比较严重的时延,…...

webGL开发智慧城市流程
开发智慧城市的WebGL应用程序涉及多个方面,包括城市模型、实时数据集成、用户界面设计等。以下是一个一般性的流程,您可以根据项目的具体需求进行调整,希望对大家有所帮助。 1.需求分析: 确定智慧城市应用程序的具体需求和功能。考…...

Django讲课笔记02:Django环境搭建
文章目录 一、学习目标二、相关概念(一)Python(二)Django 三、环境搭建(一)安装Python1. 从官方网站下载最新版本的Python2. 运行安装程序并按照安装向导进行操作3. 勾选添加到路径复选框4. 完成安装过程5.…...

黑豹程序员-原生JS拖动div到任何地方-自定义布局
效果图 代码html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html xmlns"http://www.w3.org/1999/xhtml"> <head> <meta http-equiv"Content-Type" content"text/html; charsetutf-8" /…...
<软考高项备考>《论文专题 - 7 论文的项目背景之技术架构》
1 技术架构概况 ➢ 架构前端:HTML ➢ 后端:Java ➢ 数据库: Oracle ➢ 大数据:MapReduce ➢ 人工智能:Python ➢ 物联网:RFID识别,http传输,Java ➢ 开发APP: IOS、Android 2 常用开发语言 序号语言说明1JavaJava是一种跨平台的编程语言,广…...

6.3 C++11 原子操作与原子类型
一、原子类型 1.多线程下的问题 在C中,一个全局数据在多个线程中被同时使用时,如果不加任何处理,则会出现数据同步的问题。 #include <iostream> #include <thread> #include <chrono> long val 0;void test() {for (i…...

智能优化算法应用:基于狮群算法3D无线传感器网络(WSN)覆盖优化 - 附代码
智能优化算法应用:基于狮群算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于狮群算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.狮群算法4.实验参数设定5.算法结果6.参考文献7.MA…...
BERT、GPT学习问题个人记录
目录 1. 为什么过去几年大家都在做BERT, 做GPT的人少。 2. 但最近做GPT的多了以及为什么GPT架构的scaling(扩展性)比BERT好。 3.BERT是否可以用来做生成,如果可以的话为什么大家都用GPT不用BERT. 4. BERT里的NSP后面被认为是没用的&#x…...

HeartBeat监控Mysql状态
目录 一、概述 二、 安装部署 三、配置 四、启动服务 五、查看数据 一、概述 使用heartbeat可以实现在kibana界面对 Mysql 服务存活状态进行观察,如有必要,也可在服务宕机后立即向相关人员发送邮件通知 二、 安装部署 参照章节:监控组件…...
软件开发经常出现的bug原因有哪些
软件开发中出现bug的原因是多方面的,这些原因可能涉及到开发流程、人为因素、设计问题以及其他一系列因素。以下是一些常见的导致bug的原因: 1. 错误的需求分析: 不正确、不完整或者模糊的需求分析可能导致开发人员误解客户的需求࿰…...

代码随想录27期|Python|Day15|二叉树|层序遍历|对称二叉树|翻转二叉树
本文图片来源:代码随想录 层序遍历(图论中的广度优先遍历) 这一部分有10道题,全部可以套用相同的层序遍历方法,但是需要在每一层进行处理或者修改。 102. 二叉树的层序遍历 - 力扣(LeetCode) 层…...

鸿蒙开发组件之Web
一、加载一个url myWebController: WebviewController new webview.WebviewControllerbuild() {Column() {Web({src: https://www.baidu.com,controller: this.myWebController})}.width(100%).height(100%)} 二、注意点 2.1 不能用Previewer预览 Web这个组件不能使用预览…...
成绩分析。
成绩分析 题目描述 小蓝给学生们组织了一场考试,卷面总分为 100分,每个学生的得分都是一个0到100的整数。 请计算这次考试的最高分、最低分和平均分 输入描述 输入的第一行包含一个整数n(1n104),表示考试人数。 接下来n行,每行包含…...

Excel实现字母+数字拖拉自动递增,步长可更改
目录 1、带有字母的数字序列自增加(步长可变) 2、仅字母自增加 3、字母数字同时自增 1、带有字母的数字序列自增加(步长可变) 使用Excel通常可以直接通过拖拉的方式,实现自增数字…...
Java之Stream流
一、什么是Stream流 Stream是一种处理集合(Collection)数据的方式。Stream可以让我们以一种更简洁的方式对集合进行过滤、映射、排序等操作。 二、Stream流的使用步骤 先得到一条Stream流,并把数据放上去利用Stream流中的API进行各种操作 中间…...

vue中element-ui日期选择组件el-date-picker 清空所选时间,会将model绑定的值设置为null 问题 及 限制起止日期范围
一、问题 在Vue中使用Element UI的日期选择组件 <el-date-picker>,当你清空所选时间时,组件会将绑定的 v-model 值设置为 null。这是日期选择器的预设行为,它将清空所选日期后将其视为 null。但有时后端不允许日期传空。 因此ÿ…...

使用模方时,三维模型在su中显示不了怎么办?
答:可以借助截图功能截取模型影像在su中绘制白模。 模方是一款针对实景三维模型的冗余碎片、水面残缺、道路不平、标牌破损、纹理拉伸模糊等共性问题研发的实景三维模型修复编辑软件。模方4.1新增自动单体化建模功能,支持一键自动提取房屋结构ÿ…...

AR-LDM原理及代码分析
AR-LDM原理AR-LDM代码分析pytorch_lightning(pl)的hook流程main.py 具体分析TrainSampleLightningDatasetARLDM blip mm encoder AR-LDM原理 左边是模仿了自回归地从1, 2, ..., j-1来构造 j 时刻的 frame 的过程。 在普通Stable Diffusion的基础上,使用了1, 2, .…...

MySQL常见死锁的发生场景以及如何解决
死锁的产生是因为满足了四个条件: 互斥占有且等待不可强占用循环等待 这个网站收集了很多死锁场景 接下来介绍几种常见的死锁发生场景。其中,id 为主键,no(学号)为二级唯一索引,name(姓名&am…...

Leetcode 47 全排列 II
题意理解: 首先理解全排列是什么?全排列:使用集合中所有元素按照不同元素进行排列,将所有的排列结果的集合称为全排列。 这里的全排列难度升级了,问题在于集合中的元素是可以重复的。 问题:相同的元素会导致…...

MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...

ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...

【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...

HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...

AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...