[英]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 进行显示)
我认为问题中提到的解决方案,即有一个单独的线程来清除缓冲区,是最简单的非脆弱解决方案。 这里相当不错(我认为)代码:
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 解决方案会更好。 我对该问题的另一个答案的评论详细说明了为什么没有单独线程的非脆弱解决方案似乎很困难。
另一种更简单但仅支持某些 OpenCV 后端的解决方案是使用CAP_PROP_BUFFERSIZE
。 2.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
for exactly this backend。 可能值得一试; 代码就像这样简单:
cap.set(cv2.CAP_PROP_BUFFERSIZE, 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.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
如果您不想在没有事件发生时捕获帧,为什么要预处理/处理您的帧? 如果您不处理您的帧,您可以简单地丢弃它,除非事件发生。 您的程序应该能够以足够的速度捕获、评估您的状况并丢弃,即与您的相机 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中。 如果没有按键,则循环继续进行。 按下某个键时,您将处理最后捕获的帧。
在我的树莓派 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 设置得太低可能会导致问题
也可以通过使用cv2.CAP_GSTREAMER
后端始终获取最新帧。 如果您在cv2.getBuildInformation()
中启用了 gstreamer 支持,您可以使用appsink
参数sync=false
和drop=true
初始化视频捕获
例子:
cv2.VideoCapture("rtspsrc location=rtsp://... ! decodebin ! videoconvert ! video/x-raw,framerate=30/1 ! appsink drop=true sync=false", cv2.CAP_GSTREAMER)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.