There's a subtle yet significant difference that becomes more obvious when taking a closer look at the documentation for each of the two methods.
grab"only" gets the image from the camera and holds it for further processing:The methods/functions grab the next frame from video file or camera and return true (non-zero) in the case of success. [...] you call
VideoCapture::grab()for each camera and after that call the slower methodVideoCapture::retrieve()to decode and get frame from each camera.In this case, you still have to retrieve the frame (i.e. "decode and get") from OpenCV so that you can work with it.
grabonly returns a boolean that indicates whether OpenCV was able to get the frame from the camera - the return parameter in this case is not the frame.readcombines bothgrabandretrieveoperations into one command and returns the already decoded frame:The methods/functions combine
VideoCapture::grab()andVideoCapture::retrieve()in one call. This is the most convenient method for reading video files or capturing data from decode and return the just grabbed frame. If no frames has been grabbed (camera has been disconnected, or there are no more frames in video file), the methods return false and the functions return NULL pointer.This method returns the actual frame back to your application so you don't have to worry about checking the boolean parameter and writing an additional line of code to get the frame back by OpenCV.
Depending on your application it may be easier to just use read as it will likely save you one or two lines of code.
There's a subtle yet significant difference that becomes more obvious when taking a closer look at the documentation for each of the two methods.
grab"only" gets the image from the camera and holds it for further processing:The methods/functions grab the next frame from video file or camera and return true (non-zero) in the case of success. [...] you call
VideoCapture::grab()for each camera and after that call the slower methodVideoCapture::retrieve()to decode and get frame from each camera.In this case, you still have to retrieve the frame (i.e. "decode and get") from OpenCV so that you can work with it.
grabonly returns a boolean that indicates whether OpenCV was able to get the frame from the camera - the return parameter in this case is not the frame.readcombines bothgrabandretrieveoperations into one command and returns the already decoded frame:The methods/functions combine
VideoCapture::grab()andVideoCapture::retrieve()in one call. This is the most convenient method for reading video files or capturing data from decode and return the just grabbed frame. If no frames has been grabbed (camera has been disconnected, or there are no more frames in video file), the methods return false and the functions return NULL pointer.This method returns the actual frame back to your application so you don't have to worry about checking the boolean parameter and writing an additional line of code to get the frame back by OpenCV.
Depending on your application it may be easier to just use read as it will likely save you one or two lines of code.
Adding some info on accepted answer: Also reading from openCV documentation, you should use grab if you need to synchronize images from different devices and don't have triggers connecting cameras.
grab() takes less time than read(), so you should grab() all devices (reading times would be closer) and them retrieve() each frame later. Again, this is useful f you need your feeds synchronized, for applications like a custom made stereo camera.
while True:
# grab all devices
device1.grab()
device2.grab()
...
# retrieve them later
device1.retrieve(frame_dev1)
device2.retrieve(frame_dev2)
...
I also faced the same problem. Seems that once the VideoCapture object is initialized it keeps storing the frames in some buffer of sort and returns a frame from that for every read operation. What I did is I initialized the VideoCapture object every time I wanted to read a frame and then released the stream. Following code captures 10 images at an interval of 10 seconds and stores them. Same can be done using while(True) in a loop.
for x in range(0,10):
cap = cv2.VideoCapture(0)
ret, frame = cap.read()
cv2.imwrite('test'+str(x)+'.png',frame)
cap.release()
time.sleep(10)
I've encountered the same problem and found a git repository of Azure samples for their computer vision service. The relevant part is the Camera Capture module, specifically the Video Stream class.
You can see they've implemented a Queue that is being updated to keep only the latest frame:
def update(self):
try:
while True:
if self.stopped:
return
if not self.Q.full():
(grabbed, frame) = self.stream.read()
# if the `grabbed` boolean is `False`, then we have
# reached the end of the video file
if not grabbed:
self.stop()
return
self.Q.put(frame)
# Clean the queue to keep only the latest frame
while self.Q.qsize() > 1:
self.Q.get()
I think the solution mentioned in the question, namely having a separate thread that clears the buffer, is the easiest non-brittle solution for this. Here reasonably nice (I think) code for this:
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
The frame reader thread is encapsulated inside the custom VideoCapture class, and communication with the main thread is via a queue.
I posted very similar code for a node.js question, where a JavaScript solution would have been better. My comments on another answer to that question give details why a non-brittle solution without separate thread seems difficult.
An alternative solution that is easier but supported only for some OpenCV backends is using CAP_PROP_BUFFERSIZE. The 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, support was added on 9 Mar 2018, but I got VIDEOIO ERROR: V4L: Property <unknown property string>(38) not supported by device for exactly this backend. It may be worth a try first; the code is as easy as this:
cap.set(cv2.CAP_PROP_BUFFERSIZE, 0)
Here's a simplified version of Ulrich's solution. OpenCV's read() function combines grab() and retrieve() in one call, where grab just loads the next frame in memory, and retrieve decodes the latest grabbed frame (demosaicing & motion jpeg decompression).
We're only interested in decoding the frame we're actually reading, so this solution saves some CPU, and removes the need for a queue
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
EDIT: Following Arthur Tacca's comment, added a lock to avoid simultaneous grab & retrieve, which could lead to a crash as OpenCV isn't thread-safe.