Smarter application of picamera settings

This commit is contained in:
Joel Collins 2019-06-05 16:04:14 +01:00
parent 9b30d1d38f
commit ea253f8085

View file

@ -12,7 +12,7 @@ Video port:
Splitter port 2: Video capture
Splitter port 3: [Currently unused]
StreamingCamera streams at stream_resolution
StreamingCamera streams at video_resolution
Camera capture resolution set to stream_resolution in frames()
Video port uses that resolution for everything. If a different resolution
is specified for video capture, this is handled by the resizer.
@ -39,29 +39,21 @@ from .base import BaseCamera, CaptureObject
# Richard's fix gain
from .set_picamera_gain import set_analog_gain, set_digital_gain
# Handle config and picamera settings
CONFIG_KEYS = {
'stream_resolution': tuple,
'image_resolution': tuple,
'numpy_resolution': tuple,
'analog_gain': float,
'digital_gain': float,
'jpeg_quality': int,
'picamera_settings': {
'exposure_mode': str,
'awb_mode': str,
'awb_gains': tuple,
'framerate': int,
'shutter_speed': float,
'saturation': float,
'lens_shading_table': np.ndarray,
},
}
# MAIN CLASS
class StreamingCamera(BaseCamera):
"""Raspberry Pi camera implementation of StreamingCamera."""
picamera_settings_keys = [
'exposure_mode',
'analog_gain',
'digital_gain',
'shutter_speed',
'awb_gains',
'awb_mode',
'framerate',
'saturation',
'lens_shading_table'
]
def __init__(self):
# Run BaseCamera init
@ -103,61 +95,28 @@ class StreamingCamera(BaseCamera):
self.camera.close()
# HANDLE SETTINGS
@property
def supports_lens_shading(self):
"""Determine whether the picamera module supports lens shading.
As of March 2018, picamera did not wrap the necessary MMAL commands to
set the lens shading table, or to write the value of analog or digital
gain. I have a forked version of the library that does support these.
For ease of use by people who don't want those features, this library
does not have a hard dependency on lens shading. However, we need to
check in some places whether it's available.
"""
return hasattr(self.camera, "lens_shading_table")
@property
def digital_gain(self):
# TODO: Handle missing picamera?
return self.camera.digital_gain
@digital_gain.setter
def digital_gain(self, value):
set_digital_gain(self.camera, value)
@property
def analog_gain(self):
# TODO: Handle missing picamera?
return self.camera.analog_gain
@analog_gain.setter
def analog_gain(self, value):
set_analog_gain(self.camera, value)
def read_config(self):
"""
Return config dictionary of the StreamingCamera.
"""
global CONFIG_KEYS
conf_dict = {
'stream_resolution': self.stream_resolution,
'image_resolution': self.image_resolution,
'numpy_resolution': self.numpy_resolution,
'jpeg_quality': self.jpeg_quality,
'picamera_settings': {},
}
# PiCamera parameters
for key in CONFIG_KEYS['picamera_settings']:
for key in StreamingCamera.picamera_settings_keys:
try:
value = getattr(self.camera, key)
logging.debug("{}: {}".format(key, value))
logging.debug("Reading PiCamera().{}: {}".format(key, value))
conf_dict['picamera_settings'][key] = value
except AttributeError:
logging.debug("Unable to read PiCamera attribute {}".format(key))
# StreamingCamera parameters
for key in CONFIG_KEYS:
if (key != 'picamera_settings') and hasattr(self, key):
conf_dict[key] = getattr(self, key)
return conf_dict
def apply_config(self, config: dict) -> None:
@ -170,11 +129,10 @@ class StreamingCamera(BaseCamera):
Args:
config (dict): Dictionary of config parameters.
"""
global CONFIG_KEYS
# TODO: Include timing and batching logic when applying PiCamera settings
paused_stream = False
logging.debug("Applying config:")
logging.debug("StreamingCamera: Applying config:")
logging.debug(config)
with self.lock:
@ -182,9 +140,6 @@ class StreamingCamera(BaseCamera):
# Apply valid config params to Picamera object
if not self.state['record_active']: # If not recording a video
logging.debug("Applying config:")
logging.debug(config)
# Pause stream while changing settings
if self.state['stream_active']: # If stream is active
logging.info("Pausing stream to update config.")
@ -193,19 +148,11 @@ class StreamingCamera(BaseCamera):
# PiCamera parameters
if 'picamera_settings' in config: # If new settings are given
for key, value in config['picamera_settings'].items(): # For each given setting
if hasattr(self.camera, key):
if key not in CONFIG_KEYS['picamera_settings']:
CONFIG_KEYS['picamera_settings'][key] = type(value)
logging.debug("Setting parameter {}: {}".format(key, config['picamera_settings'][key]))
setattr(self.camera, key, value) # Write setting to camera
self.apply_picamera_settings(config['picamera_settings'], pause_for_effect=True)
# StreamingCamera parameters
for key, value in config.items(): # For each provided setting
if (key != 'picamera_settings') and hasattr(self, key):
if key not in CONFIG_KEYS:
CONFIG_KEYS[key] = type(value)
logging.debug("Setting parameter {}: {}".format(key, value))
setattr(self, key, value)
# If stream was paused to update config, unpause
@ -217,6 +164,53 @@ class StreamingCamera(BaseCamera):
raise Exception(
"Cannot update camera config while recording is active.")
def apply_picamera_settings(self, settings_dict: dict, pause_for_effect: bool=True):
# Set exposure mode
if 'exposure_mode' in settings_dict:
logging.debug("Applying exposure_mode: {}".format(settings_dict['exposure_mode']))
self.camera.exposure_mode = settings_dict['exposure_mode']
# Apply gains and let them settle
if 'analog_gain' in settings_dict:
logging.debug("Applying analog_gain: {}".format(settings_dict['analog_gain']))
set_analog_gain(self.camera, settings_dict['analog_gain'])
if 'digital_gain' in settings_dict:
logging.debug("Applying digital_gain: {}".format(settings_dict['digital_gain']))
set_digital_gain(self.camera, settings_dict['digital_gain'])
# Apply shutter speed
if 'shutter_speed' in settings_dict:
logging.debug("Applying shutter_speed: {}".format(settings_dict['shutter_speed']))
self.camera.shutter_speed = settings_dict['shutter_speed']
time.sleep(0.2) # Let gains settle
# Handle AWB in a half-smart way
if 'awb_gains' in settings_dict:
logging.debug("Applying awb_mode: off")
self.camera.awb_mode = 'off'
logging.debug("Applying awb_gains: {}".format(settings_dict['awb_gains']))
self.camera.awb_gains = settings_dict['awb_gains']
elif 'awb_mode' in settings_dict:
logging.debug("Applying awb_mode: {}".format(settings_dict['awb_mode']))
self.camera.awb_mode = settings_dict['awb_mode']
# Handle some properties that can be quickly applied
batched_keys = ['framerate', 'saturation']
for key in batched_keys:
if (key in settings_dict) and hasattr(self.camera, key):
logging.debug("Applying {}: {}".format(key, settings_dict[key]))
setattr(self.camera, key, settings_dict[key])
# Handle lens shading if camera supports it
if ('lens_shading_table' in settings_dict) and hasattr(self.camera, 'lens_shading_table'):
logging.debug("Applying lens_shading_table: {}".format(settings_dict['lens_shading_table']))
self.camera.lens_shading_table = settings_dict['lens_shading_table']
# Final optional pause to settle
if pause_for_effect:
time.sleep(0.2)
def set_zoom(self, zoom_value: float = 1.) -> None:
"""
Change the camera zoom, handling re-centering and scaling.