libtuning: Add module for lux calibration
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> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
This commit is contained in:
parent
bab4db2d6d
commit
a783a90dec
3 changed files with 98 additions and 0 deletions
6
utils/tuning/libtuning/modules/lux/__init__.py
Normal file
6
utils/tuning/libtuning/modules/lux/__init__.py
Normal file
|
@ -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
|
70
utils/tuning/libtuning/modules/lux/lux.py
Normal file
70
utils/tuning/libtuning/modules/lux/lux.py
Normal file
|
@ -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
|
||||||
|
|
22
utils/tuning/libtuning/modules/lux/rkisp1.py
Normal file
22
utils/tuning/libtuning/modules/lux/rkisp1.py
Normal file
|
@ -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)
|
Loading…
Add table
Add a link
Reference in a new issue