Skip to content

Commit f8599ee

Browse files
committed
v1.1.7
1 parent cfb6d42 commit f8599ee

File tree

6 files changed

+73
-12
lines changed

6 files changed

+73
-12
lines changed

build/lib/pcv/vidIO.py

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def _construct_open_args(self):
8686
if self.api_preference is not None:
8787
args = [args[0], self.api_preference, *args[1:]]
8888
return args
89-
89+
9090
def __enter__(self):
9191
''' Re-entrant '''
9292
if not self.isOpened():
@@ -137,7 +137,7 @@ def suggested_codec(cls, filename, exclude=[]):
137137

138138
@classmethod
139139
def from_camera(cls, filename, camera, fourcc=None, isColor=True,
140-
apiPreference=None, fps=-3):
140+
apiPreference=None, fps=-3, **kwargs):
141141
''' Returns a VideoWriter based on the properties of the input camera.
142142
143143
'filename' is the name of the file to save to.
@@ -150,6 +150,7 @@ def from_camera(cls, filename, camera, fourcc=None, isColor=True,
150150
If no processing is occurring, 'camera' is suggested, otherwise
151151
it is generally best to measure the frame output.
152152
Defaults to -3, to measure over 3 frames.
153+
'kwrags' are any additional keyword arguments for initialisation.
153154
154155
'''
155156
if fourcc is None:
@@ -160,8 +161,9 @@ def from_camera(cls, filename, camera, fourcc=None, isColor=True,
160161
fps = camera.get('fps')
161162
elif fps < 0:
162163
fps = camera.measure_framerate(-fps)
163-
164-
return cls(filename, fourcc, fps, frameSize, isColor, apiPreference)
164+
165+
return cls(filename, fourcc, fps, frameSize, isColor, apiPreference,
166+
**kwargs)
165167

166168
def __repr__(self):
167169
return (f'{self.__class__.__name__}(filename={repr(self.filename)}, '
@@ -197,7 +199,7 @@ def __init__(self, *args, maxsize=0, verbose_exit=True, **kwargs):
197199
self._verbose_exit = verbose_exit
198200

199201
def _initialise_writer(self, maxsize):
200-
''' Start the Thread for grabbing images. '''
202+
''' Start the Thread for writing images from the queue. '''
201203
self.max_queue_size = maxsize
202204
self._write_queue = Queue(maxsize=maxsize)
203205
self._image_writer = Thread(name='writer', target=self._writer,
@@ -240,6 +242,57 @@ def __exit__(self, *args):
240242
print(f'Writing complete in {perf_counter()-waited:.3f}s.')
241243

242244

245+
class GuaranteedVideoWriter(VideoWriter):
246+
''' A VideoWriter with guaranteed output FPS.
247+
248+
Repeats frames when input too slow, and skips frames when input too fast.
249+
250+
'''
251+
def _initialise_writer(self, maxsize):
252+
''' Start the write-queue putter and getter threads. '''
253+
super()._initialise_writer(maxsize)
254+
self._period = 1 / self.fps
255+
self.latest = None
256+
self._finished = Event()
257+
self._looper = Thread(name='looper', target=self._write_loop,
258+
daemon=True)
259+
self._looper.start()
260+
261+
def _write_loop(self):
262+
''' Write the latest frame to the queue, at self.fps.
263+
264+
Repeats frames when input too slow, and skips frames when input too fast.
265+
266+
'''
267+
# wait until first image set, or early finish
268+
while self.latest is None and not self._finished.is_set():
269+
sleep(self._period / 2)
270+
prev = perf_counter()
271+
self._error = 0
272+
delay = self._period - 5e-3
273+
274+
# write frames at specified rate until told to stop
275+
while not self._finished.is_set():
276+
super().write(self.latest)
277+
new = perf_counter()
278+
self._error += self._period - (new - prev)
279+
delay -= self._error
280+
delay = max(delay, 0) # can't go back in time
281+
sleep(delay)
282+
prev = new
283+
284+
def write(self, img):
285+
''' Set the latest image. '''
286+
self.latest = img
287+
288+
def __exit__(self, *args):
289+
self._finished.set()
290+
self._looper.join()
291+
if self._verbose_exit:
292+
print(f'Net timing error = {self._error * 1e3:.3f}ms')
293+
super().__exit__(*args)
294+
295+
243296
class OutOfFrames(StopIteration):
244297
def __init__(msg='Out of video frames', *args, **kwargs):
245298
super().__init__(msg, *args, **kwargs)
@@ -407,7 +460,8 @@ def headless_stream(self):
407460
for read_success, frame in self:
408461
if not read_success: break # camera disconnected
409462

410-
def record_stream(self, filename, show=True, mouse_handler=DoNothing()):
463+
def record_stream(self, filename, show=True, mouse_handler=DoNothing(),
464+
writer=VideoWriter):
411465
''' Capture and record stream, with optional display.
412466
413467
'filename' is the file to save to.
@@ -416,9 +470,12 @@ def record_stream(self, filename, show=True, mouse_handler=DoNothing()):
416470
'mouse_handler' is an optional MouseCallback instance determining
417471
the effects of mouse clicks and moves during the stream. It is only
418472
useful if 'show' is set to True. Defaults to DoNothing.
473+
'writer' is a subclass of VideoWriter. Defaults to VideoWriter.
474+
Set to GuaranteedVideoWriter to allow repeated and skipped frames
475+
to better ensure a consistent output framerate.
419476
420477
'''
421-
with VideoWriter.from_camera(filename, self) as writer, mouse_handler:
478+
with writer.from_camera(filename, self) as writer, mouse_handler:
422479
for read_success, frame in self:
423480
if read_success:
424481
if show:
@@ -876,7 +933,7 @@ def step_back(vid):
876933
# enable back-stepping if not currently permitted
877934
vid._skip_frames = 0
878935
# make sure no unnecessary prints trigger from playback keys
879-
vid._verbose = False
936+
vid._verbose = False
880937

881938
# go back a step
882939
vid._direction = vid.REVERSE_DIRECTION

dist/pythonic-cv-1.1.6.tar.gz

-28.5 KB
Binary file not shown.

dist/pythonic-cv-1.1.7.tar.gz

29.1 KB
Binary file not shown.
-27.1 KB
Binary file not shown.
27.6 KB
Binary file not shown.

pythonic_cv.egg-info/PKG-INFO

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
Metadata-Version: 2.1
22
Name: pythonic-cv
3-
Version: 1.1.6
3+
Version: 1.1.7
44
Summary: Performant pythonic wrapper of unnecessarily painful opencv functionality
55
Home-page: https://github.com/ES-Alexander/pythonic-cv
66
Author: ES-Alexander
77
Author-email: sandman.esalexander@gmail.com
88
License: UNKNOWN
99
Description: _________________________________
10-
Version: 1.1.6
10+
Version: 1.1.7
1111
Author: ES Alexander
12-
Release Date: 17/Oct/2020
12+
Release Date: 22/Dec/2020
1313
_________________________________
1414

1515
# About
@@ -27,7 +27,7 @@ Description: _________________________________
2727
This library requires an existing version of `OpenCV` with Python bindings to be
2828
installed (e.g. `python3 -m pip install opencv-python`). Some features (mainly
2929
property access helpers) may not work for versions of OpenCV earlier than 4.2.0.
30-
The library was tested using Python 3.7.2, and is expected to work down to at least
30+
The library was tested using Python 3.8.5, and is expected to work down to at least
3131
Python 3.4 (although the integrated advanced features example uses matmul (@) for
3232
some processing, which was introduced in Python 3.5).
3333

@@ -83,6 +83,10 @@ Description: _________________________________
8383
If using a video file to simulate a live camera stream, use `SlowCamera` or
8484
`LockedCamera` - `Camera` will skip frames.
8585

86+
There is also a `GuaranteedVideoWriter` class which guarantees the output framerate by
87+
repeating frames when given input too slowly, and skipping frames when input is too
88+
fast.
89+
8690
## Overview
8791
![Overview of classes diagram](https://github.com/ES-Alexander/pythonic-cv/blob/master/Overview.png)
8892

0 commit comments

Comments
 (0)