From patchwork Tue Apr 7 22:01:13 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 26493 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 62AEFC32F8 for ; Tue, 7 Apr 2026 22:03:01 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1918862D66; Wed, 8 Apr 2026 00:02:55 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="HGg39Y8F"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 33B1662D4E for ; Wed, 8 Apr 2026 00:02:43 +0200 (CEST) Received: from [192.168.0.204] (ams.linuxembedded.co.uk [209.38.108.23]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7FFD2257A; Wed, 8 Apr 2026 00:01:15 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1775599275; bh=rbs3mKEUMOc5h/sZ+nnL7K22ky+pgA3A36uWXG67KIM=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=HGg39Y8FsAgy5bPKeao//ELLJx+E0zAa69LeKddrD/PbW94kaxVqQNy10djOcvRUw 2JhnzfLJmAimZuJ4jv6vZsj/lPo1UDhFmNBH+ewUC+Iqq6AU2D26t17NzDXuKHGPIU g3AB/T0NsPdbh2Gio6lay6tW8UM+lHbTjSUbFkZo= From: Kieran Bingham Date: Tue, 07 Apr 2026 23:01:13 +0100 Subject: [PATCH 10/13] ipa: simple: Convert awb to libipa implementation MIME-Version: 1.0 Message-Id: <20260407-kbingham-awb-split-v1-10-a39af3f4dc20@ideasonboard.com> References: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@ideasonboard.com> In-Reply-To: <20260407-kbingham-awb-split-v1-0-a39af3f4dc20@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1775599360; l=12978; i=kieran.bingham@ideasonboard.com; s=20260204; h=from:subject:message-id; bh=rbs3mKEUMOc5h/sZ+nnL7K22ky+pgA3A36uWXG67KIM=; b=6LbPOZq/ynlgBb2NlD2UGY0/hMIrkIZFVq+4/SokpqNOwtN4Y+pZsfyyAWWiCrRgyCCtf4JK2 8AiDn5de/LUBw9J5H9P9BVhh+t4l6GztckKL26OaRabHKDPYPqX2kxJ X-Developer-Key: i=kieran.bingham@ideasonboard.com; a=ed25519; pk=IOxS2C6nWHNjLfkDR71Iesk904i6wJDfEERqV7hDBdY= 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" This brings in both Greyworld and Bayes from libipa assuming the tuning file provides the Bayesian priors from calibration. Manual controls become available and enabled as well, and the storage in the Context is moved to the new common types. Signed-off-by: Kieran Bingham --- src/ipa/simple/algorithms/awb.cpp | 215 +++++++++++++++++++++++++++++++++----- src/ipa/simple/algorithms/awb.h | 12 +++ src/ipa/simple/algorithms/ccm.cpp | 2 +- src/ipa/simple/ipa_context.h | 12 +-- 4 files changed, 209 insertions(+), 32 deletions(-) diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp index 05155c83d172d64609053ba940a4c12a2248bb04..90c05e86bae6eefe4874feeb1263af07acd5fcfc 100644 --- a/src/ipa/simple/algorithms/awb.cpp +++ b/src/ipa/simple/algorithms/awb.cpp @@ -14,6 +14,8 @@ #include +#include "libipa/awb_bayes.h" +#include "libipa/awb_grey.h" #include "libipa/colours.h" #include "simple/ipa_context.h" @@ -23,24 +25,173 @@ LOG_DEFINE_CATEGORY(IPASoftAwb) namespace ipa::soft::algorithms { +constexpr int32_t kMinColourTemperature = 2500; +constexpr int32_t kMaxColourTemperature = 10000; +constexpr int32_t kDefaultColourTemperature = 5000; + +/* Identical to RKISP1AwbStats ... why ? */ +class SimpleAwbStats final : public AwbStats +{ +public: + 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_; + } + +private: + RGB rgbMeans_; + double rg_; + double bg_; +}; + +int Awb::init(IPAContext &context, const YamlObject &tuningData) +{ + auto &cmap = context.ctrlMap; + + cmap[&controls::ColourTemperature] = ControlInfo(kMinColourTemperature, + kMaxColourTemperature, + kDefaultColourTemperature); + + cmap[&controls::AwbEnable] = ControlInfo(false, true); + cmap[&controls::ColourGains] = ControlInfo(0.0f, 3.996f, + Span{ { 1.0f, 1.0f } }); + + if (!tuningData.contains("algorithm")) + LOG(IPASoftAwb, Info) << "No AWB algorithm specified." + << " Default to grey world"; + + auto mode = tuningData["algorithm"].get("grey"); + if (mode == "grey") { + awbAlgo_ = std::make_unique(); + } else if (mode == "bayes") { + awbAlgo_ = std::make_unique(); + } else { + LOG(IPASoftAwb, Error) << "Unknown AWB algorithm: " << mode; + return -EINVAL; + } + LOG(IPASoftAwb, Debug) << "Using AWB algorithm: " << mode; + + int ret = awbAlgo_->init(tuningData); + if (ret) { + LOG(IPASoftAwb, Error) << "Failed to init AWB algorithm"; + return ret; + } + + const auto &src = awbAlgo_->controls(); + cmap.insert(src.begin(), src.end()); + + return 0; +} + int Awb::configure(IPAContext &context, [[maybe_unused]] const IPAConfigInfo &configInfo) { - auto &gains = context.activeState.awb.gains; - gains = { { 1.0, 1.0, 1.0 } }; + context.activeState.awb.manual.gains = RGB{ 1.0 }; + auto gains = awbAlgo_->gainsFromColourTemperature(kDefaultColourTemperature); + if (gains) + context.activeState.awb.automatic.gains = *gains; + else + context.activeState.awb.automatic.gains = RGB{ 1.0 }; + + context.activeState.awb.autoEnabled = true; + context.activeState.awb.manual.temperatureK = kDefaultColourTemperature; + context.activeState.awb.automatic.temperatureK = kDefaultColourTemperature; + + context.configuration.awb.enabled = true; return 0; } +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void Awb::queueRequest(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + [[maybe_unused]] IPAFrameContext &frameContext, + const ControlList &controls) +{ + auto &awb = context.activeState.awb; + + const auto &awbEnable = controls.get(controls::AwbEnable); + if (awbEnable && *awbEnable != awb.autoEnabled) { + awb.autoEnabled = *awbEnable; + + LOG(IPASoftAwb, Debug) + << (*awbEnable ? "Enabling" : "Disabling") << " AWB"; + } + + awbAlgo_->handleControls(controls); + + frameContext.awb.autoEnabled = awb.autoEnabled; + + if (awb.autoEnabled) + return; + + const auto &colourGains = controls.get(controls::ColourGains); + const auto &colourTemperature = controls.get(controls::ColourTemperature); + bool update = false; + if (colourGains) { + awb.manual.gains.r() = (*colourGains)[0]; + awb.manual.gains.b() = (*colourGains)[1]; + /* + * \todo Colour temperature reported in metadata is now + * incorrect, as we can't deduce the temperature from the gains. + * This will be fixed with the bayes AWB algorithm. + */ + update = true; + } else if (colourTemperature) { + awb.manual.temperatureK = *colourTemperature; + const auto &gains = awbAlgo_->gainsFromColourTemperature(*colourTemperature); + if (gains) { + awb.manual.gains.r() = gains->r(); + awb.manual.gains.b() = gains->b(); + update = true; + } + } + + if (update) + LOG(IPASoftAwb, Debug) + << "Set colour gains to " << awb.manual.gains; + + frameContext.awb.gains = awb.manual.gains; + frameContext.awb.temperatureK = awb.manual.temperatureK; +} + void Awb::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, DebayerParams *params) { - auto &gains = context.activeState.awb.gains; + /* + * When AutoAWB is enabled, this is the latest opportunity to take + * the most recent and up to date desired AWB gains. + */ + if (frameContext.awb.autoEnabled) { + frameContext.awb.gains = context.activeState.awb.automatic.gains; + frameContext.awb.temperatureK = context.activeState.awb.automatic.temperatureK; + } - frameContext.gains = gains; - params->gains = gains; + params->gains = frameContext.awb.gains; } void Awb::process(IPAContext &context, @@ -49,15 +200,19 @@ void Awb::process(IPAContext &context, const SwIspStats *stats, ControlList &metadata) { - const SwIspStats::Histogram &histogram = stats->yHistogram; - const uint8_t blackLevel = context.activeState.blc.level; + IPAActiveState &activeState = context.activeState; + RGB gains = frameContext.awb.gains; - metadata.set(controls::ColourGains, { frameContext.gains.r(), - frameContext.gains.b() }); + metadata.set(controls::AwbEnable, frameContext.awb.autoEnabled); + metadata.set(controls::ColourGains, { gains.r(), gains.b() }); + metadata.set(controls::ColourTemperature, frameContext.awb.temperatureK); if (!stats->valid) return; + const SwIspStats::Histogram &histogram = stats->yHistogram; + const uint8_t blackLevel = context.activeState.blc.level; + /* * 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 +222,42 @@ 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 rgbMeans = { { static_cast(sum.r() / nPixels), + static_cast(sum.g() / nPixels), + static_cast(sum.b() / nPixels) } }; - 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); + /* + * Todo: Determine the minimum allowed thresholds from the mean + * but we currently have the sum - not the mean value! + */ + SimpleAwbStats awbStats{ rgbMeans }; + + AwbResult awbResult = awbAlgo_->calculateAwb(awbStats, frameContext.lux.lux); + + /* Todo: Check if clamping required */ + + /* Filter the values to avoid oscillations. */ + double speed = 0.2; + double ct = awbResult.colourTemperature; + ct = ct * speed + activeState.awb.automatic.temperatureK * (1 - speed); + awbResult.gains = awbResult.gains * speed + + activeState.awb.automatic.gains * (1 - speed); + + activeState.awb.automatic.temperatureK = static_cast(ct); + activeState.awb.automatic.gains = awbResult.gains; LOG(IPASoftAwb, Debug) - << "gain R/B: " << gains << "; temperature: " - << context.activeState.awb.temperatureK; + << std::showpoint + << "Means " << rgbMeans << ", gains " + << activeState.awb.automatic.gains << ", temp " + << activeState.awb.automatic.temperatureK << "K"; } REGISTER_IPA_ALGORITHM(Awb, "Awb") diff --git a/src/ipa/simple/algorithms/awb.h b/src/ipa/simple/algorithms/awb.h index ad993f39c18002547301b0588dfde143382854a9..fa8f38f65d6e9fdd18418361711e683916b9a9ba 100644 --- a/src/ipa/simple/algorithms/awb.h +++ b/src/ipa/simple/algorithms/awb.h @@ -7,6 +7,8 @@ #pragma once +#include "libipa/awb.h" + #include "algorithm.h" namespace libcamera { @@ -19,7 +21,14 @@ public: Awb() = default; ~Awb() = default; + int init(IPAContext &context, + const YamlObject &tuningData) override; int configure(IPAContext &context, const IPAConfigInfo &configInfo) override; + + void queueRequest(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, @@ -29,6 +38,9 @@ public: IPAFrameContext &frameContext, const SwIspStats *stats, ControlList &metadata) override; + +private: + std::unique_ptr awbAlgo_; }; } /* namespace ipa::soft::algorithms */ diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp index 911a5af2c90b55187f0d98519f3c29b8e0804567..6dace73202a800b2c6375c30083e1ed50ef425b1 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 YamlObject &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.temperatureK; /* 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 2bd7c4642b118d7bb94b1b16cdf4ede5fb2554b5..67b03b5b835f59cf2e339d21377e06d1bbe79b6f 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 @@ -26,6 +27,8 @@ namespace libcamera { namespace ipa::soft { struct IPASessionConfiguration { + ipa::awb::Session awb; + struct { int32_t exposureMin, exposureMax; double againMin, againMax, again10, againMinStep; @@ -38,6 +41,7 @@ struct IPASessionConfiguration { struct IPAActiveState { ipa::lux::ActiveState lux; + ipa::awb::ActiveState awb; struct { int32_t exposure; @@ -51,11 +55,6 @@ struct IPAActiveState { double lastGain; } blc; - struct { - RGB gains; - unsigned int temperatureK; - } awb; - Matrix combinedMatrix; struct { @@ -68,6 +67,7 @@ struct IPAActiveState { struct IPAFrameContext : public FrameContext { ipa::lux::FrameContext lux; + ipa::awb::FrameContext awb; Matrix ccm; @@ -76,8 +76,6 @@ struct IPAFrameContext : public FrameContext { double gain; } sensor; - RGB gains; - float gamma; std::optional contrast; std::optional saturation;