如何从opencv [python]中的捕获设备(相机)获取最新帧

mem*_*emo 11 python camera opencv video-capture opencv3.0

我想连接到相机,并且只在事件发生时捕获帧(例如按键).我想做的简化版本是这样的:

cap = cv2.VideoCapture(device_id)

while True:
    if event:
        img = cap.read()
        preprocess(img)

    process(img)
    cv.Waitkey(10)
Run Code Online (Sandbox Code Playgroud)

但是,cap.read似乎只捕获队列中的下一帧,而不是最新的帧.我在网上做了很多搜索,似乎有很多问题,但没有明确的答案.只有一些脏的黑客涉及在抓取之前和之后打开和关闭捕获设备(这对我不起作用,因为我的事件可能每秒触发多次); 或者假设一个固定的帧率并在每个事件上读取固定的n次(这对我不起作用,因为我的事件是不可预测的并且可能在任何时间间隔发生).

一个很好的解决方案是:

while True:
    if event:
        while capture_has_frames:
            img = cap.read()
        preprocess(img)

    process(img)
    cv.Waitkey(10)
Run Code Online (Sandbox Code Playgroud)

但是capture_has_frames是什么?有可能得到这些信息吗?我试着查看CV_CAP_PROP_POS_FRAMES,但它总是-1.

现在我有一个单独的线程,其中捕获以全fps运行,而在我的事件中,我正在抓取该线程的最新图像,但这似乎有点过分.

(我用的是Ubuntu 16.04顺便说一句,但我想这应该没关系.我也使用pyqtgraph进行显示)

Bru*_*mme 13

这是乌尔里希解决方案的简化版本。OpenCV的read()函数将grab()retrieve()结合在一个调用中,其中grab只是加载内存中的下一帧,而retrieve对最新抓取的帧进行解码(去马赛克和运动jpeg解压缩)。

我们只对解码我们实际读取的帧感兴趣,因此该解决方案节省了一些 CPU,并且不需要队列

import cv2
import threading


# bufferless VideoCapture
class VideoCapture:
    def __init__(self, name):
        self.cap = cv2.VideoCapture(name)
        self.lock = threading.Lock()
        self.t = threading.Thread(target=self._reader)
        self.t.daemon = True
        self.t.start()

    # grab frames as soon as they are available
    def _reader(self):
        while True:
            with self.lock:
                ret = self.cap.grab()
            if not ret:
                break

    # retrieve latest frame
    def read(self):
        with self.lock:
            _, frame = self.cap.retrieve()
        return frame
Run Code Online (Sandbox Code Playgroud)

编辑:按照 Arthur Tacca 的评论,添加了一个锁以避免同时抓取和检索,这可能会导致崩溃,因为 OpenCV 不是线程安全的。


Ulr*_*ern 7

我认为问题中提到的解决方案,即拥有一个单独的线程来清除缓冲区,是最简单的解决方案。这里的代码相当不错(我认为):

import cv2, Queue, threading, time

# bufferless VideoCapture
class VideoCapture:

  def __init__(self, name):
    self.cap = cv2.VideoCapture(name)
    self.q = Queue.Queue()
    t = threading.Thread(target=self._reader)
    t.daemon = True
    t.start()

  # read frames as soon as they are available, keeping only most recent one
  def _reader(self):
    while True:
      ret, frame = self.cap.read()
      if not ret:
        break
      if not self.q.empty():
        try:
          self.q.get_nowait()   # discard previous (unprocessed) frame
        except Queue.Empty:
          pass
      self.q.put(frame)

  def read(self):
    return self.q.get()

cap = VideoCapture(0)
while True:
  time.sleep(.5)   # simulate time between events
  frame = cap.read()
  cv2.imshow("frame", frame)
  if chr(cv2.waitKey(1)&255) == 'q':
    break
Run Code Online (Sandbox Code Playgroud)

帧读取器线程封装在自定义VideoCapture类中,并且通过队列与主线程进行通信。

我为node.js 问题发布了非常相似的代码,在此JavaScript解决方案会更好。我对这个问题的另一个答案的评论详细说明了为什么没有单独线程的非脆弱解决方案似乎很困难。

使用更容易但仅某些OpenCV后端支持的替代解决方案是使用CAP_PROP_BUFFERSIZE。的2.4文档说明它是“仅由DC1394 [火线】V 2.x的后端当前支持”。对于Linux后端V4L,根据3.4.5代码中的注释,已在2018年3月9日添加了支持,但我VIDEOIO ERROR: V4L: Property <unknown property string>(38) not supported by device完全支持此后端。可能值得一试。代码很简单:

cap.set(cv2.CAP_PROP_BUFFERSIZE, 0)
Run Code Online (Sandbox Code Playgroud)

  • @mainactual,我明白你的意思,但是如果 VideoCapture 的目的不是提供精确的时序控制,那么grab() 和retrieve() 方法的用途是什么?难道他们不应该提供这种控制吗?但是,如果您无法确定检索到的图像与您抓取的图像相同,那么它们有什么用处呢?我觉得我误会了什么。 (4认同)
  • 虽然在一个完美的世界中会有一种更优雅的方式来获取最新的帧数据,但这确实有效! (3认同)
  • @ChristianScillitoe,在一个完美的世界中,可以通过制造商的 API 使用所有相机功能,并使用提供的相机触发器来实际“在事件发生时捕获帧”。`cv2.VideoCapture` 是一个紧凑的跨平台通用类,提供方便和快速的原型设计。 (3认同)