[2/2] ipa: simple: agc: make the exposure target tunable
diff mbox series

Message ID 20261017153829.442320-3-xiaqinghuan@mail.ustc.edu.cn
State Not Applicable
Headers show
Series
  • ipa: libipa: add sensor helpers for GC08A8/S5KJNS and make the AGC target tunable
Related show

Commit Message

Qianhui Xu Oct. 17, 2026, 3:38 p.m. UTC
The simple IPA AGC targets the middle of the histogram range, i.e. a mean
sample value of 0.5. As the debayer step gamma-encodes its output (the
default gamma is 2.2), such a target maps to roughly 73% of the full code
range on output, which looks washed out and clips highlights in scenes
with a high dynamic range.

Allow platforms to override the target from the tuning file with a new
"exposureTarget" parameter, expressed as a fraction of the histogram
range. The default keeps the existing behaviour, so existing tuning files
keep their meaning.

Signed-off-by: Qianhui Xu <xiaqinghuan@mail.ustc.edu.cn>
---
 src/ipa/simple/algorithms/agc.cpp | 24 +++++++++++++++++++-----
 src/ipa/simple/algorithms/agc.h   |  4 ++++
 2 files changed, 23 insertions(+), 5 deletions(-)

Comments

Barnabás Pőcze Oct. 19, 2026, 11:24 a.m. UTC | #1
Hi

2026. 10. 17. 17:38 keltezéssel, Qianhui Xu írta:
> The simple IPA AGC targets the middle of the histogram range, i.e. a mean
> sample value of 0.5. As the debayer step gamma-encodes its output (the
> default gamma is 2.2), such a target maps to roughly 73% of the full code
> range on output, which looks washed out and clips highlights in scenes
> with a high dynamic range.
> 
> Allow platforms to override the target from the tuning file with a new
> "exposureTarget" parameter, expressed as a fraction of the histogram
> range. The default keeps the existing behaviour, so existing tuning files
> keep their meaning.

Please base changes on the master branch. This part has been considerably
changed since the latest release, and thus the diff below is no longer applicable.


> 
> Signed-off-by: Qianhui Xu <xiaqinghuan@mail.ustc.edu.cn>
> ---
>   src/ipa/simple/algorithms/agc.cpp | 24 +++++++++++++++++++-----
>   src/ipa/simple/algorithms/agc.h   |  4 ++++
>   2 files changed, 23 insertions(+), 5 deletions(-)
> 
> diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp
> index 2f7e040..b6e9d30 100644
> --- a/src/ipa/simple/algorithms/agc.cpp
> +++ b/src/ipa/simple/algorithms/agc.cpp
> @@ -25,10 +25,11 @@ namespace ipa::soft::algorithms {
>   static constexpr unsigned int kExposureBinsCount = 5;
>   
>   /*
> - * The exposure is optimal when the mean sample value of the histogram is
> - * in the middle of the range.
> + * The exposure is optimal when the mean sample value of the histogram matches
> + * the configured target. 0.5 (the middle of the range) is a bright default;
> + * sensors tuned for photographic output usually target a lower code value.
>    */
> -static constexpr float kExposureOptimal = kExposureBinsCount / 2.0;
> +static constexpr double kDefaultExposureTarget = 0.5;
>   
>   /*
>    * This implements the hysteresis for the exposure adjustment.
> @@ -38,9 +39,21 @@ static constexpr float kExposureOptimal = kExposureBinsCount / 2.0;
>   static constexpr float kExposureSatisfactory = 0.2;
>   
>   Agc::Agc()
> +	: exposureTarget_(kDefaultExposureTarget)
>   {
>   }
>   
> +int Agc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
> +{
> +	const ValueNode &target = tuningData["exposureTarget"];
> +
> +	exposureTarget_ = target.get<double>(kDefaultExposureTarget);
> +	LOG(IPASoftExposure, Debug)
> +		<< "Exposure target " << exposureTarget_;
> +
> +	return 0;
> +}
> +
>   void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV)
>   {
>   	/*
> @@ -53,8 +66,9 @@ void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou
>   
>   	int32_t &exposure = frameContext.sensor.exposure;
>   	double &again = frameContext.sensor.gain;
> +	const double optimal = exposureTarget_ * kExposureBinsCount;
>   
> -	if (exposureMSV < kExposureOptimal - kExposureSatisfactory) {
> +	if (exposureMSV < optimal - kExposureSatisfactory) {
>   		if (exposure < context.configuration.agc.exposureMax) {
>   			int32_t next = exposure * kExpNumeratorUp / kExpDenominator;
>   			if (next - exposure < 1)
> @@ -70,7 +84,7 @@ void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou
>   		}
>   	}
>   
> -	if (exposureMSV > kExposureOptimal + kExposureSatisfactory) {
> +	if (exposureMSV > optimal + kExposureSatisfactory) {
>   		if (again > context.configuration.agc.again10) {
>   			double next = again * kExpNumeratorDown / kExpDenominator;
>   			if (again - next < context.configuration.agc.againMinStep)
> diff --git a/src/ipa/simple/algorithms/agc.h b/src/ipa/simple/algorithms/agc.h
> index 112d9f5..8d3d2f6 100644
> --- a/src/ipa/simple/algorithms/agc.h
> +++ b/src/ipa/simple/algorithms/agc.h
> @@ -19,6 +19,8 @@ public:
>   	Agc();
>   	~Agc() = default;
>   
> +	int init(IPAContext &context, const ValueNode &tuningData) override;
> +
>   	void process(IPAContext &context, const uint32_t frame,
>   		     IPAFrameContext &frameContext,
>   		     const SwIspStats *stats,
> @@ -26,6 +28,8 @@ public:
>   
>   private:
>   	void updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV);
> +
> +	double exposureTarget_;
>   };
>   
>   } /* namespace ipa::soft::algorithms */

Patch
diff mbox series

diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp
index 2f7e040..b6e9d30 100644
--- a/src/ipa/simple/algorithms/agc.cpp
+++ b/src/ipa/simple/algorithms/agc.cpp
@@ -25,10 +25,11 @@  namespace ipa::soft::algorithms {
 static constexpr unsigned int kExposureBinsCount = 5;
 
 /*
- * The exposure is optimal when the mean sample value of the histogram is
- * in the middle of the range.
+ * The exposure is optimal when the mean sample value of the histogram matches
+ * the configured target. 0.5 (the middle of the range) is a bright default;
+ * sensors tuned for photographic output usually target a lower code value.
  */
-static constexpr float kExposureOptimal = kExposureBinsCount / 2.0;
+static constexpr double kDefaultExposureTarget = 0.5;
 
 /*
  * This implements the hysteresis for the exposure adjustment.
@@ -38,9 +39,21 @@  static constexpr float kExposureOptimal = kExposureBinsCount / 2.0;
 static constexpr float kExposureSatisfactory = 0.2;
 
 Agc::Agc()
+	: exposureTarget_(kDefaultExposureTarget)
 {
 }
 
+int Agc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
+{
+	const ValueNode &target = tuningData["exposureTarget"];
+
+	exposureTarget_ = target.get<double>(kDefaultExposureTarget);
+	LOG(IPASoftExposure, Debug)
+		<< "Exposure target " << exposureTarget_;
+
+	return 0;
+}
+
 void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV)
 {
 	/*
@@ -53,8 +66,9 @@  void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou
 
 	int32_t &exposure = frameContext.sensor.exposure;
 	double &again = frameContext.sensor.gain;
+	const double optimal = exposureTarget_ * kExposureBinsCount;
 
-	if (exposureMSV < kExposureOptimal - kExposureSatisfactory) {
+	if (exposureMSV < optimal - kExposureSatisfactory) {
 		if (exposure < context.configuration.agc.exposureMax) {
 			int32_t next = exposure * kExpNumeratorUp / kExpDenominator;
 			if (next - exposure < 1)
@@ -70,7 +84,7 @@  void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou
 		}
 	}
 
-	if (exposureMSV > kExposureOptimal + kExposureSatisfactory) {
+	if (exposureMSV > optimal + kExposureSatisfactory) {
 		if (again > context.configuration.agc.again10) {
 			double next = again * kExpNumeratorDown / kExpDenominator;
 			if (again - next < context.configuration.agc.againMinStep)
diff --git a/src/ipa/simple/algorithms/agc.h b/src/ipa/simple/algorithms/agc.h
index 112d9f5..8d3d2f6 100644
--- a/src/ipa/simple/algorithms/agc.h
+++ b/src/ipa/simple/algorithms/agc.h
@@ -19,6 +19,8 @@  public:
 	Agc();
 	~Agc() = default;
 
+	int init(IPAContext &context, const ValueNode &tuningData) override;
+
 	void process(IPAContext &context, const uint32_t frame,
 		     IPAFrameContext &frameContext,
 		     const SwIspStats *stats,
@@ -26,6 +28,8 @@  public:
 
 private:
 	void updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV);
+
+	double exposureTarget_;
 };
 
 } /* namespace ipa::soft::algorithms */