草庐IT

基于cv2.VideoCapture 和 OpenCV 得到更快的 FPS之Webcam篇

求则得之,舍则失之 2023-04-10 原文

使用线程处理 I/O 繁重的任务(例如从相机传感器读取帧)是一种已经存在数十年的编程模型。

例如,如果我们要构建一个网络爬虫来抓取一系列网页(根据定义,这个任务是 I/O 绑定的),我们的主程序将生成多个线程来处理并行下载这组页面,而不是仅依靠单个线程(我们的“主线程”)按顺序下载页面。这样做可以让我们更快地抓取网页。

同样的概念也适用于计算机视觉中的从相机读取帧——我们可以简单地通过创建一个新线程来提高我们的 FPS,该线程轮询相机以获取新帧,而我们的主线程处理当前帧。

这是一个简单的概念,但它在 OpenCV 示例中很少见,因为它确实为项目添加了几行额外的代码(或者有时是很多行,取决于您的线程库)。多线程也可以让你的程序更难调试,但是一旦你做对了,你可以显著提高你的 FPS。

我们将通过编写一个线程化的 Python 类来开始这一系列文章,以使用 OpenCV 访问您的网络摄像头或 USB 摄像头。

1.使用线程获得更高的 FPS

使用 OpenCV 处理视频流时获得更高 FPS 的“秘诀”是将 I/O(即从相机传感器读取帧)移动到单独的线程。
您会看到,使用 cv2.VideoCapture 函数和 .read() 方法访问您的网络摄像头/USB 摄像头是一个阻塞操作。我们的 Python 脚本的主线程被完全阻塞(即“停滞”),直到从相机设备读取帧并返回到我们的脚本。
I/O 任务,与 CPU 绑定操作相反,往往很慢。虽然计算机视觉和视频处理应用程序肯定会占用大量 CPU(特别是如果它们打算实时运行),但事实证明,相机 I/O 也可能是一个巨大的瓶颈。

正如我们将在本文后面看到的那样,仅通过调整相机 I/O 过程,我们就可以将 FPS 提高多达 379%!

当然,这并不是真正的 FPS 增加,因为它是延迟的显著减少(即,始终有一帧可用于处理;我们不需要轮询相机设备并等待 I/O 完成)。在这篇文章的其余部分,为了简洁起见,我将我们的指标称为“FPS 增加”,但也要记住,它是延迟减少和 FPS 增加的结合。

为了实现FPS的增加/延迟的减少,我们的目标是将从网络摄像头或USB设备读取帧移动到一个完全不同的线程,完全独立于我们的Python主脚本。

这将允许从 I/O 线程连续读取帧,同时我们的主线程处理当前帧。一旦主线程处理完它的帧,它只需要从 I/O 线程中获取当前帧。这无需等待阻塞 I/O 操作即可完成。

实现我们的线程视频流功能的第一步是定义一个 FPS 类,我们可以使用它来测量我们的每秒帧数。
创建一个文件名为fps.py的文件,写入以下代码:

# 导入必要的包
import datetime
class FPS:
	def __init__(self):
		# 存储开始时间、结束时间和在开始和结束间隔之间检查的帧总数
		self._start = None  # 当我们开始读取帧时的开始时间戳。
		self._end = None  # 当我们停止读取帧时的结束时间戳。
		self._numFrames = 0  # 在 _start 和 _end 间隔期间读取的帧总数。
	def start(self):
		# 启动计时器
		self._start = datetime.datetime.now()
		return self
	def stop(self):
		# 停止计时器
		self._end = datetime.datetime.now()
	def update(self):
		# 增加开始和结束间隔期间检查的帧总数
		self._numFrames += 1
	def elapsed(self):
		# 返回开始和结束间隔之间的总秒数
		return (self._end - self._start).total_seconds()
	def fps(self):
		# 计算(近似)每秒帧数
		return self._numFrames / self.elapsed()

2.使用 Python 和 OpenCV 提高网络摄像头 FPS

现在我们已经定义了 FPS 类(因此我们可以根据经验比较结果),创建WebcamVideoStream.py文件,让我们在文件中定义包含实际线程摄像头读取的 WebcamVideoStream 类:

# import the necessary packages
from threading import Thread
import cv2
class WebcamVideoStream:
	def __init__(self, src=0):
		# 初始化摄像机流并从流中读取第一帧
		self.stream = cv2.VideoCapture(src)
		(self.grabbed, self.frame) = self.stream.read()
		# 初始化用于指示线程是否应该停止的变量
		self.stopped = False
	def start(self):
		# 启动线程从视频流中读取帧
		Thread(target=self.update, args=()).start()
		return self
	def update(self):
		# 继续无限循环,直到线程停止
		while True:
			# 如果设置了线程指示器变量,则停止线程
			if self.stopped:
				return
			# 否则,从流中读取下一帧
			(self.grabbed, self.frame) = self.stream.read()
	def read(self):
		# 返回最近读取的帧
		return self.frame
	def stop(self):
		# 表示应该停止线程
		self.stopped = True

