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

Posted

技术标签:

【中文标题】如何从opencv中的捕获设备(相机)获取最新帧【英文标题】:How to get the latest frame from capture device (camera) in opencv 【发布时间】:2017-09-25 16:29:29 【问题描述】:

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

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 进行显示)

【问题讨论】:

【参考方案1】:

如果您不想在没有事件发生时捕获帧,为什么要预处理/处理您的帧?如果你不处理你的帧,你可以简单地丢弃它,除非事件发生。您的程序应该能够以足够的速度捕获、评估您的状况并丢弃,即与您的相机 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() 返回一个布尔值。如果帧被正确读取,它将是 True。然后,将捕获的帧存储在变量 frame 中。如果没有按键,则循环继续进行。当一个键被按下时,你处理你最后捕获的帧。

【讨论】:

您误解了这个问题。 OP 遇到的问题是 vidcap.read() 返回缓冲区中的下一帧,因此如果他们花费大量时间处理前一帧,vidcap.read() 将不会产生最新帧,而是产生缓冲区中的下一帧 @Christian Scillitoe:也许,我是。也许备忘录应该使用 GPU 来加速图像处理。这将与捕获同时发生,无需手动创建额外的线程(异构计算)。【参考方案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 question 发布了非常相似的代码,如果使用 javascript 解决方案会更好。我在另一个answer 上对那个问题的 cmets 详细说明了为什么没有单独线程的非脆弱解决方案似乎很困难。

一个更简单但仅支持某些 OpenCV 后端的替代解决方案是使用CAP_PROP_BUFFERSIZE。 2.4 docs 状态它“目前仅受 DC1394 [Firewire] v 2.x 后端支持”。对于 Linux 后端 V4L,根据 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)

【讨论】:

虽然在一个完美的世界里会有一种更优雅的方式来获取最新的帧数据,但这样就可以了! @ChristianScillitoe,在一个完美的世界中,人们通过制造商的 API 使用所有相机功能,并使用提供的相机触发器来实际“在事件发生时捕获一帧”。 cv2.VideoCapture 是一个紧凑的跨平台泛型类,提供方便和快速的原型设计。 @mainactual,我喜欢使用 OpenCV 等跨平台设备无关的 API 来处理视频捕获。此外,虽然 OpenCV 的 HighGUI 模块 was designed for prototyping,但视频 I/O 模块(VideoCapture 等)却没有。 @mainactual,我明白你的意思,但如果 VideoCapture 不是为了提供精确的时间控制,那么 grab() 和 retrieve() 方法有什么用呢?他们不应该提供这种控制吗?但是,如果您不能确定检索到的图像与您抓取的图像相同,它们有什么用呢?我觉得我误会了什么。 对于python 3,你想做import queue as Queue而不是import Queue【参考方案3】:

在我的树莓派 4 上,

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

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

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

cap.set( cv2.CAP_PROP_FPS, 2 )

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

【讨论】:

请不要对多个问题添加相同的答案。回答最好的一个并将其余的标记为重复。见Is it acceptable to add a duplicate answer to several questions? @Machavity 是的,我知道不能正常这样做,但在这种情况下,我在另一个问题中发布了副本,指向这个问题,Ulrich Stern 的答案,以及另一个可能是一个重复,因为另一个可能不是一个重复:另一个问题专门使用 IP 摄像机,而不是本地摄像机,我无法判断这里的答案是否适用于 IP 摄像机.这就是为什么我认为其他认为这可能是骗局的人将其打开。【参考方案4】:

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

【讨论】:

以上是关于如何从opencv中的捕获设备(相机)获取最新帧的主要内容,如果未能解决你的问题,请参考以下文章

OpenCV-Python:如何从实时视频流中获取最新帧或跳过旧帧

在android中使用opencv捕获视频帧

OpenCV,从相机捕获并保存到文件

如何使用 OpenCV4 C++ 从 c920 相机获取帧

使用 Opencv 加速从摄像头读取视频帧

将网络摄像头中的帧捕获为位图 - VFW - WINAPI