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

How to get the latest frame from capture device (camera) in opencv

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

cap = cv2.VideoCapture(device_id)

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

    process(img)
    cv.Waitkey(10)

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

一个不错的解决方案是:

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

    process(img)
    cv.Waitkey(10)

但什么是capture_has_frames?是否有可能获得该信息?我试着查看 CV_CAP_PROP_POS_FRAMES 但它总是 -1.

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

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

如果你不想在没有事件发生时捕捉帧,你为什么preprocessing/processing你的帧?如果你不处理你的框架,你可以简单地丢弃它,除非事件发生。您的程序应该能够以足够的速度捕获、评估您的条件并丢弃,即与您的相机 FPS 捕获速率相比足够快,以始终获得队列中的最后一帧。

如果不精通python 因为我用 C++ 做我的 OpenCV,但它看起来应该类似于这样:

vidcap = cv.VideoCapture(   filename    )

while True:
    success, frame = vidcap.read()
    If Not success:
         break
    If cv.waitKey(1):
         process(frame)

根据 OpenCV 参考,vidcap.read() returns 一个布尔值。如果 frame 被正确读取,它将是 True。然后,捕获的帧存储在变量frame中。如果没有按键,循环继续进行。按下某个键时,您将处理最后捕获的帧。

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

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

帧 reader 线程封装在自定义 VideoCapture class 中,与主线程的通信是通过队列进行的。

我发布了与该问题非常相似的 node.js question, where a JavaScript solution would have been better. My comments on another answer 代码,详细说明了为什么没有单独线程的非脆弱解决方案似乎很困难。

替代解决方案 更简单但仅支持某些 OpenCV 后端正在使用 CAP_PROP_BUFFERSIZE2.4 docs state it is "only supported by DC1394 [Firewire] v 2.x backend currently." For Linux backend V4L, according to a comment in the 3.4.5 code 支持是在 2018 年 3 月 9 日添加的,但我得到的正是这个后端的 VIDEOIO ERROR: V4L: Property <unknown property string>(38) not supported by device。可能值得先试一试;代码就像这样简单:

cap.set(cv2.CAP_PROP_BUFFERSIZE, 0)

在我的 Raspberry Pi 4,

cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)

确实有效,并且是我的 pi 相机为我提供最新帧所需的全部,在相机前面的场景和在预览图像中显示该场景之间有一致的 3 秒以上延迟。我的代码需要 1.3 秒来处理图像,所以我不确定为什么会出现另外 2 秒的延迟,但它是一致的并且有效。

旁注:由于我的代码需要一秒钟来处理图像,因此我还添加了

cap.set( cv2.CAP_PROP_FPS, 2 )

以防它减少任何不需要的 activity,因为我无法每秒获得一帧。但是,当我将 cv2.CAP_PROP_FPS 设置为 1 时,我得到了一个奇怪的输出,即我的所有帧几乎都是全黑的,因此将 FPS 设置得太低可能会导致问题

这里是 Ulrich 解决方案的略微简化版本。 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.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:
            ret = self.cap.grab()
            if not ret:
                break

    # retrieve latest frame
    def read(self):
        ret, frame = self.cap.retrieve()
        return frame