3.案例测试

现在我们已经定义了 FPSWebcamVideoStream 类,我们可以将所有部分放在 fps_demo.py 中:

# import the necessary packages
from __future__ import print_function
from imutils.video import WebcamVideoStream
from imutils.video import FPS
import argparse
import imutils
import cv2
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-n", "--num-frames", type=int, default=100,
	help="# of frames to loop over for FPS test")
ap.add_argument("-d", "--display", type=int, default=-1,
	help="Whether or not frames should be displayed")
args = vars(ap.parse_args())

# 此代码块将帮助我们获得 FPS 的基线,没有线程并且在从相机流中读取帧时使用阻塞 I/O。

# 获取指向视频流的指针并初始化 FPS 计数器
print("[INFO] sampling frames from webcam...")
stream = cv2.VideoCapture(0)
fps = FPS().start()
# 循环一些帧
while fps._numFrames < args["num_frames"]:
	# 从流中抓取帧并将其调整为最大宽度为 400 像素
	(grabbed, frame) = stream.read()
	frame = imutils.resize(frame, width=400)
	# 检查帧是否应该显示到我们的屏幕上
	if args["display"] > 0:
		cv2.imshow("Frame", frame)
		key = cv2.waitKey(1) & 0xFF
	# 更新 FPS 计数器
	fps.update()
# 停止计时器并显示 FPS 信息
fps.stop()
print("[INFO] elasped time: {:.2f}".format(fps.elapsed()))
print("[INFO] approx. FPS: {:.2f}".format(fps.fps()))
# 做一些清理工作
stream.release()
cv2.destroyAllWindows()

# 让我们看看我们从视频流中读取帧的线程代码:

# 创建了一个*线程*视频流,允许相机传感器预热,并启动 FPS 计数器
print("[INFO] sampling THREADED frames from webcam...")
vs = WebcamVideoStream(src=0).start()
fps = FPS().start()
# 循环某些帧…这一次使用线程流
while fps._numFrames < args["num_frames"]:
	# 从线程视频流中抓取帧并将其调整为最大宽度为 400 像素
	frame = vs.read()
	frame = imutils.resize(frame, width=400)
	# 检查帧是否应该显示到我们的屏幕上
	if args["display"] > 0:
		cv2.imshow("Frame", frame)
		key = cv2.waitKey(1) & 0xFF
	# 更新 FPS 计数器
	fps.update()
# 停止计时器并显示 FPS 信息
fps.stop()
print("[INFO] elasped time: {:.2f}".format(fps.elapsed()))
print("[INFO] approx. FPS: {:.2f}".format(fps.fps()))
# 做一些清理工作
cv2.destroyAllWindows()
vs.stop()

4.结果分析

要查看网络摄像头 I/O 线程在运行中的影响,只需执行以下命令:python fps_demo.py

正如我们所见,通过在 Python 脚本的主线程中不使用线程并从视频流中顺序读取帧,我们能够获得可观的 29.97 FPS。然而,一旦我们切换到使用线程相机 I/O,我们就达到了 143.71 FPS – 增加了 379% 以上!

这显然大大降低了我们的延迟,并显著提高了我们的FPS,这都是通过使用线程而获得的。

然而,正如我们即将发现的那样,使用 cv2.imshow 可以大大降低我们的 FPS。如果您考虑一下,这种行为是有道理的–cv2.show 函数只是另一种形式的 I/O,只是这次不是从视频流中读取帧,而是将帧发送到显示器上的输出。

注意:这里我们还使用了cv2.waitKey(1)函数,它确实为我们的主循环添加了1ms的延迟。也就是说,这个函数对于键盘交互和在屏幕上显示图像帧是必要的。

要演示 cv2.imshow I/O 如何降低 FPS,只需发出以下命令:python fps_demo.py --display 1

不使用线程,我们达到了 28.90 FPS。通过线程,我们达到了 39.93 FPS。 FPS 仍然增加了 38%,但远不及我们之前示例的 379%。

总的来说,我建议使用 cv2.imshow 函数来帮助调试您的程序——但如果您的最终生产代码不需要它,则没有理由包含它,因为您会损害您的 FPS。

这类程序的一个很好的例子就是开发一种家庭监控运动探测器,它会向你发送一条文本信息,其中包含一个刚刚走进你家前门的人的照片。实际上,您并不需要cv2.imshow函数。通过删除它,您可以提高运动检测器的性能,并允许它更快地处理更多帧。

参考目录

