From patchwork Fri Apr 11 13:04:08 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 23168 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id CC5E9C3213 for ; Fri, 11 Apr 2025 13:04:32 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 86EB368AA2; Fri, 11 Apr 2025 15:04:32 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="H9ObuWYj"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9329468A90 for ; Fri, 11 Apr 2025 15:04:30 +0200 (CEST) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:5b21:2ad5:1023:7179]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 326FF667; Fri, 11 Apr 2025 15:02:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1744376551; bh=IpGHt+jH7947Ft/P0vMDtPTf4fCCMp8E2cVf9bVm0jE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=H9ObuWYjlpxZ9P5ydp7Jb1F8/hgcdY62J/egVnDa6POf0+KITttvSUs138o21gb+n WOC7ZLpoQPEYLN+2ExdhFqm0HVcaE0V2mjzLtdwyhPRybqJwlh6rqH69d85TqQoiUI e7UzievbvVhdb3TSVofkOVCXiTkIdugdkZQG4Igo= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug , Jai Luthra Subject: [PATCH 1/8] include: linux: Add Wdr to rkisp1-config.h Date: Fri, 11 Apr 2025 15:04:08 +0200 Message-ID: <20250411130423.2164577-2-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250411130423.2164577-1-stefan.klug@ideasonboard.com> References: <20250411130423.2164577-1-stefan.klug@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" From: Jai Luthra Update linux headers. Signed-off-by: Jai Luthra Signed-off-by: Stefan Klug --- include/linux/rkisp1-config.h | 72 +++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/include/linux/rkisp1-config.h b/include/linux/rkisp1-config.h index edbc6cb65d1c..656835577c9e 100644 --- a/include/linux/rkisp1-config.h +++ b/include/linux/rkisp1-config.h @@ -169,6 +169,13 @@ */ #define RKISP1_CIF_ISP_COMPAND_NUM_POINTS 64 +/* + * Wide Dynamic Range + */ +#define RKISP1_CIF_ISP_WDR_CURVE_NUM_INTERV 32 +#define RKISP1_CIF_ISP_WDR_CURVE_NUM_COEFF (RKISP1_CIF_ISP_WDR_CURVE_NUM_INTERV + 1) +#define RKISP1_CIF_ISP_WDR_CURVE_NUM_DY_REGS 4 + /* * Measurement types */ @@ -889,6 +896,52 @@ struct rkisp1_cif_isp_compand_curve_config { __u32 y[RKISP1_CIF_ISP_COMPAND_NUM_POINTS]; }; +/** + * struct rkisp1_cif_isp_wdr_tone_curve - Tone mapping curve definition for WDR unit. + * + * @dY: the dYn increments for horizontal (input) axis of the tone curve. + * each 3-bit dY value represents an increment of 2**(value+3). + * dY[0] bits 0:2 is increment dY1, bit 3 unused + * dY[0] bits 4:6 is increment dY2, bit 7 unused + * ... + * dY[0] bits 28:30 is increment dY8, bit 31 unused + * ... and so on till dY[3] bits 28:30 is increment dY32, bit 31 unused. + * @ym: the Ym values for the vertical (output) axis of the tone curve. + * each value is 13 bit. + * + * The reset values define a linear curve which has the same effect as bypass: + * + * dY[0..3] = 0x44444444, This means that input sample range of 0-4096 is + * divided in 32 equal increments of 2**(4+3) = 128 units + * + * ym[0] = 0x0000, ym[1] = 0x0080, ... ym[31] = 0x0f80, ym[32] = 0x1000 + * which increases by 0x80 = 128 units + * + */ +struct rkisp1_cif_isp_wdr_tone_curve { + __u32 dY[RKISP1_CIF_ISP_WDR_CURVE_NUM_DY_REGS]; + __u16 ym[RKISP1_CIF_ISP_WDR_CURVE_NUM_COEFF]; +}; + +struct rkisp1_cif_isp_wdr_iref_config { + __u8 rgb_factor; + __u8 use_y9_8; + __u8 use_rgb7_8; + __u8 disable_transient; +}; + +struct rkisp1_cif_isp_wdr_config { + struct rkisp1_cif_isp_wdr_tone_curve tone_curve; + struct rkisp1_cif_isp_wdr_iref_config iref_config; + __u16 rgb_offset; + __u16 luma_offset; + __u16 dmin_thresh; + __u8 dmin_strength; + __u8 use_rgb_colorspace; + __u8 bypass_chroma_mapping; + __u8 use_iref; +}; + /*---------- PART2: Measurement Statistics ------------*/ /** @@ -1059,6 +1112,7 @@ struct rkisp1_stat_buffer { * @RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_BLS: BLS in the compand block * @RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_EXPAND: Companding expand curve * @RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_COMPRESS: Companding compress curve + * @RKISP1_EXT_PARAMS_BLOCK_TYPE_WDR: Wide dynamic range */ enum rkisp1_ext_params_block_type { RKISP1_EXT_PARAMS_BLOCK_TYPE_BLS, @@ -1081,6 +1135,7 @@ enum rkisp1_ext_params_block_type { RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_BLS, RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_EXPAND, RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_COMPRESS, + RKISP1_EXT_PARAMS_BLOCK_TYPE_WDR, }; #define RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE (1U << 0) @@ -1460,6 +1515,23 @@ struct rkisp1_ext_params_compand_curve_config { struct rkisp1_cif_isp_compand_curve_config config; } __attribute__((aligned(8))); +/** + * struct rkisp1_ext_params_wdr_config - RkISP1 extensible params + * Wide dynamic range config + * + * RkISP1 extensible parameters WDR block. + * Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_WDR` + * + * @header: The RkISP1 extensible parameters header, see + * :c:type:`rkisp1_ext_params_block_header` + * @config: WDR configuration, see + * :c:type:`rkisp1_cif_isp_wdr_config` + */ +struct rkisp1_ext_params_wdr_config { + struct rkisp1_ext_params_block_header header; + struct rkisp1_cif_isp_wdr_config config; +} __attribute__((aligned(8))); + /* * The rkisp1_ext_params_compand_curve_config structure is counted twice as it * is used for both the COMPAND_EXPAND and COMPAND_COMPRESS block types. From patchwork Fri Apr 11 13:04:09 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 23169 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 77521C3213 for ; Fri, 11 Apr 2025 13:04:35 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2D12D68AA8; Fri, 11 Apr 2025 15:04:35 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="t6E6Cmcx"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3377468A90 for ; Fri, 11 Apr 2025 15:04:33 +0200 (CEST) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:5b21:2ad5:1023:7179]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D3240735; Fri, 11 Apr 2025 15:02:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1744376553; bh=Ut3kZ+TMJy0c+9nlXzi/QYi1FHxB9nGAoYunL+7+JOM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=t6E6CmcxROAvaWAiOZizjzY47gkQdvwiq5v9t8eGXeaYty2ffg0KKUNfWLBmRf3a0 7ul7XBmcE4cE7lzukiaW6tGZFN68bLvVSntx5RCdPB31fRYp9V57DI8rSeo45RzXdn q3hNWzaxndv/YVRIdGK8+X18Qy32I6jWe104pKRY= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug Subject: [PATCH 2/8] tuning: rksip1: Add a static WideDynamicRange entry Date: Fri, 11 Apr 2025 15:04:09 +0200 Message-ID: <20250411130423.2164577-3-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250411130423.2164577-1-stefan.klug@ideasonboard.com> References: <20250411130423.2164577-1-stefan.klug@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a static WideDynamicRange entry that gets added by default. Signed-off-by: Stefan Klug --- utils/tuning/rkisp1.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/utils/tuning/rkisp1.py b/utils/tuning/rkisp1.py index 207b717a029c..1c9261021922 100755 --- a/utils/tuning/rkisp1.py +++ b/utils/tuning/rkisp1.py @@ -47,15 +47,18 @@ lsc = LSCRkISP1(debug=[lt.Debug.Plot], # values. This can also be a custom function. smoothing_function=lt.smoothing.MedianBlur(3),) lux = LuxRkISP1(debug=[lt.Debug.Plot]) +wdr = StaticModule('WideDynamicRange', + { 'ExposureConstraint': { 'MaxBrightPixels': 0.02, 'yTarget': 0.95 }, + 'MinExposureValue': -4.0 }) tuner = lt.Tuner('RkISP1') -tuner.add([agc, awb, blc, ccm, color_processing, filter, gamma_out, lsc, lux]) +tuner.add([agc, awb, blc, ccm, color_processing, filter, gamma_out, lsc, lux, wdr]) tuner.set_input_parser(YamlParser()) tuner.set_output_formatter(YamlOutput()) # Bayesian AWB uses the lux value, so insert the lux algorithm before AWB. tuner.set_output_order([agc, lux, awb, blc, ccm, color_processing, - filter, gamma_out, lsc]) + filter, gamma_out, lsc, wdr]) if __name__ == '__main__': sys.exit(tuner.run(sys.argv)) From patchwork Fri Apr 11 13:04:10 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 23170 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 33C67C3213 for ; Fri, 11 Apr 2025 13:04:38 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E442268AA8; Fri, 11 Apr 2025 15:04:37 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="WCJoIusD"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5B27668A90 for ; Fri, 11 Apr 2025 15:04:36 +0200 (CEST) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:5b21:2ad5:1023:7179]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 0C723735; Fri, 11 Apr 2025 15:02:36 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1744376557; bh=TmqTRaOVWD1etF2XOjcJeFWQ1ul6T1CHZaICjXw3c0A=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=WCJoIusD4fscWJmzOWmGUvm2iic0F3HvNIJMy3DOL9K3kCHNaUKsu/uWRfakqgUnF zT5yxYVfTiNb1ujJ28Dc64Sp/LfTC8TiKSPix5jN79ifhIfGjynP2bHuRZrhRo34TW xkgGxgn1eAVutxRLhnKArkLQPGI/6DzsCXpUAmrE= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug Subject: [PATCH 3/8] libcamera: Add PID controller class Date: Fri, 11 Apr 2025 15:04:10 +0200 Message-ID: <20250411130423.2164577-4-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250411130423.2164577-1-stefan.klug@ideasonboard.com> References: <20250411130423.2164577-1-stefan.klug@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" A PID controller is a practical and proven solution for many regulation tasks. This implementation can be parameterized either using the standard form or normal form [1]. Additionally output limits can be specified that are used to clamp the output and to prevent integrator windup. [1]: https://en.wikipedia.org/wiki/Proportional-integral-derivative_controller Signed-off-by: Stefan Klug --- include/libcamera/internal/meson.build | 1 + include/libcamera/internal/pid_controller.h | 46 ++++++ src/libcamera/meson.build | 1 + src/libcamera/pid_controller.cpp | 169 ++++++++++++++++++++ 4 files changed, 217 insertions(+) create mode 100644 include/libcamera/internal/pid_controller.h create mode 100644 src/libcamera/pid_controller.cpp diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build index 45408b313848..fa4c515332ee 100644 --- a/include/libcamera/internal/meson.build +++ b/include/libcamera/internal/meson.build @@ -32,6 +32,7 @@ libcamera_internal_headers = files([ 'matrix.h', 'media_device.h', 'media_object.h', + 'pid_controller.h', 'pipeline_handler.h', 'process.h', 'pub_key.h', diff --git a/include/libcamera/internal/pid_controller.h b/include/libcamera/internal/pid_controller.h new file mode 100644 index 000000000000..c1212aeb0946 --- /dev/null +++ b/include/libcamera/internal/pid_controller.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas On Board Oy + * + * PID Controller + */ +#pragma once + +#include + +#include + +namespace libcamera { + +LOG_DECLARE_CATEGORY(PidController) +class PidController +{ +public: + PidController(double Kp = 1.0, double Ki = 1e6, double Kd = 0.0, + double min = std::numeric_limits::min(), + double max = std::numeric_limits::max()); + + void setNormalParameters(double Kp = 1.0, double Ki = 1e6, double Kd = 0.0); + void setStandardParameters(double Kp = 1.0, double Ti = 1e6, double Td = 0.0); + void setOutputLimits(double min = std::numeric_limits::min(), + double max = std::numeric_limits::max()); + void reset(); + + void setTarget(double target); + double process(double value, double dt = 1.0); + +private: + double Kp_; + double Ki_; + double Kd_; + double max_; + double min_; + double target_; + + bool clamped_bottom_; + bool clamped_top_; + double integral_; + double last_error_; +}; + +} /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index de22b8e60dde..cb9c384d5062 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -43,6 +43,7 @@ libcamera_internal_sources = files([ 'matrix.cpp', 'media_device.cpp', 'media_object.cpp', + 'pid_controller.cpp', 'pipeline_handler.cpp', 'process.cpp', 'pub_key.cpp', diff --git a/src/libcamera/pid_controller.cpp b/src/libcamera/pid_controller.cpp new file mode 100644 index 000000000000..edfa7efafdfd --- /dev/null +++ b/src/libcamera/pid_controller.cpp @@ -0,0 +1,169 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas on Board Oy + * + * PID controller + */ + +#include "libcamera/internal/pid_controller.h" + +#include + +#include + +/** + * \file pid_controller.h + * \brief PID controller class + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(PidController) +/** + * \class PidController + * \brief Implementation of a PID controller + * + * Implementation of a Proportional Integral Derivative controller. + * See https://en.wikipedia.org/wiki/Proportional-integral-derivative_controller + * for the underlying details. + * + */ + +/** + * \brief Construct a PidController with optional normal parameters + * \param[in] Kp Proportional gain + * \param[in] Ki Integral gain + * \param[in] Kd Derivative gain + * \param[in] min Minimum output value + * \param[in] max Maximum output value + * + * For the parameters \see setNormalParameters() and \see setOutputLimits(). + */ +PidController::PidController(double Kp, double Ki, double Kd, double min, double max) +{ + reset(); + setNormalParameters(Kp, Ki, Kd); + setOutputLimits(min, max); +} + +/** + * \brief Set the normal parameters + * \param[in] Kp Proportional gain + * \param[in] Ki Integral gain + * \param[in] Kd Derivative gain + * + * Set the normal parameters of the PID controller. + */ +void PidController::setNormalParameters(double Kp, double Ki, double Kd) +{ + Kp_ = Kp; + Ki_ = Ki; + Kd_ = Kd; +} + +/** + * \brief Set the standard parameters + * \param[in] Kp Proportional gain + * \param[in] Ti Integration time constant + * \param[in] Td Derivative time constant + * + * Set the standard parameters of the PID controller. Functionally it is + * identical to the normal parameters but has the added benefit that it is + * easier to understand the values. The \a Ti parameter specifies the time the + * controller will tolerate the output value to be away from the target. Td is + * the time in which the controller tries to approach the target value. + * + * \see https://en.wikipedia.org/wiki/Proportional-integral-derivative_controller#Standard_form + */ +void PidController::setStandardParameters(double Kp, double Ti, double Td) +{ + Kp_ = Kp; + Ki_ = Kp / Ti; + Kd_ = Kp / Td; +} + +/** + * \brief Set the output limits + * \param[in] min Minimum output value + * \param[in] max Maximum output value + * + * Set the minimum and maximum output values of the controller. The controller + * will clamp the output to these values and ensure that no windup of the + * integral part occurs. + */ +void PidController::setOutputLimits(double min, double max) +{ + min_ = min; + max_ = max; +} + +/** + * \brief Reset the controller + * + * Reset the internal state of the controller. + */ +void PidController::reset() +{ + last_error_ = 0; + integral_ = 0; + clamped_bottom_ = false; + clamped_top_ = false; +} + +/** + * \brief Set the target value + * \param[in] target Target value + * + * Set the target value the controller shall reach. In controller theory this is + * usually called the set point. + */ +void PidController::setTarget(double target) +{ + target_ = target; +} + +/** + * \brief Run a regulation step + * \param[in] value Measured value + * \param[in] dt Time since last call + * \return Output value + * + * Process the last measurement (also called process variable PV) and return the + * new regulation value. The \a dt parameter specifies the time since the last + * call. It defaults to 1.0, so in cases that are not time but frame based, it + * can be left out. + */ +double PidController::process(double value, double dt) +{ + double error = target_ - value; + double derivative = (error - last_error_) / dt; + + /* If we hit a limit disable the integrative part in that direction */ + if ((error * Ki_ > 0 && !clamped_top_) || + (error * Ki_ < 0 && !clamped_bottom_)) + integral_ += error * dt; + + double ret = Kp_ * error + Ki_ * integral_ + Kd_ * derivative; + + clamped_top_ = false; + if (ret >= max_) { + clamped_top_ = true; + ret = max_; + } + + clamped_bottom_ = false; + if (ret <= min_) { + clamped_bottom_ = true; + ret = min_; + } + + LOG(PidController, Debug) << "Value: " << value << ", Target: " << target_ + << ", Error: " << error << ", Integral: " << integral_ + << ", Derivative: " << derivative << ", Output: " << ret; + + last_error_ = error; + + return ret; +} + +} /* namespace libcamera */ From patchwork Fri Apr 11 13:04:11 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 23171 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id C761AC3213 for ; Fri, 11 Apr 2025 13:04:41 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 819F668AB0; Fri, 11 Apr 2025 15:04:41 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ZNPdXWj7"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3EC5368A90 for ; Fri, 11 Apr 2025 15:04:39 +0200 (CEST) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:5b21:2ad5:1023:7179]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E293C667; Fri, 11 Apr 2025 15:02:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1744376560; bh=B2fZGNGruVYdJyc8L5k+mXbvXdTVuBui0YHXQkKomoQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ZNPdXWj7fee1+oRgNs0k08k5U1mxKfonhoT+ryfaqNdBilR5Lc/45WZxi9klSZMf3 O4wELSFKW/qXO+K9AQTofzof7k2vwrdFj0/JPuUsEIkiTwbrRcjXoIpEOHO1qEvJTl H/puZz89A08uIrBP+nq6CEOIPVZKCBeztmMufIkM= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug Subject: [PATCH 4/8] ipa: rkisp1: Add correction for exposure quantization Date: Fri, 11 Apr 2025 15:04:11 +0200 Message-ID: <20250411130423.2164577-5-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250411130423.2164577-1-stefan.klug@ideasonboard.com> References: <20250411130423.2164577-1-stefan.klug@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" When WDR regulation is active, it can happen that exposure times get set to very low values (Sometimes 2-3 lines). This intentionally introduced underexposure is corrected by the GWDR module. As exposure time is quantized by lines, the smalles possible change in exposure time now results in a quite visible change in perceived brightness. Mitigate that by applying a small global gain to account for the error introduced by the exposure quantization. ToDo: This needs perfect frame synchronous control of the sensor to work properly which is not guaranteed in all cases. It still improves the behavior with the current regulation. Signed-off-by: Stefan Klug --- src/ipa/rkisp1/algorithms/agc.cpp | 2 ++ src/ipa/rkisp1/algorithms/awb.cpp | 12 ++++++++---- src/ipa/rkisp1/ipa_context.h | 2 ++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index 8e77455e7afd..0e4fd3663167 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -583,6 +583,8 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, activeState.agc.automatic.exposure = newExposureTime / lineDuration; activeState.agc.automatic.gain = aGain; + activeState.agc.automatic.gainLostInExposureQuantization = + newExposureTime / (activeState.agc.automatic.exposure * lineDuration); /* * Expand the target frame duration so that we do not run faster than * the minimum frame duration when we have short exposures. diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp index 84aa1d29419d..cc78136a22c8 100644 --- a/src/ipa/rkisp1/algorithms/awb.cpp +++ b/src/ipa/rkisp1/algorithms/awb.cpp @@ -170,6 +170,7 @@ void Awb::queueRequest(IPAContext &context, awbAlgo_->handleControls(controls); frameContext.awb.autoEnabled = awb.autoEnabled; + frameContext.awb.additionalGain = 1.0; if (awb.autoEnabled) return; @@ -218,15 +219,18 @@ void Awb::prepare(IPAContext &context, const uint32_t frame, auto &awb = context.activeState.awb; frameContext.awb.gains = awb.automatic.gains; frameContext.awb.temperatureK = awb.automatic.temperatureK; + frameContext.awb.additionalGain = + context.activeState.agc.automatic.gainLostInExposureQuantization; } + auto gains = frameContext.awb.gains * frameContext.awb.additionalGain; auto gainConfig = params->block(); gainConfig.setEnabled(true); - gainConfig->gain_green_b = std::clamp(256 * frameContext.awb.gains.g(), 0, 0x3ff); - gainConfig->gain_blue = std::clamp(256 * frameContext.awb.gains.b(), 0, 0x3ff); - gainConfig->gain_red = std::clamp(256 * frameContext.awb.gains.r(), 0, 0x3ff); - gainConfig->gain_green_r = std::clamp(256 * frameContext.awb.gains.g(), 0, 0x3ff); + gainConfig->gain_green_b = std::clamp(256 * gains.g(), 0, 0x3ff); + gainConfig->gain_blue = std::clamp(256 * gains.b(), 0, 0x3ff); + gainConfig->gain_red = std::clamp(256 * gains.r(), 0, 0x3ff); + gainConfig->gain_green_r = std::clamp(256 * gains.g(), 0, 0x3ff); /* If we have already set the AWB measurement parameters, return. */ if (frame > 0) diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index 7ccc7b501aff..182203880ac9 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -76,6 +76,7 @@ struct IPAActiveState { } manual; struct { uint32_t exposure; + double gainLostInExposureQuantization; double gain; } automatic; @@ -149,6 +150,7 @@ struct IPAFrameContext : public FrameContext { RGB gains; bool autoEnabled; unsigned int temperatureK; + double additionalGain; } awb; struct { From patchwork Fri Apr 11 13:04:12 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 23172 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 57BD1C3213 for ; Fri, 11 Apr 2025 13:04:43 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 052B668AB5; Fri, 11 Apr 2025 15:04:43 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="hnOQ40rw"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 790D968AA8 for ; Fri, 11 Apr 2025 15:04:41 +0200 (CEST) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:5b21:2ad5:1023:7179]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 283A2667; Fri, 11 Apr 2025 15:02:42 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1744376562; bh=nLc6I4mBkYBfe77vUG7kKX5alg1HtTiVAllPwtAYeB8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=hnOQ40rwQWhsl9DobSGn7tmaOtQyy+HrhXNw/OqVGB/pX3EcYLBzlfWB5rOHF7GR8 5HucSFlsScLMp2Lr7Mz6LFOg0YeExTgik8WdtzyxgTJ0uf1X/SdqsuNfYj4WpXqGvc r5D2TvWuxWW2KrqOJ0x4HnP2tbxIQhzS7STlDdlc= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug Subject: [PATCH 5/8] ipa: rkisp1: Switch histogram to RGB combined mode Date: Fri, 11 Apr 2025 15:04:12 +0200 Message-ID: <20250411130423.2164577-6-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250411130423.2164577-1-stefan.klug@ideasonboard.com> References: <20250411130423.2164577-1-stefan.klug@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" The Y mode of the histogram get's captured at the ISP output, before the output formatter. This has the side effect, that the first and the last bins of are empty in case of limited YUV range. Another side effect is that gamma and GWDR processing is included in the histogram which makes algorithm development very difficult. In RGB mode the histogram is taken after xtalk (CCM) and is therefore independent of gamma and WDR. The limited range issue also does not apply. In the ISP reference it is however stated that "it is not possible to calculate a luminance or grayscale histogram from an RGB histogram since the position information is lost during its generation". During testing the RGB histogram provided good data and better algorithmic stability at a possible (but not measured) inaccuracy. Another option would be to pass the color space information into the IPA and strip the histogram accordingly. For ease of implementation switch to the RGB mode. Signed-off-by: Stefan Klug --- src/ipa/rkisp1/algorithms/agc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index 0e4fd3663167..a2c8d5403ba2 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -379,7 +379,7 @@ void Agc::prepare(IPAContext &context, const uint32_t frame, hstConfig.setEnabled(true); hstConfig->meas_window = context.configuration.agc.measureWindow; - hstConfig->mode = RKISP1_CIF_ISP_HISTOGRAM_MODE_Y_HISTOGRAM; + hstConfig->mode = RKISP1_CIF_ISP_HISTOGRAM_MODE_RGB_COMBINED; Span weights{ hstConfig->hist_weight, From patchwork Fri Apr 11 13:04:13 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 23173 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id D7B79C3213 for ; Fri, 11 Apr 2025 13:04:45 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8F2AF68AB0; Fri, 11 Apr 2025 15:04:45 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="f/JDnwQf"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id AB56068A90 for ; Fri, 11 Apr 2025 15:04:43 +0200 (CEST) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:5b21:2ad5:1023:7179]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 4F0A0735; Fri, 11 Apr 2025 15:02:44 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1744376564; bh=QRY09doo8IaDxRa25m74bcMS89llj5PFJXtpfClJxFY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=f/JDnwQfPKiCewA3sWfU/3oqS6PoNukLLEwvmoX0JHbL/Jf7cK1Ib/BwkCc3q7NRo amAhE0C34hEG37aRe7/KUE9am7O9eyo02uFz2gV+ks6CYqQH4sfiFtETNuZL1aiphK 0FCvvwjK/jkfacFhf6VISEvj7SNPvC3rbidLN3z4= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug Subject: [PATCH 6/8] ipa: rkisp1: Add WDR algorithm Date: Fri, 11 Apr 2025 15:04:13 +0200 Message-ID: <20250411130423.2164577-7-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250411130423.2164577-1-stefan.klug@ideasonboard.com> References: <20250411130423.2164577-1-stefan.klug@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a WDR algorithm to do global tone mapping. Global tone mapping is used to increase the perceived dynamic range of a image. The typical effect is that in areas that are normally overexposed additional structure gets visible. The overall idea is that the algorithm applies an exposure value correction to underexpose the image to the point where only a small number of saturated pixels is left. This artificial underexposure is then mitigated by applying a tone mapping curve. This algorithm implements 4 tone mapping strategies: - Linear - Power - Exponential - Histogram equalization Signed-off-by: Stefan Klug --- src/ipa/rkisp1/algorithms/meson.build | 1 + src/ipa/rkisp1/algorithms/wdr.cpp | 502 ++++++++++++++++++++++++++ src/ipa/rkisp1/algorithms/wdr.h | 63 ++++ src/ipa/rkisp1/ipa_context.h | 10 + src/ipa/rkisp1/params.cpp | 1 + src/ipa/rkisp1/params.h | 2 + src/libcamera/control_ids_debug.yaml | 7 +- src/libcamera/control_ids_draft.yaml | 67 ++++ 8 files changed, 652 insertions(+), 1 deletion(-) create mode 100644 src/ipa/rkisp1/algorithms/wdr.cpp create mode 100644 src/ipa/rkisp1/algorithms/wdr.h diff --git a/src/ipa/rkisp1/algorithms/meson.build b/src/ipa/rkisp1/algorithms/meson.build index c66b0b70b82f..ff9e26d2684b 100644 --- a/src/ipa/rkisp1/algorithms/meson.build +++ b/src/ipa/rkisp1/algorithms/meson.build @@ -13,4 +13,5 @@ rkisp1_ipa_algorithms = files([ 'gsl.cpp', 'lsc.cpp', 'lux.cpp', + 'wdr.cpp', ]) diff --git a/src/ipa/rkisp1/algorithms/wdr.cpp b/src/ipa/rkisp1/algorithms/wdr.cpp new file mode 100644 index 000000000000..b342750c54a8 --- /dev/null +++ b/src/ipa/rkisp1/algorithms/wdr.cpp @@ -0,0 +1,502 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas On Board + * + * RkISP1 Wide Dynamic Range control + */ + +#include "wdr.h" + +#include +#include + +#include "libcamera/internal/yaml_parser.h" + +#include +#include + +#include "linux/rkisp1-config.h" + +/** + * \file wdr.h + */ + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +/** + * \class WideDynamicRange + * \brief RkISP1 Wide Dynamic Range algorithm + * + * This algorithm implements automatic global tone mapping for the RkISP1. + * Global tone mapping is done by the GWDR hardware block and applies + * a global tone mapping curve to the image to increase the perceived dynamic + * range. Imagine a indoor scene with bright outside visible through the windows. + * With normal exposure settings, the windwos will be completely saturated and + * no structure (sky/clouds) will be visible because the AEGC has to increase + * overall exposure to reach a certain level of mean brightness. In WDR mode, + * the algorithm will artifically reduce the exposure time so that the texture + * and colours become visible in the former saturated areas. Then the global + * tone mapping curve is applied to mitigate the loss of brightness. + * + * Calculating that tone mapping curve is the most difficult part. This + * algorithm implements four tone mapping strategies: + * - Linear: The tone mapping curve is a combination of two linear functions + * with one kneepoint + * - Power: The tone mapping curve follows a power function + * - Exponential: The tone mapping curve follows an exponential function + * - HistogramEqualization: The tone mapping curve tries to equalize the + * histogram + * + * The overall strategy is the same in all cases: A negative exposure value is + * applied to the AEGC regulation until the number of nearly saturated pixels go + * below a given threshold (controllable via WdrMaxBrightPixels, default is 2%) + * or the MinExposureValue specified in the tuning file is reached. + * + * The global tone mapping curve is then calculated so that it accounts for the + * reduction of brightness due to the negative exposure value. As the result of + * tone mapping is very difficult to quantize and as it is by definition a + * lossy process there is not a single "correct" solution. + * + * The approach taken here is based on the sinmple linear model. The kneepoint + * of the curve is calculated so that a 50% grey pixel in the normal exposed + * image ends up at 50% grey in the WDR image again. So the first section of the + * curve has a gain of pow(2, - WDR-EV) up to 50% level. The second part of the + * curve compresses the remaining range. Using the WdrStrength control, the gain + * of that initial section can be further adjusted. + * + * In the Power and Exponential modes, the curves are calculated so that they + * pass through that kneepoint. + * + * The histogram equalization mode tries to equalize the histogram of the + * image and acts independently of the calculated exposure value. + * + * \code{.unparsed} + * algorithms: + * - WideDynamicRange: + * ExposureConstraint: + * MaxBrightPixels: 0.02 + * yTarget: 0.95 + * MinExposureValue: -4.0 + * \endcode + */ + +LOG_DEFINE_CATEGORY(RkISP1Wdr) + +static constexpr unsigned int kTonecurveXIntervals = RKISP1_CIF_ISP_WDR_CURVE_NUM_INTERV; + +/* + * Increasing interval sizes. The intervals are crafted so that they sum + * up to 4096. This results in better fitting curves than the constant intervals + * (all entries are 4) + */ +static constexpr std::array kLoglikeIntervals = { + { 0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6 } +}; + +WideDynamicRange::WideDynamicRange() +{ +} + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int WideDynamicRange::init([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const YamlObject &tuningData) +{ + toneCurveIntervalValues_ = kLoglikeIntervals; + + /* Calculate a list of normed x values */ + toneCurveX_[0] = 0.0; + int lastValue = 0; + for (unsigned int i = 1; i < toneCurveX_.size(); i++) { + lastValue += std::pow(2, toneCurveIntervalValues_[i - 1] + 3); + lastValue = std::min(lastValue, 4096); + toneCurveX_[i] = lastValue / 4096.0; + } + + strength_ = 1.0; + mode_ = controls::draft::WdrOff; + exposureConstraintMaxBrightPixels_ = 0.02; + exposureConstraintY_ = 0.95; + minExposureValue_ = -4.0; + + const auto &constraint = tuningData["ExposureConstraint"]; + if (!constraint.isDictionary()) { + LOG(RkISP1Wdr, Warning) + << "ExposureConstraint not found in tuning data." + "Using default values MaxBrightPixels: " + << exposureConstraintMaxBrightPixels_ + << " yTarget: " << exposureConstraintY_; + } else { + exposureConstraintMaxBrightPixels_ = + constraint["MaxBrightPixels"].get().value_or(0.98); + exposureConstraintY_ = constraint["yTarget"].get().value_or(0.95); + } + + const auto &minExp = tuningData["MinExposureValue"]; + minExposureValue_ = minExp.get().value_or(minExposureValue_); + if (!minExp) { + LOG(RkISP1Wdr, Warning) + << "MinExposureValue not found in tuning data." + "Using default value " + << minExposureValue_; + } + + context.ctrlMap[&controls::draft::WdrMode] = + ControlInfo(controls::draft::WdrModeValues, controls::draft::WdrOff); + context.ctrlMap[&controls::draft::WdrStrength] = + ControlInfo(0.0f, 2.0f, static_cast(strength_)); + context.ctrlMap[&controls::draft::WdrMaxBrightPixels] = + ControlInfo(0.0f, 1.0f, static_cast(exposureConstraintMaxBrightPixels_)); + + applyCompensationLinear(1.0, 0.0); + + return 0; +} + +void WideDynamicRange::applyHistogramEqualization(double strength) +{ + if (hist_.empty()) + return; + + strength *= 0.65; + + /* + * In a fully equalized histogram, all bins have the same value. Try + * to equalize the histogram by applying a gain or damping depending on + * the distance of the actual bin value from that norm. + */ + std::vector gains; + gains.resize(hist_.size()); + double sum = 0; + double norm = 1.0 / (gains.size()); + for (unsigned i = 0; i < hist_.size(); i++) { + double diff = 1.0 + strength * (hist_[i] - norm) / norm; + /* Todo: fix for large intensity */ + //diff = std::max(diff, 0.0); + gains[i] = diff; + sum += diff; + } + /* Never amplify the last entry. */ + gains.back() = std::max(gains.back(), 1.0); + + double scale = gains.size() / sum; + for (auto &v : gains) + v *= scale; + + Pwl pwl; + double step = 1.0 / gains.size(); + double lastX = 0; + double lastY = 0; + + pwl.append(lastX, lastY); + for (unsigned int i = 0; i < gains.size() - 1; i++) { + lastY += gains[i] * step; + lastX += step; + pwl.append(lastX, lastY); + } + pwl.append(1.0, 1.0); + + for (unsigned int i = 0; i < toneCurveX_.size(); i++) { + toneCurveY_[i] = pwl.eval(toneCurveX_[i]); + } +} + +Vector WideDynamicRange::kneePoint(double gain, double strength) +{ + gain = std::pow(gain, strength); + double y = 0.5; + double x = y / gain; + //double x = 1 / (gain+1); + //double y = gain*x; + + return { { x, y } }; +} + +void WideDynamicRange::applyCompensationLinear(double gain, double strength) +{ + auto kp = kneePoint(gain, strength); + double g1 = kp[1] / kp[0]; + double g2 = (kp.y() - 1) / (kp.x() - 1); + + for (unsigned int i = 0; i < toneCurveX_.size(); i++) { + double x = toneCurveX_[i]; + double y; + if (x < kp.x()) { + y = g1 * x; + } else { + y = g2 * x + 1 - g2; + } + toneCurveY_[i] = y; + } +} + +void WideDynamicRange::applyCompensationPower(double gain, double strength) +{ + double e = 1.0; + if (strength > 1e-6) { + auto kp = kneePoint(gain, strength); + /* Calculate an exponent to go through the knee point. */ + e = log(kp[1]) / log(kp[0]); + } + + for (unsigned int i = 0; i < toneCurveX_.size(); i++) { + toneCurveY_[i] = std::pow(toneCurveX_[i], e); + } +} + +void WideDynamicRange::applyCompensationExponential(double gain, double strength) +{ + double k = 0.1; + auto kp = kneePoint(gain, strength); + double kx = kp[0]; + double ky = kp[1]; + + if (kx > ky) { + LOG(RkISP1Wdr, Warning) << "Invalid knee point: " << kp; + kx = ky; + } + + /* + * The exponential curve is based on the function proposed by Glozman + * et al. in + * S. Glozman, T. Kats, and O. Yadid-Pecht, "Exponent Operator Based + * Tone Mapping Algorithm for Color Wide Dynamic Range Images," 2011. + * + * That function uses a k factor as parameter for the WDR compression + * curve: + * k=0: maximum compression + * k=infinity: linear curve + * + * To calculate a k factor that results in a curve that passes through + * the kneepoint, the equation needs to be solved for k after inserting + * the kneepoint. This can be formulated as search for a zero point. + * Unfortunately there is no closed solution for that transformation. + * Using newton's method to approximate the value is numerically + * unstable. + * + * Luckily the function only crosses the x axis once and for the set of + * possible kneepoints, a negative and a positive point can be guessed. + * The approximation is then implemented using bisection. + */ + if (std::abs(kx - ky) < 0.001) { + k = 1e8; + } else { + double kl = 0.0001; + double kh = 1000; + + auto fk = [=](double v) { + return exp(-kx / v) - ky * exp(-1.0 / v) + ky - 1.0; + }; + + ASSERT(fk(kl) < 0); + ASSERT(fk(kh) > 0); + + k = kh / 10.0; + while (fk(k) > 0) { + kh = k; + k /= 10.0; + } + + do { + k = (kl + kh) / 2; + if (fk(k) < 0) + kl = k; + else + kh = k; + } while (std::abs(kh - kl) > 1e-3); + } + + double a = 1.0 / (1.0 - std::exp(-1.0 / k)); + for (unsigned int i = 0; i < toneCurveX_.size(); i++) { + toneCurveY_[i] = a * (1.0 - std::exp(-toneCurveX_[i] / k)); + } +} + +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void WideDynamicRange::queueRequest([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) +{ + strength_ = controls.get(controls::draft::WdrStrength).value_or(strength_); + exposureConstraintMaxBrightPixels_ = + controls.get(controls::draft::WdrMaxBrightPixels) + .value_or(exposureConstraintMaxBrightPixels_); + + const auto &mode = controls.get(controls::draft::WdrMode); + if (mode) { + mode_ = static_cast(*mode); + } + + frameContext.wdr.mode = mode_; +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void WideDynamicRange::prepare(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + RkISP1Params *params) +{ + if (!params) { + LOG(RkISP1Wdr, Warning) << "Params is null"; + return; + } + + auto config = params->block(); + if (!config) { + LOG(RkISP1Wdr, Warning) << "Wdr block is invalid"; + return; + } + + auto mode = frameContext.wdr.mode; + + config.setEnabled(mode != controls::draft::WdrOff); + + double comp = 0; + + /* + * Todo: This overwrites frameContext.agc.exposureValue so that + * in the next call to Agc::process() that exposureValue get's + * applied. In the future it is planned to move the exposure + * calculations from Agc::process() to Agc::prepare(). In this + * case, we need to ensure that this code get's called early enough. + */ + if (mode != controls::draft::WdrOff) { + frameContext.wdr.wdrExposureValue = context.activeState.wdr.exposureValue; + frameContext.wdr.agcExposureValue = frameContext.agc.exposureValue; + + /* + * When WDR is enabled, the maxBrightPixels constraint is always + * active. This is problematic when the user sets a positive + * exposureValue. As this would mean that the negative WDR + * exposure value and the user provided positive value should + * cancel each other out. But as the regulation is not absolute + * and only dependent on the measured changes in the histogram, + * reducing the wdr EV would only lead to an even stronger pull + * from regulation. Positive user supplied EVs must therefore be + * handled using the wdr curve. + */ + if (frameContext.wdr.wdrExposureValue < 0) { + frameContext.agc.exposureValue = + std::min(frameContext.agc.exposureValue, + frameContext.wdr.wdrExposureValue); + comp = frameContext.agc.exposureValue - frameContext.wdr.agcExposureValue; + } + } + + /* Calculate how much EV we need to compensate with the WDR curve. */ + double gain = pow(2.0, -comp); + + if (mode == controls::draft::WdrOff) { + applyCompensationLinear(1.0, 0.0); + } else if (mode == controls::draft::WdrLinear) { + applyCompensationLinear(gain, strength_); + } else if (mode == controls::draft::WdrPower) { + applyCompensationPower(gain, strength_); + } else if (mode == controls::draft::WdrExponential) { + applyCompensationExponential(gain, strength_); + } else if (mode == controls::draft::WdrHistogramEqualization) { + applyHistogramEqualization(strength_); + } + + //reset value + config->dmin_strength = 0x10; + config->dmin_thresh = 0; + + for (unsigned int i = 0; i < kTonecurveXIntervals; i++) { + int v = toneCurveIntervalValues_[i]; + config->tone_curve.dY[i / 8] |= (v & 0x07) << ((i % 8) * 4); + } + + std::vector debugCurve; + + /* + * Fix the curve to adhere to the hardware constraints. Don't apply a + * constraint on the first element, which is most likely zero anyways. + */ + int lastY = toneCurveY_[0] * 4096.0; + for (unsigned int i = 0; i < toneCurveX_.size(); i++) { + int diff = static_cast(toneCurveY_[i] * 4096.0) - lastY; + diff = std::clamp(diff, -2048, 2048); + lastY = lastY + diff; + config->tone_curve.ym[i] = lastY; + debugCurve.push_back({ static_cast(toneCurveX_[i] * 4096.0), + lastY }); + } + + context.debugMetadata.set>(controls::debug::WdrCurve, debugCurve); +} + +void WideDynamicRange::process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const rkisp1_stat_buffer *stats, + ControlList &metadata) +{ + if (!stats || !(stats->meas_type & RKISP1_CIF_ISP_STAT_HIST)) { + LOG(RkISP1Wdr, Error) << "No histogram data in statistics"; + return; + } + + if (frame == 0) { + pid_.setStandardParameters(1, 5.0, 3.0); + pid_.setOutputLimits(minExposureValue_, 0.0); + context.activeState.wdr.exposureValue = 0.0; + } + + pid_.setTarget(exposureConstraintY_); + + const rkisp1_cif_isp_stat *params = &stats->params; + auto mode = frameContext.wdr.mode; + + metadata.set(controls::draft::WdrMode, mode); + + Histogram cumHist({ params->hist.hist_bins, context.hw->numHistogramBins }, + [](uint32_t x) { return x >> 4; }); + + std::vector hist; + double sum = 0; + for (unsigned i = 0; i < context.hw->numHistogramBins; i++) { + double v = params->hist.hist_bins[i] >> 4; + hist.push_back(v); + sum += v; + } + + /* Scale so that the entries sum up to 1. */ + double scale = 1.0 / sum; + for (auto &v : hist) + v *= scale; + hist_.swap(hist); + + double mean = cumHist.quantile(1.0 - exposureConstraintMaxBrightPixels_) / + cumHist.bins(); + LOG(RkISP1Wdr, Debug) << "Mean y of bright pixels: " << mean; + + if (mode == controls::draft::WdrOff) { + metadata.set(controls::draft::WdrExposureValue, 0); + return; + } + + context.activeState.wdr.exposureValue = pid_.process(mean); + LOG(RkISP1Wdr, Debug) << "Active state WDR ev: " << context.activeState.wdr.exposureValue; + metadata.set(controls::draft::WdrExposureValue, frameContext.wdr.wdrExposureValue); + + /* + * We overwrote agc exposure value in prepare() to create an + * underexposure for WDR. Report the original exposure value in metadata. + */ + metadata.set(controls::ExposureValue, frameContext.wdr.agcExposureValue); +} + +REGISTER_IPA_ALGORITHM(WideDynamicRange, "WideDynamicRange") + +} /* namespace ipa::rkisp1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/wdr.h b/src/ipa/rkisp1/algorithms/wdr.h new file mode 100644 index 000000000000..d9bf9e9c433e --- /dev/null +++ b/src/ipa/rkisp1/algorithms/wdr.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2021-2022, Ideas On Board + * + * RkISP1 Wide Dynamic Range control + */ + +#pragma once + +#include + +#include "libcamera/internal/pid_controller.h" + +#include "linux/rkisp1-config.h" + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +class WideDynamicRange : public Algorithm +{ +public: + WideDynamicRange(); + ~WideDynamicRange() = default; + + int init(IPAContext &context, const YamlObject &tuningData) override; + + void queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + RkISP1Params *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const rkisp1_stat_buffer *stats, + ControlList &metadata) override; + +private: + Vector kneePoint(double gain, double strength); + void applyCompensationLinear(double gain, double strength); + void applyCompensationPower(double gain, double strength); + void applyCompensationExponential(double gain, double strength); + void applyHistogramEqualization(double strength); + + double exposureConstraintMaxBrightPixels_; + double exposureConstraintY_; + double minExposureValue_; + double strength_; + + controls::draft::WdrModeEnum mode_; + std::vector hist_; + PidController pid_; + + std::array toneCurveIntervalValues_; + std::array toneCurveX_; + std::array toneCurveY_; +}; + +} /* namespace ipa::rkisp1::algorithms */ +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index 182203880ac9..ce7a3fb7f2bf 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -125,6 +125,10 @@ struct IPAActiveState { struct { double gamma; } goc; + + struct { + double exposureValue; + } wdr; }; struct IPAFrameContext : public FrameContext { @@ -188,6 +192,12 @@ struct IPAFrameContext : public FrameContext { struct { double lux; } lux; + + struct { + controls::draft::WdrModeEnum mode; + double wdrExposureValue; + double agcExposureValue; + } wdr; }; struct IPAContext { diff --git a/src/ipa/rkisp1/params.cpp b/src/ipa/rkisp1/params.cpp index b4a889e415fc..fccfa430b13d 100644 --- a/src/ipa/rkisp1/params.cpp +++ b/src/ipa/rkisp1/params.cpp @@ -74,6 +74,7 @@ const std::map kBlockTypeInfo = { RKISP1_BLOCK_TYPE_ENTRY_EXT(CompandBls, COMPAND_BLS, compand_bls), RKISP1_BLOCK_TYPE_ENTRY_EXT(CompandExpand, COMPAND_EXPAND, compand_curve), RKISP1_BLOCK_TYPE_ENTRY_EXT(CompandCompress, COMPAND_COMPRESS, compand_curve), + RKISP1_BLOCK_TYPE_ENTRY_EXT(Wdr, WDR, wdr), }; } /* namespace */ diff --git a/src/ipa/rkisp1/params.h b/src/ipa/rkisp1/params.h index 04b06c2a6266..a31c54b4df53 100644 --- a/src/ipa/rkisp1/params.h +++ b/src/ipa/rkisp1/params.h @@ -40,6 +40,7 @@ enum class BlockType { CompandBls, CompandExpand, CompandCompress, + Wdr, }; namespace details { @@ -74,6 +75,7 @@ RKISP1_DEFINE_BLOCK_TYPE(Afc, afc) RKISP1_DEFINE_BLOCK_TYPE(CompandBls, compand_bls) RKISP1_DEFINE_BLOCK_TYPE(CompandExpand, compand_curve) RKISP1_DEFINE_BLOCK_TYPE(CompandCompress, compand_curve) +RKISP1_DEFINE_BLOCK_TYPE(Wdr, wdr) } /* namespace details */ diff --git a/src/libcamera/control_ids_debug.yaml b/src/libcamera/control_ids_debug.yaml index 797532712099..9489e677402e 100644 --- a/src/libcamera/control_ids_debug.yaml +++ b/src/libcamera/control_ids_debug.yaml @@ -3,4 +3,9 @@ %YAML 1.1 --- vendor: debug -controls: [] +controls: +- WdrCurve: + type: Point + direction: out + description: Debug control WdrCurve found in src/ipa/rkisp1/algorithms/wdr.cpp + size: '[n]' diff --git a/src/libcamera/control_ids_draft.yaml b/src/libcamera/control_ids_draft.yaml index 03309eeac34f..d53d181bbf47 100644 --- a/src/libcamera/control_ids_draft.yaml +++ b/src/libcamera/control_ids_draft.yaml @@ -293,5 +293,72 @@ controls: Currently identical to ANDROID_STATISTICS_FACE_IDS. size: [n] + - WdrMode: + type: int32_t + direction: inout + description: | + Set the WDR mode. + + The WDR mode is used to select the algorithm used for global tone + mapping. It will automatically reduce the exposure time of the sensor + so that there are only a small number of saturated pixels in the image. + The algorithm then compensate for the loss of brightness by applying a + global tone mapping curve to the image. + enum: + - name: WdrOff + value: 0 + description: Wdr is disabled. + - name: WdrLinear + value: 1 + description: + Apply a linear global tone mapping curve. + + The curve with two linear sections is applied. This produces good + results at the expense of a slightly artificial look. + - name: WdrPower + value: 2 + description: | + Apply a power global tone mapping curve. + + This curve has high gain values on the dark areas of an image and + high compression values on the bright area. It therefore tends to + produce noticeable noise artifacts. + - name: WdrExponential + value: 3 + description: | + Apply a exponential global tone mapping curve. + + This curve has lower gain values in dark areas compared to the power + curve but produces a more natural look compared to the linear curve. + It is therefore the best choice for most scenes. + - name: WdrHistogramEqualization + value: 4 + description: | + Apply histogram equalization. + + This curve preserves most of the information of the image at the + expense of a very artificial look. It is therefore best suited for + technical analysis. + - WdrStrength: + type: float + direction: in + description: | + Specify the strength of the wdr algorithm. The exact meaning of this + value is specific to the algorithm in use. Usually a value of 0 means no + global tone mapping is applied. A values of 1 is the default value and + the correct value for most scenes. A value above 1 increases the global + tone mapping effect and can lead to unrealistic image effects, + - WdrMaxBrightPixels: + type: float + direction: in + description: | + Percentage of allowed (nearly) saturated pixels. + - WdrExposureValue: + type: float + direction: out + description: | + Return the exposure value compensation applied by the WDR algorithm. + + \sa ExposureValue ... From patchwork Fri Apr 11 13:04:14 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 23174 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 74799C3213 for ; Fri, 11 Apr 2025 13:04:48 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 27B3868AB7; Fri, 11 Apr 2025 15:04:48 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="q6CmMme1"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 07A6D68AB7 for ; Fri, 11 Apr 2025 15:04:47 +0200 (CEST) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:5b21:2ad5:1023:7179]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A3FFF735; Fri, 11 Apr 2025 15:02:47 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1744376567; bh=QRn+OoNJ58tmeeS9trxG0Oy1XFt/JcK3eLm7mqaQQn0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=q6CmMme1ltj1It/O3HWqQPRcBP5YB14wPKKIW+AlA3C7COri/p5bShhN8ZimJdvlv /uOgirQoBYv1kePIwNim3Q04/kPjRigJlCXMrTiHqxdKZpxaCXeh+8eInT1JimkE/1 hpg9L31ODKTD/MPIZbsRdWN4t+kivi6LQ0sEvNZA= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug Subject: [PATCH 7/8] libcamera: vector: Add explicit cast and data accessor Date: Fri, 11 Apr 2025 15:04:14 +0200 Message-ID: <20250411130423.2164577-8-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250411130423.2164577-1-stefan.klug@ideasonboard.com> References: <20250411130423.2164577-1-stefan.klug@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" When dealing with Vectors it is useful to be able to cast the to a different underlying type or to access the underlying data. Add functions for that. Signed-off-by: Stefan Klug --- include/libcamera/internal/vector.h | 11 +++++++++++ src/libcamera/vector.cpp | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/include/libcamera/internal/vector.h b/include/libcamera/internal/vector.h index 16b6aef0b38f..63da55019aef 100644 --- a/include/libcamera/internal/vector.h +++ b/include/libcamera/internal/vector.h @@ -63,6 +63,17 @@ public: return data_[i]; } + template + explicit operator Vector() const + { + Vector ret; + for (unsigned int i = 0; i < Rows; i++) + ret[i] = static_cast(data_[i]); + return ret; + } + + constexpr Span data() const { return data_; } + constexpr Vector operator-() const { Vector ret; diff --git a/src/libcamera/vector.cpp b/src/libcamera/vector.cpp index 4dad1b9001c5..bb89f0de7c4f 100644 --- a/src/libcamera/vector.cpp +++ b/src/libcamera/vector.cpp @@ -64,6 +64,24 @@ LOG_DEFINE_CATEGORY(Vector) * \copydoc Vector::operator[](size_t i) const */ +/** + * \fn Vector::data() + * \brief Access the vector data + * + * Access the contents of the vector as linear array of values. + * + * \return A span referencing the vector data as a linear array + */ + +/** + * \fn Vector::operator Vector() + * \brief Cast to a different underlying type + * + * Cast the vector to a different underlying type using static_cast(). + * + * \return A vector of type T2 + */ + /** * \fn Vector::operator-() const * \brief Negate a Vector by negating both all of its coordinates From patchwork Fri Apr 11 13:04:15 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 23175 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 72C6BC3213 for ; Fri, 11 Apr 2025 13:04:51 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2AADC68AB4; Fri, 11 Apr 2025 15:04:51 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Rfz8rFf/"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id A9BFA68AB4 for ; Fri, 11 Apr 2025 15:04:49 +0200 (CEST) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:5b21:2ad5:1023:7179]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5E01E735; Fri, 11 Apr 2025 15:02:50 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1744376570; bh=a4GTBrVlT555xzFzrZUEcNd7nTZIbDjvjOE1SuHrkiw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Rfz8rFf/ZHpVpZfVL4B5H8zIL5SCEVixRKD0xbRPt64yeBde6lPmpjNFuyj+okAhX iaBRWxdysODrLNU0IRDCPav5oje5Om0yrMuIO9il8fxLY47MVPzmzP9aE/qmrebvSV o5LsOVzvIOEJq4RVhDpSbQGCDvhOD73eIxcZTNlg= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug Subject: [PATCH 8/8] ipa: rkisp1: agc: Add debug controls for statistic values Date: Fri, 11 Apr 2025 15:04:15 +0200 Message-ID: <20250411130423.2164577-9-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250411130423.2164577-1-stefan.klug@ideasonboard.com> References: <20250411130423.2164577-1-stefan.klug@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add debug controls for AWB stats and exposure means. Signed-off-by: Stefan Klug --- src/ipa/rkisp1/algorithms/agc.cpp | 16 +++++++++++++++ src/ipa/rkisp1/algorithms/awb.cpp | 3 +++ src/libcamera/control_ids_debug.yaml | 29 ++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index a2c8d5403ba2..6dc8b9fc0a31 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -400,8 +400,10 @@ void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext, { utils::Duration exposureTime = context.configuration.sensor.lineDuration * frameContext.sensor.exposure; + utils::Duration exposureTimeCorrected = exposureTime * frameContext.awb.additionalGain; metadata.set(controls::AnalogueGain, frameContext.sensor.gain); metadata.set(controls::ExposureTime, exposureTime.get()); + metadata.set(controls::debug::ExposureTimeCorrected, exposureTimeCorrected.get()); metadata.set(controls::FrameDuration, frameContext.agc.frameDuration.get()); metadata.set(controls::ExposureTimeMode, frameContext.agc.autoExposureEnabled @@ -574,6 +576,20 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, frameContext.agc.exposureMode, hist, effectiveExposureValue); + auto &debugMeta = context.debugMetadata; + std::vector histMeta; + for (unsigned i = 0; i < context.hw->numHistogramBins; i++) + histMeta.push_back(params->hist.hist_bins[i] >> 4); + debugMeta.set>(controls::debug::StatsHistogram, histMeta); + + double meanExposure = 0; + for (uint8_t expMean : expMeans_) + meanExposure += expMean; + meanExposure = meanExposure / expMeans_.size() / 255.0; + debugMeta.set(controls::debug::StatsExpMean, meanExposure); + + debugMeta.set>(controls::debug::StatsExpMeans, expMeans_); + LOG(RkISP1Agc, Debug) << "Divided up exposure time, analogue gain and digital gain are " << newExposureTime << ", " << aGain << " and " << dGain; diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp index cc78136a22c8..be08458e45e3 100644 --- a/src/ipa/rkisp1/algorithms/awb.cpp +++ b/src/ipa/rkisp1/algorithms/awb.cpp @@ -310,6 +310,9 @@ void Awb::process(IPAContext &context, RGB rgbMeans = calculateRgbMeans(frameContext, awb); + context.debugMetadata.set>(controls::debug::RgbMeans, Vector(rgbMeans).data()); + context.debugMetadata.set(controls::debug::AwbCount, awb->awb_mean[0].cnt); + /* * If the means are too small we don't have enough information to * meaningfully calculate gains. Freeze the algorithm in that case. diff --git a/src/libcamera/control_ids_debug.yaml b/src/libcamera/control_ids_debug.yaml index 9489e677402e..d0f9329187f7 100644 --- a/src/libcamera/control_ids_debug.yaml +++ b/src/libcamera/control_ids_debug.yaml @@ -1,9 +1,38 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # +# This file was generated by utils/gen-debug-controls.py +# %YAML 1.1 --- vendor: debug controls: +- AwbCount: + type: int + direction: out + description: Debug control AwbCount found in src/ipa/rkisp1/algorithms/awb.cpp +- ExposureTimeCorrected: + type: float + direction: out + description: Debug control ExposureTimeCorrected found in src/ipa/rkisp1/algorithms/agc.cpp +- RgbMeans: + type: float + direction: out + description: Debug control RgbMeans found in src/ipa/rkisp1/algorithms/awb.cpp + size: '[n]' +- StatsExpMean: + type: const float + direction: out + description: Debug control StatsExpMean found in src/ipa/rkisp1/algorithms/agc.cpp +- StatsExpMeans: + type: uint8_t + direction: out + description: Debug control StatsExpMeans found in src/ipa/rkisp1/algorithms/agc.cpp + size: '[n]' +- StatsHistogram: + type: int32_t + direction: out + description: Debug control StatsHistogram found in src/ipa/rkisp1/algorithms/agc.cpp + size: '[n]' - WdrCurve: type: Point direction: out