From patchwork Mon Jul 20 14:59:21 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27395 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 E0EBBC328C for ; Mon, 20 Jul 2026 14:59:49 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A535267E65; Mon, 20 Jul 2026 16:59:48 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="NuipyECX"; 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 A3A8A67E54 for ; Mon, 20 Jul 2026 16:59:42 +0200 (CEST) Received: from [192.168.1.106] (mob-5-90-63-152.net.vodafone.it [5.90.63.152]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id AE40BD77; Mon, 20 Jul 2026 16:58:43 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1784559524; bh=zXUtBigMiw6eO+vt+/Fv+syxd0sdkfiYn9N4A5x3bFU=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=NuipyECXCd+u7lN0WUFC2Wla4hLnboKCWJdTTYRF0i0Spgy5poWq3AjoHKYZw2klY oa0ItaZMs24MxBsGgIqnU+H3TtncAHv5YRqWRdjT5X46oJFoGty0ShU5Vj2cmo+ooW FZMkyrhuJhptJ/tPg6BJ0H+ThlPMoGqGUxg/W43c= From: Jacopo Mondi Date: Mon, 20 Jul 2026 16:59:21 +0200 Subject: [PATCH v6 03/31] ipa: simple: awb: Port to use libipa AwbAlgorithm MIME-Version: 1.0 Message-Id: <20260720-libipa-algorithms-v6-3-ececb73f97cb@ideasonboard.com> References: <20260720-libipa-algorithms-v6-0-ececb73f97cb@ideasonboard.com> In-Reply-To: <20260720-libipa-algorithms-v6-0-ececb73f97cb@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham , Robert Mader X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=13654; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=y7LE77rh0+hi/oHub7+mkgErDdvlHu3tWSXsZtrgSRQ=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqXjfS4sPjsps7BC5PGXyc3Cmk5BtK3y1K1Z5r0 Uch80gvbCqJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCal430gAKCRByNAaPFqFW PDVKEACACyMHfEwD9uDkevC90fb747lX9Xz4iNpe4n/pVc8WxbS4BEj0CvitBxvAvgktAe9bvjB 0g7DAh/DTQBRzwOrDPTlJOSINZGYyzUwU+W1scFyPF0CRriKRQKHbzOVxU6jE2gJF9hOpn/PIS+ 9P9g21xjFmk8vO+lOA73XZj7XW5XrI8EHDYZH6bg6KWBCr8sqnOHzmKKTolK1Xj7OgyPD0e6qdN i9+bLpdU6/+Oc/ANE+UqvHcm6IR+/66pNP975PR00nt2XTdmAzCml7YW/b1jJVCg/2k8TZvxTP5 yYgcLB2FN392gaV4ZNYg+NWxBOym2dl7kOsJ659uKQUwjuSr4WMAg9KS7y1hp/JYsiJ57+OKBUQ XDUOJziHK5qOxv9bZhoX5oKI2cjVl831t3bjSHTzq+Qf08YoGWhqel7+wXMBUHRqbm5LJiHL9cP V19kDkE3/Zmedltn5rv6xXl7tL9us44Oz+Lj0ol3op8qI9c+PtVjKTWKEO70y81BKlU6Fui0mNb tXHct60uPQE+/60mNzgu01Nv2egEJOalyhTmvGQVdImXcMbxmolB8N4aRDyBbeO4duBZaALTFDo v4nsjv23aZltuyuEMm1hjGEvhVfy5KUcShT6aX42BH6dCZSD8Imt84wALq7v5q18Wzg/m0bqR/M /EQxTtqSS4ngbAg== X-Developer-Key: i=jacopo.mondi@ideasonboard.com; a=openpgp; fpr=72392EDC88144A65C701EA9BA5826A2587AD026B 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: Kieran Bingham Port the SoftISP Awb algorithm to use the new libipa implementation of AwbAlgorithm. The awbAlgo_ class member is initialized with the Q<2, 8> type even if there is no physical register representation for SoftISP. The usage of libipa::awb::Context, which defines the gains vector as RGB, in the SimpleIPA ActiveState and FrameContext requires changes to debayer_cpu.cpp and blc.cpp in order not to mix RGB and RGB. Signed-off-by: Kieran Bingham Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Tested-by: Robert Mader Tested-by: Milan Zamazal --- v5->v6: - Remove [[maybe_unused]] from header - cast sum.r()/b()/g() to double before averaging - remove unused include for colors.h - remove \todo about means --- .../internal/software_isp/debayer_params.h | 4 +- src/ipa/simple/algorithms/awb.cpp | 140 +++++++++++++++------ src/ipa/simple/algorithms/awb.h | 28 +++++ src/ipa/simple/algorithms/blc.cpp | 2 +- src/ipa/simple/algorithms/ccm.cpp | 2 +- src/ipa/simple/ipa_context.h | 12 +- src/libcamera/software_isp/debayer_cpu.cpp | 12 +- 7 files changed, 145 insertions(+), 55 deletions(-) diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h index 6772b43bced4..1074720d73c2 100644 --- a/include/libcamera/internal/software_isp/debayer_params.h +++ b/include/libcamera/internal/software_isp/debayer_params.h @@ -21,10 +21,10 @@ struct DebayerParams { Matrix combinedMatrix = { { 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 } }; - RGB blackLevel = RGB({ 0.0, 0.0, 0.0 }); + RGB blackLevel = RGB({ 0.0, 0.0, 0.0 }); float gamma = 1.0; float contrastExp = 1.0; - RGB gains = RGB({ 1.0, 1.0, 1.0 }); + RGB gains = RGB({ 1.0, 1.0, 1.0 }); }; } /* namespace libcamera */ diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp index 05155c83d172..0bd6b66ce859 100644 --- a/src/ipa/simple/algorithms/awb.cpp +++ b/src/ipa/simple/algorithms/awb.cpp @@ -14,50 +14,113 @@ #include -#include "libipa/colours.h" -#include "simple/ipa_context.h" - namespace libcamera { LOG_DEFINE_CATEGORY(IPASoftAwb) namespace ipa::soft::algorithms { +/* + * \todo Replace it with a proper Lux algorithm + */ +static constexpr unsigned int kDefaultLux = 500; + +class SimpleAwbStats final : public AwbStats +{ +public: + SimpleAwbStats() = default; + + SimpleAwbStats(const RGB &rgbMeans) + { + rgbMeans_ = rgbMeans; + + rg_ = rgbMeans_.r() / rgbMeans_.g(); + bg_ = rgbMeans_.b() / rgbMeans_.g(); + } + + double computeColourError(const RGB &gains) const override + { + /* + * Compute the sum of the squared colour error (non-greyness) as + * it appears in the log likelihood equation. + */ + double deltaR = gains.r() * rg_ - 1.0; + double deltaB = gains.b() * bg_ - 1.0; + double delta2 = deltaR * deltaR + deltaB * deltaB; + + return delta2; + } + + RGB rgbMeans() const override + { + return rgbMeans_; + } + + bool valid() const override + { + /* Minimum mean value below which AWB can't operate. */ + constexpr double minValue = 0.2; + + return rgbMeans_.r() > minValue || rgbMeans_.g() > minValue || + rgbMeans_.b() > minValue; + } + +private: + RGB rgbMeans_; + double rg_; + double bg_; +}; + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Awb::init(IPAContext &context, const ValueNode &tuningData) +{ + return awbAlgo_.init(tuningData, context.ctrlMap); +} + +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ int Awb::configure(IPAContext &context, [[maybe_unused]] const IPAConfigInfo &configInfo) { - auto &gains = context.activeState.awb.gains; - gains = { { 1.0, 1.0, 1.0 } }; + return awbAlgo_.configure(context.activeState.awb); +} - return 0; +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void Awb::queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) +{ + awbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb, + controls); } +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ void Awb::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, DebayerParams *params) { - auto &gains = context.activeState.awb.gains; + awbAlgo_.prepare(context.activeState.awb, frameContext.awb); - frameContext.gains = gains; - params->gains = gains; + params->gains = frameContext.awb.gains; } -void Awb::process(IPAContext &context, - [[maybe_unused]] const uint32_t frame, - IPAFrameContext &frameContext, - const SwIspStats *stats, - ControlList &metadata) +SimpleAwbStats Awb::calculateRgbMeans(IPAContext &context, + const SwIspStats *stats) const { + if (!stats->valid) + return {}; + const SwIspStats::Histogram &histogram = stats->yHistogram; const uint8_t blackLevel = context.activeState.blc.level; - metadata.set(controls::ColourGains, { frameContext.gains.r(), - frameContext.gains.b() }); - - if (!stats->valid) - return; - /* * Black level must be subtracted to get the correct AWB ratios, they * would be off if they were computed from the whole brightness range @@ -67,30 +130,31 @@ void Awb::process(IPAContext &context, histogram.begin(), histogram.end(), uint64_t(0)); const uint64_t offset = blackLevel * nPixels; const uint64_t minValid = 1; + /* * Make sure the sums are at least minValid, while preventing unsigned * integer underflow. */ const RGB sum = stats->sum_.max(offset + minValid) - offset; - /* - * Calculate red and blue gains for AWB. - * Clamp max gain at 4.0, this also avoids 0 division. - */ - auto &gains = context.activeState.awb.gains; - gains = { { - sum.r() <= sum.g() / 4 ? 4.0f : static_cast(sum.g()) / sum.r(), - 1.0, - sum.b() <= sum.g() / 4 ? 4.0f : static_cast(sum.g()) / sum.b(), - } }; - - RGB rgbGains{ { 1 / gains.r(), 1 / gains.g(), 1 / gains.b() } }; - context.activeState.awb.temperatureK = estimateCCT(rgbGains); - metadata.set(controls::ColourTemperature, context.activeState.awb.temperatureK); - - LOG(IPASoftAwb, Debug) - << "gain R/B: " << gains << "; temperature: " - << context.activeState.awb.temperatureK; + RGB rgbMeans = { { static_cast(sum.r()) / nPixels, + static_cast(sum.g()) / nPixels, + static_cast(sum.b()) / nPixels } }; + + return SimpleAwbStats(rgbMeans); +} + +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void Awb::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const SwIspStats *stats, + ControlList &metadata) +{ + SimpleAwbStats awbStats = calculateRgbMeans(context, stats); + + awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats, + kDefaultLux, metadata); } REGISTER_IPA_ALGORITHM(Awb, "Awb") diff --git a/src/ipa/simple/algorithms/awb.h b/src/ipa/simple/algorithms/awb.h index ad993f39c180..ff0a7a28e8f2 100644 --- a/src/ipa/simple/algorithms/awb.h +++ b/src/ipa/simple/algorithms/awb.h @@ -7,19 +7,36 @@ #pragma once +#include + +#include "libcamera/internal/software_isp/debayer_params.h" +#include "libcamera/internal/value_node.h" + +#include "libipa/awb.h" +#include "libipa/fixedpoint.h" +#include "simple/ipa_context.h" + #include "algorithm.h" namespace libcamera { namespace ipa::soft::algorithms { +class SimpleAwbStats; + class Awb : public Algorithm { public: Awb() = default; ~Awb() = default; + int init(IPAContext &context, + const ValueNode &tuningData) override; int configure(IPAContext &context, const IPAConfigInfo &configInfo) 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, @@ -29,6 +46,17 @@ public: IPAFrameContext &frameContext, const SwIspStats *stats, ControlList &metadata) override; + +private: + SimpleAwbStats calculateRgbMeans(IPAContext &context, + const SwIspStats *stats) const; + + /* + * There actually is no Q register format for SoftISP, but allow the + * colour gains to range in the [0.0f, 3.999f] interval, which seems + * reasonable. + */ + AwbAlgorithm> awbAlgo_; }; } /* namespace ipa::soft::algorithms */ diff --git a/src/ipa/simple/algorithms/blc.cpp b/src/ipa/simple/algorithms/blc.cpp index 677be56ed669..e45a913cd970 100644 --- a/src/ipa/simple/algorithms/blc.cpp +++ b/src/ipa/simple/algorithms/blc.cpp @@ -53,7 +53,7 @@ void BlackLevel::prepare(IPAContext &context, DebayerParams *params) { /* Latch the blacklevel gain so GPUISP can apply. */ - params->blackLevel = RGB(context.activeState.blc.level / 255.0f); + params->blackLevel = RGB(context.activeState.blc.level / 255.0f); } void BlackLevel::process(IPAContext &context, diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp index ace9c35dc462..1174784edc7e 100644 --- a/src/ipa/simple/algorithms/ccm.cpp +++ b/src/ipa/simple/algorithms/ccm.cpp @@ -44,7 +44,7 @@ int Ccm::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData) void Ccm::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params) { - const unsigned int ct = context.activeState.awb.temperatureK; + const unsigned int ct = frameContext.awb.colourTemperature; /* Change CCM only on bigger temperature changes. */ if (!currentCcm_ || diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index 8ccfacb46a59..29643a655ce1 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -16,6 +16,7 @@ #include "libcamera/internal/matrix.h" #include "libcamera/internal/vector.h" +#include #include #include "core_ipa_interface.h" @@ -36,6 +37,8 @@ struct IPASessionConfiguration { }; struct IPAActiveState { + ipa::awb::ActiveState awb; + struct { int32_t exposure; double again; @@ -48,11 +51,6 @@ struct IPAActiveState { double lastGain; } blc; - struct { - RGB gains; - unsigned int temperatureK; - } awb; - Matrix combinedMatrix; struct { @@ -64,6 +62,8 @@ struct IPAActiveState { }; struct IPAFrameContext : public FrameContext { + ipa::awb::FrameContext awb; + Matrix ccm; struct { @@ -71,8 +71,6 @@ struct IPAFrameContext : public FrameContext { double gain; } sensor; - RGB gains; - float gamma; std::optional contrast; std::optional saturation; diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp index 49382b4c2719..ab22635fdfaf 100644 --- a/src/libcamera/software_isp/debayer_cpu.cpp +++ b/src/libcamera/software_isp/debayer_cpu.cpp @@ -1010,9 +1010,9 @@ void DebayerCpu::updateLookupTables(const DebayerParams ¶ms) }; const unsigned int gammaTableSize = gammaTable_.size(); - const RGB blackIndex = params.blackLevel * kRGBLookupSize; - const RGB gains = params.gains; - const RGB div = (RGB(kRGBLookupSize) - blackIndex).max(1.0); + const RGB blackIndex = params.blackLevel * kRGBLookupSize; + const RGB gains = params.gains; + const RGB div = (RGB(kRGBLookupSize) - blackIndex).max(1.0); if (ccmEnabled_) { if (gammaUpdateNeeded || @@ -1025,7 +1025,7 @@ void DebayerCpu::updateLookupTables(const DebayerParams ¶ms) const unsigned int greenIndex = 1; const unsigned int blueIndex = swapRedBlueGains_ ? 0 : 2; for (unsigned int i = 0; i < kRGBLookupSize; i++) { - const RGB rgb = (gains * (RGB(i) - blackIndex) * kRGBLookupSize / div) + const RGB rgb = (gains * (RGB(i) - blackIndex) * kRGBLookupSize / div) .clamp(0.0, kRGBLookupSize - 1); red[i].r = std::round(rgb.r() * params.combinedMatrix[redIndex][0]); red[i].g = std::round(rgb.r() * params.combinedMatrix[greenIndex][0]); @@ -1045,8 +1045,8 @@ void DebayerCpu::updateLookupTables(const DebayerParams ¶ms) auto &green = green_; auto &blue = swapRedBlueGains_ ? red_ : blue_; for (unsigned int i = 0; i < kRGBLookupSize; i++) { - const RGB lutGains = - (gains * (RGB(i) - blackIndex) * gammaTableSize / div) + const RGB lutGains = + (gains * (RGB(i) - blackIndex) * gammaTableSize / div) .clamp(0.0, gammaTableSize - 1); red[i] = gammaTable_[lutGains.r()]; green[i] = gammaTable_[lutGains.g()];