https://www.pyimagesearch.com/2015/12/21/increasing-webcam-fps-with-python-and-opencv/

有关基于cv2.VideoCapture 和 OpenCV 得到更快的 FPS之Webcam篇的更多相关文章

  1. ruby-on-rails - 每次我尝试部署时,我都会得到 - (gcloud.preview.app.deploy) 错误响应 : [4] DEADLINE_EXCEEDED - 2

    我是Google云的新手,我正在尝试对其进行首次部署。我的第一个部署是RubyonRails项目。我基本上是在关注thisguideinthegoogleclouddocumentation.唯一的区别是我使用的是我自己的项目,而不是他们提供的“helloworld”项目。这是我的app.yaml文件runtime:customvm:trueentrypoint:bundleexecrackup-p8080-Eproductionconfig.ruresources:cpu:0.5memory_gb:1.3disk_size_gb:10当我转到我的项目目录并运行gcloudprevie

  2. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  3. Vscode+Cmake配置并运行opencv环境(Windows和Ubuntu大同小异) - 2

    之前在培训新生的时候,windows环境下配置opencv环境一直教的都是网上主流的vsstudio配置属性表,但是这个似乎对新生来说难度略高(虽然个人觉得完全是他们自己的问题),加之暑假之后对cmake实在是爱不释手,且这样配置确实十分简单(其实都不需要配置),故斗胆妄言vscode下配置CV之法。其实极为简单,图比较多所以很长。如果你看此文还配不好,你应该思考一下是不是自己的问题。闲话少说,直接开始。0.CMkae简介有的人到大二了都不知道cmake是什么,我不说是谁。CMake是一个开源免费并且跨平台的构建工具,可以用简单的语句来描述所有平台的编译过程。它能够根据当前所在平台输出对应的m

  4. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  5. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  6. ruby - 如何更快地解决 project euler #21? - 2

    原始问题Letd(n)bedefinedasthesumofproperdivisorsofn(numberslessthannwhichdivideevenlyinton).Ifd(a)=bandd(b)=a,whereab,thenaandbareanamicablepairandeachofaandbarecalledamicablenumbers.Forexample,theproperdivisorsof220are1,2,4,5,10,11,20,22,44,55and110;therefored(220)=284.Theproperdivisorsof284are1,2,

  7. ruby-on-rails - (Ruby,Rails) 基于角色的身份验证和用户管理...? - 2

    我正在寻找用于Rails的优质管理插件。似乎大多数现有的插件/gem(例如“restful_authentication”、“acts_as_authenticated”)都围绕着self注册等展开。但是,我正在寻找一种功能齐全的基于管理/管理角色的解决方案——但不是简单地附加到另一个非基于角色的解决方案。如果我找不到,我想我会自己动手......只是不想重新发明轮子。 最佳答案 RyanBates最近做了两个关于授权的railscast(注意身份验证和授权之间的区别;身份验证检查用户是否如她所说的那样,授权检查用户是否有权访问资源

  8. ruby-on-rails - 使用 rspec 测试自定义验证器。为什么我会得到 Proc? - 2

    如标题所示,我正在尝试使用Rspec测试自定义验证器。我得到一个错误,我不明白为什么......如果你能阐明一些问题,我将非常感激。我们开始吧:验证者规范require'spec_helper'describeGraphDateValidatordoit"shouldnotvalidateactivitywithemptystarttime"doexpect{Graph.new({start_time:''}).valid?}.toeq(false)endend如果我打印Graph.new({start_time:''}).valid?它会打印false然而,当它通过规范时,它返回一个

  9. ruby - 为什么我用递归得到 "stack level too deep"? - 2

    我有这个ruby代码:defget_sumnreturn0ifn似乎正在为999之前的值工作。当我尝试9999时,它给了我这个:stackleveltoodeep(SystemStackError)所以,我添加了这个:RubyVM::InstructionSequence.compile_option={:tailcall_optimization=>true,:trace_instruction=>false}但什么也没发生。我的ruby版本是:ruby1.9.3p392(2013-02-22revision39386)[x86_64-darwin12.2.1]我还增加了机器的堆栈大

  10. ruby - 在 Rakefile 中动态生成 Rake 测试任务(基于现有的测试文件) - 2

    我正在根据Rakefile中的现有测试文件动态生成测试任务。假设您有各种以模式命名的单元测试文件test_.rb.所以我正在做的是创建一个以“测试”命名空间内的文件名命名的任务。使用下面的代码,我可以用raketest:调用所有测试require'rake/testtask'task:default=>'test:all'namespace:testdodesc"Runalltests"Rake::TestTask.new(:all)do|t|t.test_files=FileList['test_*.rb']endFileList['test_*.rb'].eachdo|task|n

随机推荐