Pi camera is now persistent in StreamingCamera
This commit is contained in:
parent
61beb211ae
commit
a7598c5e5b
3 changed files with 140 additions and 114 deletions
|
|
@ -106,6 +106,15 @@ class BaseCamera(object):
|
|||
# Stop worker thread
|
||||
self.stop_worker()
|
||||
|
||||
def wait_for_camera(self, timeout=5):
|
||||
"""Wait for camera object, with 5 second timeout."""
|
||||
timeout_time = time.time() + timeout
|
||||
while not self.camera:
|
||||
if time.time() > timeout_time:
|
||||
raise TimeoutError("Timeout waiting for camera")
|
||||
else:
|
||||
pass
|
||||
|
||||
# START AND STOP WORKER THREAD
|
||||
|
||||
def start_worker(self, timeout: int=5) -> bool:
|
||||
|
|
@ -131,6 +140,7 @@ class BaseCamera(object):
|
|||
|
||||
def stop_worker(self, timeout: int=5) -> bool:
|
||||
"""Flags worker thread for stop. Waits for thread to close, or times out."""
|
||||
logging.debug("Stopping worker thread")
|
||||
timeout_time = time.time() + timeout
|
||||
|
||||
self.stop = True
|
||||
|
|
@ -200,9 +210,7 @@ class BaseCamera(object):
|
|||
def _thread(self):
|
||||
"""Camera background thread."""
|
||||
self.frames_iterator = self.frames()
|
||||
|
||||
# Update state
|
||||
self.state['stream_active'] = True
|
||||
logging.debug("Entering worker thread.")
|
||||
|
||||
for frame in self.frames_iterator:
|
||||
self.frame = frame
|
||||
|
|
@ -217,15 +225,13 @@ class BaseCamera(object):
|
|||
|
||||
try:
|
||||
if self.stop is True:
|
||||
logging.debug("Worker thread flagged for stop.")
|
||||
self.frames_iterator.close()
|
||||
break
|
||||
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
logging.debug("BaseCamera worker thread exiting...")
|
||||
# Reset thread
|
||||
self.thread = None
|
||||
# Destroy any stored camera object (used especially for handling Pi cameras)
|
||||
self.camera = None
|
||||
# Update state
|
||||
self.state['stream_active'] = False
|
||||
|
|
@ -59,6 +59,8 @@ class StreamingCamera(BaseCamera):
|
|||
"""Raspberry Pi camera implementation of StreamingCamera."""
|
||||
# Run BaseCamera init
|
||||
BaseCamera.__init__(self)
|
||||
# Attach to Pi camera
|
||||
self.camera = picamera.PiCamera()
|
||||
|
||||
# Camera settings
|
||||
self.settings.update({
|
||||
|
|
@ -83,7 +85,11 @@ class StreamingCamera(BaseCamera):
|
|||
|
||||
def close(self):
|
||||
"""Close the Raspberry Pi StreamingCamera"""
|
||||
BaseCamera.close(self) # Run BaseCamera close method
|
||||
# Run BaseCamera close method
|
||||
BaseCamera.close(self)
|
||||
# Detatch Pi camera
|
||||
if self.camera:
|
||||
self.camera.close()
|
||||
|
||||
# HANDLE SETTINGS
|
||||
|
||||
|
|
@ -96,53 +102,69 @@ class StreamingCamera(BaseCamera):
|
|||
"""Open config_picamera.yaml file and write to camera."""
|
||||
global DEFAULT_CONFIG
|
||||
|
||||
self.start_worker()
|
||||
paused_stream = False
|
||||
|
||||
if not self.state['record_active']: # If not recording a video
|
||||
|
||||
#self.start_worker()
|
||||
|
||||
if self.state['stream_active']: # If stream is active
|
||||
logging.info("Pausing stream to update settings.")
|
||||
self.pause_stream_for_capture() # Pause stream
|
||||
paused_stream = True # Remember to unpause stream when done
|
||||
|
||||
if not config_path:
|
||||
config = load_config(DEFAULT_CONFIG)
|
||||
else:
|
||||
config = load_config(config_path)
|
||||
|
||||
logging.debug(config)
|
||||
|
||||
# StreamingCamera settings
|
||||
if 'video_resolution' in config:
|
||||
self.settings['video_resolution'] = config['video_resolution']
|
||||
if 'image_resolution' in config:
|
||||
self.settings['image_resolution'] = config['image_resolution']
|
||||
if 'numpy_resolution' in config:
|
||||
self.settings['numpy_resolution'] = config['numpy_resolution']
|
||||
|
||||
if 'jpeg_quality' in config:
|
||||
self.settings['jpeg_quality'] = config['jpeg_quality']
|
||||
if 'framerate' in config:
|
||||
self.settings['framerate'] = config['framerate']
|
||||
|
||||
# Camera AWB
|
||||
if 'awb_mode' in config:
|
||||
self.camera.awb_mode = config['awb_mode']
|
||||
if 'red_gain' in config and 'blue_gain' in config:
|
||||
self.camera.awb_gains = (config['red_gain'], config['blue_gain'])
|
||||
|
||||
# Camera framerate
|
||||
if 'framerate' in config:
|
||||
self.camera.framerate = config['framerate']
|
||||
# Camera exposure
|
||||
if 'shutter_speed' in config:
|
||||
self.camera.shutter_speed = config['shutter_speed']
|
||||
if 'saturation' in config:
|
||||
self.camera.saturation = config['saturation']
|
||||
# Camera misc.
|
||||
self.camera.led = False
|
||||
# Richard's library to set analog and digital gains
|
||||
if 'analog_gain' in config:
|
||||
set_analog_gain(self.camera, config['analog_gain'])
|
||||
if 'digital_gain' in config:
|
||||
set_digital_gain(self.camera, config['digital_gain'])
|
||||
|
||||
if paused_stream: # If stream was paused to update settings
|
||||
logging.info("Resuming stream.")
|
||||
self.resume_stream_for_capture()
|
||||
|
||||
if not config_path:
|
||||
config = load_config(DEFAULT_CONFIG)
|
||||
else:
|
||||
config = load_config(config_path)
|
||||
|
||||
logging.debug(config)
|
||||
|
||||
# StreamingCamera settings
|
||||
if 'video_resolution' in config:
|
||||
self.settings['video_resolution'] = config['video_resolution']
|
||||
if 'image_resolution' in config:
|
||||
self.settings['image_resolution'] = config['image_resolution']
|
||||
if 'numpy_resolution' in config:
|
||||
self.settings['numpy_resolution'] = config['numpy_resolution']
|
||||
|
||||
if 'jpeg_quality' in config:
|
||||
self.settings['jpeg_quality'] = config['jpeg_quality']
|
||||
if 'framerate' in config:
|
||||
self.settings['framerate'] = config['framerate']
|
||||
|
||||
# Camera AWB
|
||||
if 'awb_mode' in config:
|
||||
self.camera.awb_mode = config['awb_mode']
|
||||
if 'red_gain' in config and 'blue_gain' in config:
|
||||
self.camera.awb_gains = (config['red_gain'], config['blue_gain'])
|
||||
|
||||
# Camera framerate
|
||||
if 'framerate' in config:
|
||||
self.camera.framerate = config['framerate']
|
||||
# Camera exposure
|
||||
if 'shutter_speed' in config:
|
||||
self.camera.shutter_speed = config['shutter_speed']
|
||||
if 'saturation' in config:
|
||||
self.camera.saturation = config['saturation']
|
||||
# Camera misc.
|
||||
self.camera.led = False
|
||||
# Richard's library to set analog and digital gains
|
||||
if 'analog_gain' in config:
|
||||
set_analog_gain(self.camera, config['analog_gain'])
|
||||
if 'digital_gain' in config:
|
||||
set_digital_gain(self.camera, config['digital_gain'])
|
||||
raise Exception("Cannot update camera settings while recording is active.")
|
||||
|
||||
def change_zoom(self, zoom_value: int=1) -> None:
|
||||
"""Change the camera zoom, handling recentering and scaling."""
|
||||
self.start_worker()
|
||||
#self.start_worker()
|
||||
|
||||
zoom_value = float(zoom_value)
|
||||
if zoom_value < 1:
|
||||
|
|
@ -165,7 +187,7 @@ class StreamingCamera(BaseCamera):
|
|||
|
||||
def start_preview(self) -> bool:
|
||||
"""Start the onboard GPU camera preview."""
|
||||
self.start_worker()
|
||||
#self.start_worker()
|
||||
|
||||
self.camera.start_preview()
|
||||
self.state['preview_active'] = True
|
||||
|
|
@ -173,7 +195,7 @@ class StreamingCamera(BaseCamera):
|
|||
|
||||
def stop_preview(self) -> bool:
|
||||
"""Stop the onboard GPU camera preview."""
|
||||
self.start_worker()
|
||||
#self.start_worker()
|
||||
|
||||
self.camera.stop_preview()
|
||||
self.state['preview_active'] = False
|
||||
|
|
@ -202,7 +224,7 @@ class StreamingCamera(BaseCamera):
|
|||
|
||||
# Start recording method only if a current recording is not running
|
||||
if not self.state['record_active']:
|
||||
self.start_worker()
|
||||
#self.start_worker()
|
||||
|
||||
# If no target is specified, store to StreamingCamera
|
||||
if not target:
|
||||
|
|
@ -319,7 +341,7 @@ class StreamingCamera(BaseCamera):
|
|||
(default 'h264')
|
||||
resize ((int, int)): Resize the captured image.
|
||||
"""
|
||||
self.start_worker()
|
||||
#self.start_worker()
|
||||
|
||||
# If no target is specified, store to StreamingCamera
|
||||
if not target:
|
||||
|
|
@ -367,7 +389,7 @@ class StreamingCamera(BaseCamera):
|
|||
use_video_port (bool): Capture from the video port used for streaming (lower resolution, faster)
|
||||
resize ((int, int)): Resize the captured image.
|
||||
"""
|
||||
self.start_worker()
|
||||
#self.start_worker()
|
||||
|
||||
if use_video_port:
|
||||
resolution = self.settings['video_resolution']
|
||||
|
|
@ -415,24 +437,28 @@ class StreamingCamera(BaseCamera):
|
|||
# Run this initialisation method
|
||||
self.initialisation()
|
||||
|
||||
with picamera.PiCamera() as self.camera:
|
||||
# Let camera warm up
|
||||
time.sleep(0.1)
|
||||
# Settings config
|
||||
self.update_settings()
|
||||
# Set stream resolution
|
||||
self.camera.resolution = self.settings['video_resolution']
|
||||
self.wait_for_camera()
|
||||
|
||||
# streaming
|
||||
self.stream = io.BytesIO()
|
||||
# Settings config
|
||||
self.update_settings()
|
||||
# Set stream resolution
|
||||
self.camera.resolution = self.settings['video_resolution']
|
||||
|
||||
# start recording on video splitter port 1
|
||||
self.camera.start_recording(
|
||||
self.stream,
|
||||
format='mjpeg',
|
||||
quality=self.settings['jpeg_quality'],
|
||||
splitter_port=1)
|
||||
# Create stream
|
||||
self.stream = io.BytesIO()
|
||||
|
||||
# Start recording on video splitter port 1
|
||||
self.camera.start_recording(
|
||||
self.stream,
|
||||
format='mjpeg',
|
||||
quality=self.settings['jpeg_quality'],
|
||||
splitter_port=1)
|
||||
|
||||
# Update state
|
||||
logging.debug("STREAM ACTIVE")
|
||||
self.state['stream_active'] = True
|
||||
|
||||
try: # While the iterator is not closed
|
||||
while True:
|
||||
# reset stream for next frame
|
||||
self.stream.seek(0)
|
||||
|
|
@ -441,8 +467,14 @@ class StreamingCamera(BaseCamera):
|
|||
time.sleep(1/self.settings['framerate']*0.1)
|
||||
# yield the result to be read
|
||||
frame = self.stream.getvalue()
|
||||
|
||||
# ensure the size of package is right
|
||||
if len(frame) == 0:
|
||||
pass
|
||||
else:
|
||||
yield frame
|
||||
finally: # When GeneratorExit or StopIteration raised, run cleanup code
|
||||
logging.debug("Stopping stream recording on port 1")
|
||||
self.camera.stop_recording(splitter_port=1)
|
||||
self.state['stream_active'] = False
|
||||
logging.debug("FRAME ITERATOR END")
|
||||
|
|
|
|||
|
|
@ -26,16 +26,6 @@ success_string = """
|
|||
"""
|
||||
|
||||
|
||||
def wait_for_cam(camera, timeout=5):
|
||||
"""Wait for camera object in global namespace, with 5 second timeout."""
|
||||
timeout_time = time.time() + timeout
|
||||
while not camera:
|
||||
if time.time() > timeout_time:
|
||||
raise TimeoutError("Timeout waiting for camera")
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
class TestCaptureMethods(unittest.TestCase):
|
||||
|
||||
def test_still_capture(self):
|
||||
|
|
@ -46,7 +36,7 @@ class TestCaptureMethods(unittest.TestCase):
|
|||
for resize in [None, (640, 480)]:
|
||||
|
||||
# Wait for camera
|
||||
wait_for_cam(camera)
|
||||
camera.wait_for_camera()
|
||||
|
||||
# Capture to a context (auto-deletes files when done)
|
||||
with camera.capture(
|
||||
|
|
@ -114,7 +104,7 @@ class TestCaptureMethods(unittest.TestCase):
|
|||
for use_video_port in [True, False]:
|
||||
for resize in [None, (640, 480)]:
|
||||
# Wait for camera
|
||||
wait_for_cam(camera)
|
||||
camera.wait_for_camera()
|
||||
|
||||
# Capture
|
||||
stream = camera.capture(
|
||||
|
|
@ -151,7 +141,7 @@ class TestUnencodedMethods(unittest.TestCase):
|
|||
print("{}, {}, {}".format(id, resize, use_video_port))
|
||||
|
||||
# Wait for camera
|
||||
wait_for_cam(camera)
|
||||
camera.wait_for_camera()
|
||||
|
||||
# Capture RGB array
|
||||
yuv = camera.array(
|
||||
|
|
@ -183,7 +173,7 @@ class TestUnencodedMethods(unittest.TestCase):
|
|||
print("{}, {}, {}".format(id, resize, use_video_port))
|
||||
|
||||
# Wait for camera
|
||||
wait_for_cam(camera)
|
||||
camera.wait_for_camera()
|
||||
|
||||
# Capture RGB array
|
||||
rgb = camera.array(
|
||||
|
|
@ -216,7 +206,7 @@ class TestRecordMethods(unittest.TestCase):
|
|||
for write_to_file in [True, False, None]:
|
||||
|
||||
# Wait for camera
|
||||
wait_for_cam(camera)
|
||||
camera.wait_for_camera()
|
||||
|
||||
# Start recording
|
||||
with camera.start_recording(
|
||||
|
|
@ -249,7 +239,7 @@ class TestRecordMethods(unittest.TestCase):
|
|||
global camera
|
||||
|
||||
# Wait for camera
|
||||
wait_for_cam(camera)
|
||||
camera.wait_for_camera()
|
||||
|
||||
# Start recording
|
||||
stream = camera.start_recording()
|
||||
|
|
@ -270,52 +260,50 @@ class TestRecordMethods(unittest.TestCase):
|
|||
|
||||
|
||||
class TestThreadStarting(unittest.TestCase):
|
||||
def test_capture_restarting_stream(self):
|
||||
def test_restarting_stream(self):
|
||||
"""Tests that a capture call restarts the camera worker thread."""
|
||||
global camera
|
||||
|
||||
# Wait for camera
|
||||
wait_for_cam(camera)
|
||||
camera.wait_for_camera()
|
||||
|
||||
# Force-stop the camera worker thread (should return True)
|
||||
self.assertTrue(camera.stop_worker())
|
||||
|
||||
# Check Pi camera has been disconnected
|
||||
self.assertFalse(camera.camera)
|
||||
self.assertTrue(camera.camera)
|
||||
self.assertFalse(camera.thread)
|
||||
|
||||
# Restart worker thread by initialising a capture
|
||||
with camera.capture(
|
||||
use_video_port=True) as stream:
|
||||
# Restart worker thread
|
||||
self.assertTrue(camera.start_worker())
|
||||
|
||||
# Ensure StreamObject 'stream' has
|
||||
# a valid BytesIO object and byte string
|
||||
self.assertTrue(isinstance(stream.data, io.IOBase))
|
||||
self.assertTrue(isinstance(stream.binary, (bytes, bytearray)))
|
||||
self.assertTrue(camera.camera)
|
||||
self.assertTrue(camera.thread)
|
||||
self.assertIsInstance(camera.stream, io.IOBase)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
camera = None
|
||||
with StreamingCamera() as camera:
|
||||
camera = StreamingCamera()
|
||||
|
||||
suites = [
|
||||
unittest.TestLoader().loadTestsFromTestCase(TestCaptureMethods),
|
||||
unittest.TestLoader().loadTestsFromTestCase(TestUnencodedMethods),
|
||||
unittest.TestLoader().loadTestsFromTestCase(TestThreadStarting),
|
||||
unittest.TestLoader().loadTestsFromTestCase(TestRecordMethods),
|
||||
]
|
||||
suites = [
|
||||
unittest.TestLoader().loadTestsFromTestCase(TestCaptureMethods),
|
||||
unittest.TestLoader().loadTestsFromTestCase(TestUnencodedMethods),
|
||||
unittest.TestLoader().loadTestsFromTestCase(TestThreadStarting),
|
||||
unittest.TestLoader().loadTestsFromTestCase(TestRecordMethods),
|
||||
]
|
||||
|
||||
alltests = unittest.TestSuite(suites)
|
||||
alltests = unittest.TestSuite(suites)
|
||||
|
||||
result = unittest.TextTestRunner(verbosity=2).run(alltests)
|
||||
result = unittest.TextTestRunner(verbosity=2).run(alltests)
|
||||
|
||||
print("Objects created during tests:")
|
||||
# Show us what was captured
|
||||
for im in camera.images:
|
||||
pprint(im.metadata)
|
||||
for im in camera.videos:
|
||||
pprint(im.metadata)
|
||||
print("Objects created during tests:")
|
||||
# Show us what was captured
|
||||
for im in camera.images:
|
||||
pprint(im.metadata)
|
||||
for im in camera.videos:
|
||||
pprint(im.metadata)
|
||||
|
||||
if result.wasSuccessful():
|
||||
print(success_string)
|
||||
if result.wasSuccessful():
|
||||
print(success_string)
|
||||
|
||||
camera.close()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue