[10/13] ipa: simple: Convert awb to libipa implementation
diff mbox series

Message ID 20260407-kbingham-awb-split-v1-10-a39af3f4dc20@ideasonboard.com
State New
Headers show
Series
  • ipa: simple: Convert to libipa AWB implementation
Related show

Commit Message

Kieran Bingham April 7, 2026, 10:01 p.m. UTC
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 <kieran.bingham@ideasonboard.com>
---
 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(-)

Patch
diff mbox series

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 <libcamera/control_ids.h>
 
+#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<double> &rgbMeans)
+		: rgbMeans_(rgbMeans)
+	{
+		rg_ = rgbMeans_.r() / rgbMeans_.g();
+		bg_ = rgbMeans_.b() / rgbMeans_.g();
+	}
+
+	double computeColourError(const RGB<double> &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<double> rgbMeans() const override
+	{
+		return rgbMeans_;
+	}
+
+private:
+	RGB<double> 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<const float, 2>{ { 1.0f, 1.0f } });
+
+	if (!tuningData.contains("algorithm"))
+		LOG(IPASoftAwb, Info) << "No AWB algorithm specified."
+				      << " Default to grey world";
+
+	auto mode = tuningData["algorithm"].get<std::string>("grey");
+	if (mode == "grey") {
+		awbAlgo_ = std::make_unique<AwbGrey>();
+	} else if (mode == "bayes") {
+		awbAlgo_ = std::make_unique<AwbBayes>();
+	} 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<double>{ 1.0 };
+	auto gains = awbAlgo_->gainsFromColourTemperature(kDefaultColourTemperature);
+	if (gains)
+		context.activeState.awb.automatic.gains = *gains;
+	else
+		context.activeState.awb.automatic.gains = RGB<double>{ 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<float> 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<uint64_t> 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<float>(sum.g()) / sum.r(),
-		1.0,
-		sum.b() <= sum.g() / 4 ? 4.0f : static_cast<float>(sum.g()) / sum.b(),
-	} };
+	RGB<double> rgbMeans = { { static_cast<double>(sum.r() / nPixels),
+				   static_cast<double>(sum.g() / nPixels),
+				   static_cast<double>(sum.b() / nPixels) } };
 
-	RGB<double> 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<unsigned int>(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<AwbAlgorithm> 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 <libipa/awb.h>
 #include <libipa/fc_queue.h>
 #include <libipa/lux.h>
 
@@ -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<float> gains;
-		unsigned int temperatureK;
-	} awb;
-
 	Matrix<float, 3, 3> combinedMatrix;
 
 	struct {
@@ -68,6 +67,7 @@  struct IPAActiveState {
 
 struct IPAFrameContext : public FrameContext {
 	ipa::lux::FrameContext lux;
+	ipa::awb::FrameContext awb;
 
 	Matrix<float, 3, 3> ccm;
 
@@ -76,8 +76,6 @@  struct IPAFrameContext : public FrameContext {
 		double gain;
 	} sensor;
 
-	RGB<float> gains;
-
 	float gamma;
 	std::optional<float> contrast;
 	std::optional<float> saturation;