Skip to content

Commit b28ef26

Browse files
author
Christoph Spörk
committed
Add support for skipping frames (in case videofile has malformed frames)
Add delegate call to get stream dimensions Replace long polling put queue with built-in blocking wait for put (with configurable timeout on instantiation)
1 parent 9f740a5 commit b28ef26

File tree

1 file changed

+59
-16
lines changed

1 file changed

+59
-16
lines changed

imutils/video/filevideostream.py

+59-16
Original file line numberDiff line numberDiff line change
@@ -6,72 +6,115 @@
66

77
# import the Queue class from Python 3
88
if sys.version_info >= (3, 0):
9-
from queue import Queue
9+
from queue import Queue, Full
1010

1111
# otherwise, import the Queue class for Python 2.7
1212
else:
1313
from Queue import Queue
1414

1515

16+
class EndOfStreamException(Exception):
17+
pass
18+
19+
1620
class FileVideoStream:
17-
def __init__(self, path, transform=None, queue_size=128):
21+
22+
def __init__(self, path, transform=None, queue_size=128, skip_frames=True, queue_put_timeout=0.5):
1823
# initialize the file video stream along with the boolean
1924
# used to indicate if the thread should be stopped or not
25+
self.queue_put_timeout = queue_put_timeout
26+
self.skip_frames = skip_frames
2027
self.stream = cv2.VideoCapture(path)
28+
self.total_frames = self.stream.get(cv2.CAP_PROP_FRAME_COUNT)
29+
self.skipped_frames = 0
2130
self.stopped = False
2231
self.transform = transform
2332

2433
# initialize the queue used to store frames read from
2534
# the video file
2635
self.Q = Queue(maxsize=queue_size)
27-
# intialize thread
36+
# initialize thread
2837
self.thread = Thread(target=self.update, args=())
2938
self.thread.daemon = True
3039

40+
def check_eos(self):
41+
if self.stream.get(cv2.CAP_PROP_POS_FRAMES) == self.total_frames:
42+
# If the number of captured frames is equal to the total number of frames,
43+
# we stop
44+
self.stop()
45+
raise EndOfStreamException("End of stream")
46+
3147
def start(self):
3248
# start a thread to read frames from the file video stream
3349
self.thread.start()
3450
return self
3551

52+
def get_frame(self):
53+
# grab the current frame
54+
flag, frame = self.stream.read()
55+
56+
while not flag and self.skip_frames:
57+
self.check_eos()
58+
# if self.skipped_frames == 0:
59+
# print(f"Skipping frame(s)")
60+
self.skipped_frames += 1
61+
flag, frame = self.stream.read()
62+
if self.skipped_frames > 0:
63+
# print(f"Resuming video...")
64+
self.skipped_frames = 0
65+
66+
return flag, frame
67+
3668
def update(self):
3769
# keep looping infinitely
3870
while True:
3971
# if the thread indicator variable is set, stop the
4072
# thread
4173
if self.stopped:
4274
break
43-
44-
# otherwise, ensure the queue has room in it
45-
if not self.Q.full():
75+
try:
4676
# read the next frame from the file
47-
(grabbed, frame) = self.stream.read()
48-
77+
grabbed, frame = self.get_frame()
4978
# if the `grabbed` boolean is `False`, then we have
5079
# reached the end of the video file
5180
if not grabbed:
5281
self.stopped = True
53-
82+
raise EndOfStreamException()
83+
5484
# if there are transforms to be done, might as well
5585
# do them on producer thread before handing back to
56-
# consumer thread. ie. Usually the producer is so far
86+
# consumer thread. i.e. Usually the producer is so far
5787
# ahead of consumer that we have time to spare.
5888
#
5989
# Python is not parallel but the transform operations
60-
# are usually OpenCV native so release the GIL.
90+
# are typically OpenCV native so release the GIL.
6191
#
6292
# Really just trying to avoid spinning up additional
6393
# native threads and overheads of additional
6494
# producer/consumer queues since this one was generally
6595
# idle grabbing frames.
6696
if self.transform:
6797
frame = self.transform(frame)
98+
while True:
99+
try:
100+
# try to put it into the queue
101+
self.Q.put(frame, True, self.queue_put_timeout)
102+
break
103+
except Full:
104+
# after timeout seconds check if we are still not stopped
105+
if self.stopped:
106+
raise EndOfStreamException()
107+
except EndOfStreamException:
108+
# if we got stopped or reached eos
109+
self.stopped = True
110+
break
111+
self.stream.release()
68112

69-
# add the frame to the queue
70-
self.Q.put(frame)
71-
else:
72-
time.sleep(0.1) # Rest for 10ms, we have a full queue
113+
def dim(self):
114+
width = int(self.stream.get(cv2.CAP_PROP_FRAME_WIDTH))
115+
height = int(self.stream.get(cv2.CAP_PROP_FRAME_HEIGHT))
73116

74-
self.stream.release()
117+
return [width, height]
75118

76119
def read(self):
77120
# return next frame in the queue

0 commit comments

Comments
 (0)