@@ -7,6 +7,8 @@
#include "agc.h"
+#include <algorithm>
+#include <cmath>
#include <stdint.h>
#include <libcamera/base/log.h>
@@ -37,52 +39,74 @@ static constexpr float kExposureOptimal = kExposureBinsCount / 2.0;
*/
static constexpr float kExposureSatisfactory = 0.2;
+/*
+ * Proportional gain for exposure/gain adjustment. Maps the MSV error to a
+ * multiplicative correction factor:
+ *
+ * factor = 1.0 + kExpProportionalGain * error
+ *
+ * With kExpProportionalGain = 0.04:
+ * - max error ~2.5 -> factor 1.10 (~10% step, same as before)
+ * - error 1.0 -> factor 1.04 (~4% step)
+ * - error 0.3 -> factor 1.012 (~1.2% step)
+ *
+ * This replaces the fixed 10% bang-bang step with a proportional correction
+ * that converges smoothly and avoids overshooting near the target.
+ */
+static constexpr float kExpProportionalGain = 0.04;
+
+/*
+ * Maximum multiplicative step per frame, to bound the correction when the
+ * scene changes dramatically.
+ */
+static constexpr float kExpMaxStep = 0.15;
+
Agc::Agc()
{
}
void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV)
{
- /*
- * kExpDenominator of 10 gives ~10% increment/decrement;
- * kExpDenominator of 5 - about ~20%
- */
- static constexpr uint8_t kExpDenominator = 10;
- static constexpr uint8_t kExpNumeratorUp = kExpDenominator + 1;
- static constexpr uint8_t kExpNumeratorDown = kExpDenominator - 1;
-
int32_t &exposure = frameContext.sensor.exposure;
double &again = frameContext.sensor.gain;
- if (exposureMSV < kExposureOptimal - kExposureSatisfactory) {
+ double error = kExposureOptimal - exposureMSV;
+
+ if (std::abs(error) <= kExposureSatisfactory)
+ return;
+
+ /*
+ * Compute a proportional correction factor. The sign of the error
+ * determines the direction: positive error means too dark (increase),
+ * negative means too bright (decrease).
+ */
+ float step = std::clamp(static_cast<float>(error) * kExpProportionalGain,
+ -kExpMaxStep, kExpMaxStep);
+ float factor = 1.0f + step;
+
+ if (factor > 1.0f) {
+ /* Scene too dark: increase exposure first, then gain. */
if (exposure < context.configuration.agc.exposureMax) {
- int32_t next = exposure * kExpNumeratorUp / kExpDenominator;
- if (next - exposure < 1)
- exposure += 1;
- else
- exposure = next;
+ int32_t next = static_cast<int32_t>(exposure * factor);
+ exposure = std::max(next, exposure + 1);
} else {
- double next = again * kExpNumeratorUp / kExpDenominator;
+ double next = again * factor;
if (next - again < context.configuration.agc.againMinStep)
again += context.configuration.agc.againMinStep;
else
again = next;
}
- }
-
- if (exposureMSV > kExposureOptimal + kExposureSatisfactory) {
+ } else {
+ /* Scene too bright: decrease gain first, then exposure. */
if (again > context.configuration.agc.again10) {
- double next = again * kExpNumeratorDown / kExpDenominator;
+ double next = again * factor;
if (again - next < context.configuration.agc.againMinStep)
again -= context.configuration.agc.againMinStep;
else
again = next;
} else {
- int32_t next = exposure * kExpNumeratorDown / kExpDenominator;
- if (exposure - next < 1)
- exposure -= 1;
- else
- exposure = next;
+ int32_t next = static_cast<int32_t>(exposure * factor);
+ exposure = std::min(next, exposure - 1);
}
}
@@ -96,6 +120,7 @@ void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou
LOG(IPASoftExposure, Debug)
<< "exposureMSV " << exposureMSV
+ << " error " << error << " factor " << factor
<< " exp " << exposure << " again " << again;
}