[英]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.