如何从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_BUFFERSIZE
。 2.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
我想连接到相机,并且只在事件发生时(例如按键)捕获帧。我想做的简化版本是这样的:
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_BUFFERSIZE
。 2.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