Python代码示例:无多线程读取摄像头视频帧及多线程基础解析
概述

在本文里,我们会看到两个Python代码示例,它们没有多线程,用于从摄像头读取视频帧,我们还会看到使用/不使用多线程所获得的FPS的差异。
什么是多线程?
线程是进程里的一个执行单元,多线程是指借助在线程间快速切换对CPU的控制(此为上下文切换)来并发执行多个线程,在我们的示例里,我们会看到多线程通过提升FPS(每秒帧数)达成更快的实时视频处理。
Python中的线程基础
以下代码片段展示了怎样运用python里的threading模块来创建线程:
导入线程模块
import threading
# importing the time module
import time
有一个用于打印“Hello”的函数,不过,该函数会休眠
在第11次迭代时,持续2秒钟
def print_hello():
for i in [id_1644546101](20):
if i == 10:
time.sleep(2)
print("Hello")
函数用于打印数字,直到给定的数字 。
def print_numbers(num):
for i in range(num+1):
print(i)
创建线程,目标被设置为该名称 。
需要在线程内部执行的函数,并且
args是要提供给函数的参数,那个函数
# needs to be executed.
print(来自主线程的问候。)
thread1 = threading.Thread(target = print_hello, args = ())
thread2 = threading.Thread(target = print_numbers, args = (10,))
# Starting the two threads
thread1.start()
thread2.start()
print(“又是那条主线!”)
让我们通过跟踪代码的执行来尝试理解输出:
主线程开始执行,打印出“Greetings from the main thread”,创建thread1,创建thread2,启动thread1线程,启动thread2线程。
发生上下文切换,开始执行thread1。
前十次迭代之后,thread1进入睡眠状态,thread2开始执行,在下一次上下文切换之前,thread2完成执行。
现在,主线程获得了CPU的控制权,主线程打印出“It’s the main thread again!”
另一个上下文切换发生,thread2恢复执行并完成。
由于主线程没有更多指令要执行,因此程序终止。
使用thread.join()
要是需要阻塞主线程,一直到thread1完成执行,该怎么办?要是需要阻塞主线程,一直到thread2完成执行,又该怎么办?
thread.join()能发挥作用,因为它会阻塞调用线程,直至调用其join()方法的线程结束:
# importing the threading module
import threading
# importing the time module
import time
# Function to print "Hello", however, the function sleeps
# for 2 seconds at the 11th iteration
def print_hello():
for i in range(20):
if i == 10:
time.sleep(2)
print("Hello")
# Function to print numbers till a given number
def print_numbers(num):
for i in range(num+1):
print(i)
# Creating the threads. Target is set to the name of the
# function that neeeds to be executed inside the thread and
# args are the arguments to be supplied to the function that
# needs to be executed.
print("Greetings from the main thread.")
thread1 = threading.Thread(target = print_hello, args = ())
thread2 = threading.Thread(target = print_numbers, args = (10,))
# Starting the two threads
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print("It's the main thread again!")
print(线程1已经完成执行,线程2也已经完成执行。)
原因多线程有助于更快的处理
视频处理代码分成两部分,一部分是从摄像头读取下一个可用帧,另一部分是对帧进行视频处理,比如运行深度学习模型来进行人脸识别等 。
读取下一帧,在没有多线程的程序里按顺序处理。程序等待下一帧可用,之后对其做必要处理。读取帧所需时间主要和请求、等待以及把下一个视频帧从相机传输到内存所需时间相关。对视频帧进行计算花费的时间,不管是在CPU上还是GPU上,都占据了视频处理所花的大部分时间。
在有多线程的程序里,读取下一帧,处理它不必是顺序的。一个线程执行读取下一帧任务时,主线程能用CPU或GPU处理最后读取的帧。如此,通过重叠两个任务,能减少读取和处理帧的总时间。
OpenCV 代码——没有多线程
引入所需的库
import cv2
import time
开启视频捕捉流
vcap = cv2.VideoCapture(0)
if vcap.isOpened() is False :
print(退出时出现错误,无法访问网络摄像头流。)
exit(0)
fps_input_stream = int(vcap.get(5))
print(网络摄像头硬件的帧率,输入流的帧率:{}.format(fps_input_stream))
grabbed, frame = vcap.read() 读取单帧用于初始化,进行硬件预热。
处理输入流中的帧
num_frames_processed = 0
start = time.time()
while True :
grabbed, frame = vcap.read()
if grabbed is False :
print(正在退出,没有更多帧可供读取。)
break
添加一个延迟,用于模拟处理一帧所花费的时间。
delay = 0.03 延迟值以秒为单位,所以,延迟等于1就相当于1秒 。
time.sleep(delay)
num_frames_processed += 1
cv2.imshow('frame' , frame)
key = cv2.waitKey(1)
if key == ord('q'):
break
end = time.time()
打印时间流逝情况以及帧率
elapsed = end-start
fps = num_frames_processed/elapsed
print(帧率为 {} ,已用时间为 {} ,已处理帧数为 {} 。.format(fps, elapsed, num_frames_processed))
释放输入流,关闭所有窗口
vcap.release()
cv2.destroyAllWindows()
OpenCV 代码——多线程
# importing required libraries
import cv2
import time
from threading import Thread 用于实现多线程处理的库
定义一个用于实现多线程处理的辅助类 。
class WebcamStream :
def __init__(self, stream_id=0):
self.stream_id = stream_id 默认值为0,用于主摄像头。
开启视频捕捉流
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))
从视频捕获流中读取单个帧用于初始化
self.grabbed , self.frame = self.vcap.read()
if self.grabbed is False :
print('[Exiting] No more frames to read')
exit(0)
当从self.vcap流中读取帧时,self.stopped被设置为False 。
self.stopped = True
参考该线程,以便从输入流中读取下一个可用帧。
self.t = Thread(target=self.update, args=())
self.t.daemon = True 守护线程在程序执行时,一直在后台运行
用于启动线程的方法,该线程用于在输入流中抓取下一个可用帧 。
def start(self):
self.stopped = False
self.t.start()
方法,用于读取下一帧,
def update(self):
while True :
if self.stopped is True :
break
self.grabbed , self.frame = self.vcap.read()
if self.grabbed is False :
print('[Exiting] No more frames to read')
self.stopped = True
break
self.vcap.release()
方法,用于返回最新读取的帧。
def read(self):
return self.frame
用于停止读取帧的方法被调用了。
def stop(self):
self.stopped = True
webcam_stream = WebcamStream(stream_id=0) stream_id等于0,用于主摄像头 。
webcam_stream.start()
# processing frames in input stream
num_frames_processed = 0
start = time.time()
while True :
if webcam_stream.stopped is True :
break
else :
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 += 1
cv2.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()
我之前自学Python时整理了诸多Python学习资料,如今这些资料我已用不上,便上传至CSDN官方,有需要的朋友可扫描下方二维码获取
一、学习大纲
二、开发工具
三、Python基础材料
四、实战资料