OpenCV视频流Python多线程处理方法详细分析

 

前言

最近在功能性测试的过程中,需要在Python环境下用OpenCV读取网络摄像头的视频流,接着用目标检测器进行视屏帧的后续处理。在测试过程中发现如果是单线程的情况,会出现比较严重的时延,如果目标检测模型稍微大一点,像YOLOv4这类的,那么情况更加严重。

后面考虑到演示效果,从单线程改为了多线程,即单独用一个线程实时捕获视频帧,主线程在需要时从子线程拷贝最近的帧使用即可。通过这样的修改,不仅时延基本消失,整个流程的实时性也有相对的提升,可以说是非常实用的技巧。

 

Python多线程编程

使用Python进行多线程编程是较为简单的,Python的threading模块封装了相关的操作,通过编写功能类继承threading.Thread即可实现自己的逻辑。简单的代码示例如下所示:

class myThread(threading.Thread):
  def __init__(self, name=None):
      super(myThread, self).__init__(name=name)
  def run(self):
      print('=> Thread %s is running ...' % self.name)
thread = myThread()
thread.start()
thread.join()

上面的代码简单展示了如何使用线程类:通过调用start()方法,线程实例开始在单独的线程上下文中运行自己的run()函数处理任务,直到线程退出。在此期间,主线程可以继续执行任务。当主线程任务执行结束时,主线程可通过设置全局状态变量告知子线程退出,同时调用join()方法等待子线程运行结束。

 

OpenCV视屏流的多线程处理

在上面例子的基础上,可对简单的单线程处理流程进行优化,即将读取视频帧的部分单独放在一个线程执行,同时提供线程间同步、数据交互的支持,在主线程中运行目标检测模型和后续处理流程,在需要时从读取视频帧的子线程获取最近的帧进行预处理、推理、后处理和可视化等操作。相关的示例代码如下:

import numpy as np
import cv2
import threading
from copy import deepcopy
thread_lock = threading.Lock()
thread_exit = False
class myThread(threading.Thread):
  def __init__(self, camera_id, img_height, img_width):
      super(myThread, self).__init__()
      self.camera_id = camera_id
      self.img_height = img_height
      self.img_width = img_width
      self.frame = np.zeros((img_height, img_width, 3), dtype=np.uint8)
  def get_frame(self):
      return deepcopy(self.frame)
  def run(self):
      global thread_exit
      cap = cv2.VideoCapture(self.camera_id)
      while not thread_exit:
          ret, frame = cap.read()
          if ret:
              frame = cv2.resize(frame, (self.img_width, self.img_height))
              thread_lock.acquire()
              self.frame = frame
              thread_lock.release()
          else:
              thread_exit = True
      cap.release()
def main():
  global thread_exit
  camera_id = 0
  img_height = 480
  img_width = 640
  thread = myThread(camera_id, img_height, img_width)
  thread.start()
  while not thread_exit:
      thread_lock.acquire()
      frame = thread.get_frame()
      thread_lock.release()
      cv2.imshow('Video', frame)
      if cv2.waitKey(1) & 0xFF == ord('q'):
          thread_exit = True
  thread.join()
if __name__ == "__main__":
  main()

在上面的代码中,为确保资源访问不受冲突,使用threading.Lock进行保护;主线程使用thread_exit全局状态变量控制子线程的运行状态。稍微特别一点的是,thread_exit实际上控制着两个线程的运行状态,因为在上述的处理流程中,两个线程都拥有终止运行流程的话语权,故这样的处理是合理的。

 

结语

实际上使用多线程并行处理任务,最大程度地利用资源早已是老生常谈的技巧,例如在服务器端,会开辟有专门的线程池用于处理随时可能到来的请求,而在嵌入式通信终端上,也通常采用线程池的方式来处理收到的消息包,以尽可能提升实时性。虽然多线程的处理方式相较单线程而言要稍微复杂一些,但带来的性能提升确是实打实的,所以还是很值得一试。

关于OpenCV视频流多线程处理方法详细分析的文章就介绍至此,更多相关OpenCV视频流内容请搜索编程宝库以前的文章,希望以后支持编程宝库

 一、图像直方图1.1 定义图像直方图是图像的基本属性之一,也是反映图像像素数据分布的统计学特征,其横坐标代表了图像像素点在[0,255]范围中,纵坐标代表图像像素点出现的个数或 ...