Message ID | 20250123114204.79321-13-stefan.klug@ideasonboard.com |
---|---|
State | New |
Headers | show |
Series |
|
Related | show |
On Thu, Jan 23, 2025 at 12:41:02PM +0100, Stefan Klug wrote: > For the lux algorithm, reference values get calculated based on a tuning > image taken at a known lux level. The reference data contains the mean Y > of the image, lux level, exposure time, gain and aperture. This module > calculates these values for insertion into the tuning file. > > Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com> > > --- > > Changes in v2: > - Added this patch > --- > .../tuning/libtuning/modules/lux/__init__.py | 6 ++ > utils/tuning/libtuning/modules/lux/lux.py | 70 +++++++++++++++++++ > utils/tuning/libtuning/modules/lux/rkisp1.py | 22 ++++++ > 3 files changed, 98 insertions(+) > create mode 100644 utils/tuning/libtuning/modules/lux/__init__.py > create mode 100644 utils/tuning/libtuning/modules/lux/lux.py > create mode 100644 utils/tuning/libtuning/modules/lux/rkisp1.py > > diff --git a/utils/tuning/libtuning/modules/lux/__init__.py b/utils/tuning/libtuning/modules/lux/__init__.py > new file mode 100644 > index 000000000000..af9d4e08e64f > --- /dev/null > +++ b/utils/tuning/libtuning/modules/lux/__init__.py > @@ -0,0 +1,6 @@ > +# SPDX-License-Identifier: GPL-2.0-or-later > +# > +# Copyright (C) 2025, Ideas on Board > + > +from libtuning.modules.lux.lux import Lux > +from libtuning.modules.lux.rkisp1 import LuxRkISP1 > diff --git a/utils/tuning/libtuning/modules/lux/lux.py b/utils/tuning/libtuning/modules/lux/lux.py > new file mode 100644 > index 000000000000..5cf7d3baeb5d > --- /dev/null > +++ b/utils/tuning/libtuning/modules/lux/lux.py > @@ -0,0 +1,70 @@ > +# SPDX-License-Identifier: GPL-2.0-or-later > +# > +# Copyright (C) 2019, Raspberry Pi Ltd > +# Copyright (C) 2025, Ideas on Board > +# > +# Base Lux tuning module > + > +from ..module import Module > + > +import logging > +import numpy as np > + > +logger = logging.getLogger(__name__) > + > + > +class Lux(Module): > + type = 'lux' > + hr_name = 'LUX (Base)' s/LUX/Lux/ The rest looks sound to me. Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> > + out_name = 'GenericLux' > + > + def __init__(self, debug: list): > + super().__init__() > + > + self.debug = debug > + > + def calculate_lux_reference_values(self, images): > + # The lux calibration is done on a single image. For best effects, the > + # image with lux level closest to 1000 is chosen. > + imgs = [img for img in images if img.macbeth is not None] > + lux_values = [img.lux for img in imgs] > + index = lux_values.index(min(lux_values, key=lambda l: abs(1000 - l))) > + img = imgs[index] > + logger.info(f'Selected image {img.name} for lux calibration') > + > + if img.lux < 50: > + logger.warning(f'A Lux level of {img.lux} is very low for proper lux calibration') > + > + ref_y = self.calculate_y(img) > + exposure_time = img.exposure > + gain = img.againQ8_norm > + aperture = 1 > + logger.info(f'RefY:{ref_y} Exposure time:{exposure_time}µs Gain:{gain} Aperture:{aperture}') > + return {'referenceY': ref_y, > + 'referenceExposureTime': exposure_time, > + 'referenceAnalogueGain': gain, > + 'referenceDigitalGain': 1.0, > + 'referenceLux': img.lux} > + > + def calculate_y(self, img): > + max16Bit = 0xffff > + # Average over all grey patches. > + ap_r = np.mean(img.patches[0][3::4]) / max16Bit > + ap_g = (np.mean(img.patches[1][3::4]) + np.mean(img.patches[2][3::4])) / 2 / max16Bit > + ap_b = np.mean(img.patches[3][3::4]) / max16Bit > + logger.debug(f'Averaged grey patches: Red: {ap_r}, Green: {ap_g}, Blue: {ap_b}') > + > + # Calculate white balance gains. > + gr = ap_g / ap_r > + gb = ap_g / ap_b > + logger.debug(f'WB gains: Red: {gr} Blue: {gb}') > + > + # Calculate the mean Y value of the whole image > + a_r = np.mean(img.channels[0]) * gr > + a_g = (np.mean(img.channels[1]) + np.mean(img.channels[2])) / 2 > + a_b = np.mean(img.channels[3]) * gb > + y = 0.299 * a_r + 0.587 * a_g + 0.114 * a_b > + y /= max16Bit > + > + return y > + > diff --git a/utils/tuning/libtuning/modules/lux/rkisp1.py b/utils/tuning/libtuning/modules/lux/rkisp1.py > new file mode 100644 > index 000000000000..62d3f94cabd8 > --- /dev/null > +++ b/utils/tuning/libtuning/modules/lux/rkisp1.py > @@ -0,0 +1,22 @@ > +# SPDX-License-Identifier: GPL-2.0-or-later > +# > +# Copyright (C) 2024, Ideas on Board > +# > +# Lux module for tuning rkisp1 > + > +from .lux import Lux > + > + > +class LuxRkISP1(Lux): > + hr_name = 'Lux (RkISP1)' > + out_name = 'Lux' > + > + def __init__(self, **kwargs): > + super().__init__(**kwargs) > + > + # We don't need anything from the config file. > + def validate_config(self, config: dict) -> bool: > + return True > + > + def process(self, config: dict, images: list, outputs: dict) -> dict: > + return self.calculate_lux_reference_values(images) > -- > 2.43.0 >
diff --git a/utils/tuning/libtuning/modules/lux/__init__.py b/utils/tuning/libtuning/modules/lux/__init__.py new file mode 100644 index 000000000000..af9d4e08e64f --- /dev/null +++ b/utils/tuning/libtuning/modules/lux/__init__.py @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2025, Ideas on Board + +from libtuning.modules.lux.lux import Lux +from libtuning.modules.lux.rkisp1 import LuxRkISP1 diff --git a/utils/tuning/libtuning/modules/lux/lux.py b/utils/tuning/libtuning/modules/lux/lux.py new file mode 100644 index 000000000000..5cf7d3baeb5d --- /dev/null +++ b/utils/tuning/libtuning/modules/lux/lux.py @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2019, Raspberry Pi Ltd +# Copyright (C) 2025, Ideas on Board +# +# Base Lux tuning module + +from ..module import Module + +import logging +import numpy as np + +logger = logging.getLogger(__name__) + + +class Lux(Module): + type = 'lux' + hr_name = 'LUX (Base)' + out_name = 'GenericLux' + + def __init__(self, debug: list): + super().__init__() + + self.debug = debug + + def calculate_lux_reference_values(self, images): + # The lux calibration is done on a single image. For best effects, the + # image with lux level closest to 1000 is chosen. + imgs = [img for img in images if img.macbeth is not None] + lux_values = [img.lux for img in imgs] + index = lux_values.index(min(lux_values, key=lambda l: abs(1000 - l))) + img = imgs[index] + logger.info(f'Selected image {img.name} for lux calibration') + + if img.lux < 50: + logger.warning(f'A Lux level of {img.lux} is very low for proper lux calibration') + + ref_y = self.calculate_y(img) + exposure_time = img.exposure + gain = img.againQ8_norm + aperture = 1 + logger.info(f'RefY:{ref_y} Exposure time:{exposure_time}µs Gain:{gain} Aperture:{aperture}') + return {'referenceY': ref_y, + 'referenceExposureTime': exposure_time, + 'referenceAnalogueGain': gain, + 'referenceDigitalGain': 1.0, + 'referenceLux': img.lux} + + def calculate_y(self, img): + max16Bit = 0xffff + # Average over all grey patches. + ap_r = np.mean(img.patches[0][3::4]) / max16Bit + ap_g = (np.mean(img.patches[1][3::4]) + np.mean(img.patches[2][3::4])) / 2 / max16Bit + ap_b = np.mean(img.patches[3][3::4]) / max16Bit + logger.debug(f'Averaged grey patches: Red: {ap_r}, Green: {ap_g}, Blue: {ap_b}') + + # Calculate white balance gains. + gr = ap_g / ap_r + gb = ap_g / ap_b + logger.debug(f'WB gains: Red: {gr} Blue: {gb}') + + # Calculate the mean Y value of the whole image + a_r = np.mean(img.channels[0]) * gr + a_g = (np.mean(img.channels[1]) + np.mean(img.channels[2])) / 2 + a_b = np.mean(img.channels[3]) * gb + y = 0.299 * a_r + 0.587 * a_g + 0.114 * a_b + y /= max16Bit + + return y + diff --git a/utils/tuning/libtuning/modules/lux/rkisp1.py b/utils/tuning/libtuning/modules/lux/rkisp1.py new file mode 100644 index 000000000000..62d3f94cabd8 --- /dev/null +++ b/utils/tuning/libtuning/modules/lux/rkisp1.py @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2024, Ideas on Board +# +# Lux module for tuning rkisp1 + +from .lux import Lux + + +class LuxRkISP1(Lux): + hr_name = 'Lux (RkISP1)' + out_name = 'Lux' + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + # We don't need anything from the config file. + def validate_config(self, config: dict) -> bool: + return True + + def process(self, config: dict, images: list, outputs: dict) -> dict: + return self.calculate_lux_reference_values(images)
For the lux algorithm, reference values get calculated based on a tuning image taken at a known lux level. The reference data contains the mean Y of the image, lux level, exposure time, gain and aperture. This module calculates these values for insertion into the tuning file. Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com> --- Changes in v2: - Added this patch --- .../tuning/libtuning/modules/lux/__init__.py | 6 ++ utils/tuning/libtuning/modules/lux/lux.py | 70 +++++++++++++++++++ utils/tuning/libtuning/modules/lux/rkisp1.py | 22 ++++++ 3 files changed, 98 insertions(+) create mode 100644 utils/tuning/libtuning/modules/lux/__init__.py create mode 100644 utils/tuning/libtuning/modules/lux/lux.py create mode 100644 utils/tuning/libtuning/modules/lux/rkisp1.py