new file mode 100644
@@ -0,0 +1,218 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Red Hat Inc.
+ *
+ * Luminance mean sample value based AGC algorithm
+ */
+
+#include "agc_msv.h"
+
+#include <algorithm>
+#include <cmath>
+#include <optional>
+
+#include <libcamera/base/log.h>
+
+#include "histogram.h"
+
+namespace libcamera {
+
+namespace ipa {
+
+LOG_DEFINE_CATEGORY(AgcMSV)
+
+/**
+ * \class AgcMSV
+ * \brief Binned mean luminance based AGC algorithm
+ */
+
+/**
+ * \class AgcMSV::Limits
+ * \brief Set of limits for the algorithm
+ *
+ * \var AgcMSV::Limits::exposure
+ * \brief Minimum and maximum allowed exposure (in lines)
+ *
+ * \var AgcMSV::Limits::gain
+ * \brief Minimum and maximum allowed gain
+ *
+ * \var AgcMSV::Limits::gainMinStep
+ * \brief Minimum allowed gain adjustment
+ *
+ * \var AgcMSV::Limits::gain1
+ * \brief The gain value assumed to result in a gain of 1.0
+ *
+ * The algorithm will not lower the gain value below this.
+ */
+
+/**
+ * \struct AgcMSV::Params
+ * \brief Collection of parameters for the algorithm
+ *
+ * \var AgcMSV::Params::yHist
+ * \brief Luminance histogram of the frame
+ *
+ * \var AgcMSV::Params::exposure
+ * \brief Effective exposure of the frame
+ *
+ * \var AgcMSV::Params::gain
+ * \brief Effective gain of the frame
+ */
+
+/**
+ * \struct AgcMSV::Result
+ * \brief Collection of results of the algorithm
+ *
+ * \var AgcMSV::Result::exposure
+ * \brief The applicable exposure (in lines)
+ *
+ * \var AgcMSV::Result::analogueGain
+ * \brief The applicable analogue gain
+ */
+
+namespace {
+
+/*
+ * The number of bins to use for the optimal exposure calculations.
+ */
+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.
+ */
+static constexpr float kExposureOptimal = kExposureBinsCount / 2.0;
+
+/*
+ * This implements the hysteresis for the exposure adjustment.
+ * It is small enough to have the exposure close to the optimal, and is big
+ * enough to prevent the exposure from wobbling around the optimal value.
+ */
+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;
+
+std::optional<float> calculateMSV(const Histogram &histogram)
+{
+ /*
+ * Calculate Mean Sample Value (MSV) according to formula from:
+ * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf
+ */
+ const unsigned int yHistValsPerBin = histogram.bins() / kExposureBinsCount;
+ const unsigned int yHistValsPerBinMod =
+ histogram.bins() / (histogram.bins() % kExposureBinsCount + 1);
+ int exposureBins[kExposureBinsCount] = {};
+ unsigned int denom = 0;
+ unsigned int num = 0;
+
+ if (yHistValsPerBin == 0)
+ return {};
+
+ for (unsigned int i = 0; i < histogram.bins(); i++) {
+ unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin;
+ exposureBins[idx] += histogram[i];
+ }
+
+ for (unsigned int i = 0; i < kExposureBinsCount; i++) {
+ LOG(AgcMSV, Debug) << i << ": " << exposureBins[i];
+ denom += exposureBins[i];
+ num += exposureBins[i] * (i + 1);
+ }
+
+ return (denom == 0 ? 0 : static_cast<float>(num) / denom);
+}
+
+} /* namespace */
+
+/**
+ * \brief Set the limits for the algorithm
+ */
+void AgcMSV::setLimits(const Limits &limits)
+{
+ limits_ = limits;
+}
+
+/**
+ * \brief Calculate a new set of AGC parameters
+ */
+AgcMSV::Result AgcMSV::calculateNewEv(const Params ¶ms)
+{
+ auto exposureMSV = calculateMSV(params.yHist);
+ if (!exposureMSV) {
+ LOG(AgcMSV, Debug)
+ << "Not adjusting exposure due to insufficient histogram data";
+ return { params.exposure, params.gain };
+ }
+
+ return updateExposure(params.exposure, params.gain, *exposureMSV);
+}
+
+AgcMSV::Result AgcMSV::updateExposure(uint32_t exposure, double again, float exposureMSV)
+{
+ float error = kExposureOptimal - exposureMSV;
+ if (std::abs(error) <= kExposureSatisfactory)
+ return { exposure, again };
+
+ /*
+ * 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(error * kExpProportionalGain,
+ -kExpMaxStep, kExpMaxStep);
+ float factor = 1.0f + step;
+
+ if (factor > 1.0f) {
+ /* Scene too dark: increase exposure first, then gain. */
+ if (exposure < limits_.exposure[1]) {
+ uint32_t next = exposure * factor;
+ exposure = std::max(next, exposure + 1);
+ } else {
+ double next = again * factor;
+ again = std::max(next, again + limits_.gainMinStep);
+ }
+ } else {
+ /* Scene too bright: decrease gain first, then exposure. */
+ if (again > limits_.gain1) {
+ double next = again * factor;
+ again = std::min(next, again - limits_.gainMinStep);
+ } else {
+ uint32_t next = exposure * factor;
+ exposure = std::min(next, exposure - 1);
+ }
+ }
+
+ exposure = std::clamp(exposure, limits_.exposure[0], limits_.exposure[1]);
+ again = std::clamp(again, limits_.gain[0], limits_.gain[1]);
+
+ LOG(AgcMSV, Debug)
+ << "exposureMSV:" << exposureMSV
+ << " error:" << error << " factor:" << factor
+ << " exposure:" << exposure << " analogue-gain:" << again;
+
+ return { exposure, again };
+}
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
new file mode 100644
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Red Hat Inc.
+ *
+ * Luminance mean sample value based AGC algorithm
+ */
+
+#pragma once
+
+#include "histogram.h"
+
+#include <libcamera/base/utils.h>
+
+namespace libcamera {
+
+namespace ipa {
+
+class AgcMSV
+{
+public:
+ struct Limits {
+ std::array<uint32_t, 2> exposure;
+ std::array<double, 2> gain;
+ double gainMinStep;
+ double gain1;
+ };
+
+ struct Params {
+ const Histogram &yHist;
+ uint32_t exposure;
+ double gain;
+ };
+
+ struct Result {
+ uint32_t exposure;
+ double analogueGain;
+ };
+
+ void setLimits(const Limits &limits);
+ [[nodiscard]] Result calculateNewEv(const Params ¶ms);
+
+private:
+ AgcMSV::Result updateExposure(uint32_t exposure, double again, float exposureMSV);
+
+ Limits limits_ = {};
+};
+
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
@@ -3,6 +3,7 @@
libipa_headers = files([
'agc.h',
'agc_mean_luminance.h',
+ 'agc_msv.h',
'algorithm.h',
'awb_bayes.h',
'awb_grey.h',
@@ -25,6 +26,7 @@ libipa_headers = files([
libipa_sources = files([
'agc.cpp',
'agc_mean_luminance.cpp',
+ 'agc_msv.cpp',
'algorithm.cpp',
'awb_bayes.cpp',
'awb_grey.cpp',
@@ -7,10 +7,6 @@
#include "agc.h"
-#include <algorithm>
-#include <cmath>
-#include <optional>
-#include <stdint.h>
#include <utility>
#include <libcamera/base/log.h>
@@ -25,138 +21,22 @@ LOG_DEFINE_CATEGORY(IPASoftExposure)
namespace ipa::soft::algorithms {
-/*
- * The number of bins to use for the optimal exposure calculations.
- */
-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.
- */
-static constexpr float kExposureOptimal = kExposureBinsCount / 2.0;
-
-/*
- * This implements the hysteresis for the exposure adjustment.
- * It is small enough to have the exposure close to the optimal, and is big
- * enough to prevent the exposure from wobbling around the optimal value.
- */
-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;
-
-namespace {
-
-std::optional<float> calculateMSV(const Histogram &histogram)
-{
- /*
- * Calculate Mean Sample Value (MSV) according to formula from:
- * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf
- */
- const unsigned int yHistValsPerBin = histogram.bins() / kExposureBinsCount;
- const unsigned int yHistValsPerBinMod =
- histogram.bins() / (histogram.bins() % kExposureBinsCount + 1);
- int exposureBins[kExposureBinsCount] = {};
- unsigned int denom = 0;
- unsigned int num = 0;
-
- if (yHistValsPerBin == 0)
- return {};
-
- for (unsigned int i = 0; i < histogram.bins(); i++) {
- unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin;
- exposureBins[idx] += histogram[i];
- }
-
- for (unsigned int i = 0; i < kExposureBinsCount; i++) {
- LOG(IPASoftExposure, Debug) << i << ": " << exposureBins[i];
- denom += exposureBins[i];
- num += exposureBins[i] * (i + 1);
- }
-
- return (denom == 0 ? 0 : static_cast<float>(num) / denom);
-}
-
-} /* namespace */
-
-Agc::Agc()
+int Agc::configure(IPAContext &context, [[maybe_unused]] const IPAConfigInfo &configInfo)
{
-}
-
-void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV)
-{
- uint32_t exposure = frameContext.sensor.exposure;
- double again = frameContext.sensor.gain;
-
- 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) {
- uint32_t next = exposure * factor;
- exposure = std::max(next, exposure + 1);
- } else {
- double next = again * factor;
- again = std::max(next, again + context.configuration.agc.againMinStep);
- }
- } else {
- /* Scene too bright: decrease gain first, then exposure. */
- if (again > context.configuration.agc.again10) {
- double next = again * factor;
- again = std::max(next, again - context.configuration.agc.againMinStep);
- } else {
- uint32_t next = exposure * factor;
- exposure = std::min(next, exposure - 1);
- }
- }
-
- exposure = std::clamp(exposure, context.configuration.agc.exposureMin,
- context.configuration.agc.exposureMax);
- again = std::clamp(again, context.configuration.agc.againMin,
- context.configuration.agc.againMax);
-
- frameContext.agc.exposure = exposure;
- frameContext.agc.gain = again;
-
- context.activeState.agc.exposure = exposure;
- context.activeState.agc.again = again;
-
- LOG(IPASoftExposure, Debug)
- << "exposureMSV " << exposureMSV
- << " error " << error << " factor " << factor
- << " exp " << exposure << " again " << again;
+ agc_.setLimits({
+ .exposure = {
+ context.configuration.agc.exposureMin,
+ context.configuration.agc.exposureMax,
+ },
+ .gain = {
+ context.configuration.agc.againMin,
+ context.configuration.agc.againMax,
+ },
+ .gainMinStep = context.configuration.agc.againMinStep,
+ .gain1 = context.configuration.agc.again10,
+ });
+
+ return 0;
}
void Agc::process(IPAContext &context,
@@ -197,14 +77,17 @@ void Agc::process(IPAContext &context,
for (unsigned int i = 1; i < blackLevelHistIdx; i++)
histogram[0] += std::exchange(histogram[i], 0);
- auto exposureMSV = calculateMSV({ histogram });
- if (!exposureMSV) {
- LOG(IPASoftExposure, Debug)
- << "Not adjusting exposure due to insufficient histogram data";
- return;
- }
+ const auto &newEv = agc_.calculateNewEv({
+ .yHist = { histogram },
+ .exposure = frameContext.sensor.exposure,
+ .gain = frameContext.sensor.gain,
+ });
+
+ frameContext.agc.exposure = newEv.exposure;
+ frameContext.agc.gain = newEv.analogueGain;
- updateExposure(context, frameContext, *exposureMSV);
+ context.activeState.agc.exposure = frameContext.agc.exposure;
+ context.activeState.agc.again = frameContext.agc.gain;
}
REGISTER_IPA_ALGORITHM(Agc, "Agc")
@@ -9,6 +9,8 @@
#include "algorithm.h"
+#include <libipa/agc_msv.h>
+
namespace libcamera {
namespace ipa::soft::algorithms {
@@ -16,8 +18,7 @@ namespace ipa::soft::algorithms {
class Agc : public Algorithm
{
public:
- Agc();
- ~Agc() = default;
+ int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
void process(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
@@ -25,7 +26,7 @@ public:
ControlList &metadata) override;
private:
- void updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV);
+ AgcMSV agc_;
};
} /* namespace ipa::soft::algorithms */
Move the agc algorithm used into a separate component in libipa, and use it. While it is also a (mean) luminance based algorithm, there is already `AgcMeanLuminance`, so call it `AgcMSV` (from "mean sample value"). This move also removes the dependency on the black level and makes it use the `Histogram` type instead of the software isp specific types. With the removal of the black level information, it is assumed to be 0 and a black level corrected histogram is expected. Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> --- src/ipa/libipa/agc_msv.cpp | 218 ++++++++++++++++++++++++++++++ src/ipa/libipa/agc_msv.h | 51 +++++++ src/ipa/libipa/meson.build | 2 + src/ipa/simple/algorithms/agc.cpp | 167 ++++------------------- src/ipa/simple/algorithms/agc.h | 7 +- 5 files changed, 300 insertions(+), 145 deletions(-) create mode 100644 src/ipa/libipa/agc_msv.cpp create mode 100644 src/ipa/libipa/agc_msv.h