如何从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_hasframes 是什么?是否有可能获得该信息?我尝试查看 _CV_CAP_PROP_POSFRAMES 但它始终为 -1。

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

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

原文由 memo 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 2.4k
2 个回答

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

 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

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

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

一种更简单但仅支持某些 OpenCV 后端的 替代解决方案 是使用 CAP_PROP_BUFFERSIZE2.4 文档 声明它“目前仅受 DC1394 [Firewire] 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)

原文由 Ulrich Stern 发布,翻译遵循 CC BY-SA 4.0 许可协议

这是 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.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

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

原文由 Bruno Degomme 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题