[PATCH v5 1/3] ipa: rkisp1: agc: Read histogram weights from tuning file
Paul Elder
paul.elder at ideasonboard.com
Wed Jun 12 12:50:45 CEST 2024
On Tue, Jun 11, 2024 at 07:08:18PM +0300, Laurent Pinchart wrote:
> Hi Paul,
>
> Thank you for the patch.
>
> On Fri, Jun 07, 2024 at 05:03:28PM +0900, Paul Elder wrote:
> > Add support to the rkisp1 AGC to read histogram weights from the tuning
> > file. As controls for selecting the metering mode are not yet supported,
> > for now hardcode the matrix metering mode, which is the same as what the
> > AGC previously hardcoded.
> >
> > Signed-off-by: Paul Elder <paul.elder at ideasonboard.com>
> > Reviewed-by: Stefan Klug <stefan.klug at ideasonboard.com>
> > Reviewed-by: Daniel Scally <dan.scally at ideasonboard.com>
> >
> > ---
> > No change in v5
> >
> > Changes in v4:
> > - add debug print when parsing the yaml file
> >
> > Changes in v3:
> > - support the new tuning file layout for interpolated matrices
> > - support both v10 and v12
> >
> > Changes in v2:
> > - add default metering mode if none are read successfully from the
> > tuning file
> > - compute the predivider instead of using a table
> > ---
> > src/ipa/rkisp1/algorithms/agc.cpp | 94 ++++++++++++++++++++++++++++++-
> > src/ipa/rkisp1/algorithms/agc.h | 6 ++
> > 2 files changed, 97 insertions(+), 3 deletions(-)
> >
> > diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
> > index 50e0690fe..3bbafd978 100644
> > --- a/src/ipa/rkisp1/algorithms/agc.cpp
> > +++ b/src/ipa/rkisp1/algorithms/agc.cpp
> > @@ -17,6 +17,8 @@
> > #include <libcamera/control_ids.h>
> > #include <libcamera/ipa/core_ipa_interface.h>
> >
> > +#include "libcamera/internal/yaml_parser.h"
> > +
> > #include "libipa/histogram.h"
> >
> > /**
> > @@ -36,6 +38,76 @@ namespace ipa::rkisp1::algorithms {
> >
> > LOG_DEFINE_CATEGORY(RkISP1Agc)
> >
> > +int Agc::parseMeteringModes(IPAContext &context, const YamlObject &tuningData,
> > + const char *prop)
> > +{
> > + const YamlObject &yamlMeteringModes = tuningData[prop];
>
> I think this belongs to the caller.
>
> > + if (!yamlMeteringModes.isDictionary()) {
> > + LOG(RkISP1Agc, Error)
> > + << "'" << prop << "' parameter not found in tuning file";
> > + return -EINVAL;
> > + }
> > +
> > + for (const auto &[key, value] : yamlMeteringModes.asDict()) {
> > + if (controls::AeMeteringModeNameValueMap.find(key) ==
> > + controls::AeMeteringModeNameValueMap.end()) {
> > + LOG(RkISP1Agc, Warning)
> > + << "Skipping unknown metering mode '" << key << "'";
> > + continue;
> > + }
> > +
> > + for (const auto &[version, matrix] : value.asDict()) {
>
> What is the version ? It isn't used below except in a debugging message.
It's "v10" or "v12". It is indeed not used because...
>
> More generally, where do we document the format of the tuning data ? If
> the answer is '\todo' then could we have at least one example yaml file
> in the source tree that includes all tuning blocks ? This can be done on
> top of this series, but please sooner than later.
(there's a schema patch from Stefan)
>
> > + std::vector<uint8_t> weights =
> > + matrix.getList<uint8_t>().value_or(std::vector<uint8_t>{});
> > + if (weights.size() != context.hw->numHistogramWeights)
...this is the "real" version checker.
> > + continue;
>
> I'd be very vocal about this, or even fail parsing completely.
The tuning file will have something like:
- v10: <v10 metering mode>
- v12: <v12 metering mode>
So it'll skip the non-matching version and only keep the matching. We
only have to be vocal if none are found... in which case a default one
is used. So I guess I should add a message when using a default one
below then.
>
> > +
> > + LOG(RkISP1Agc, Debug)
> > + << "Matched metering matrix mode "
> > + << key << ", version " << version;
> > +
> > + meteringModes_[controls::AeMeteringModeNameValueMap.at(key)] = weights;
>
> If there are multiple "versions", you'll overwrite the metering modes.
> Is that really intended ?
If there are multiple version then they'd get skipped because of the
above "real" version checker.
>
> > + }
> > + }
> > +
> > + if (meteringModes_.empty()) {
> > + int32_t meteringModeId = controls::AeMeteringModeNameValueMap.at("MeteringMatrix");
> > + std::vector<uint8_t> weights(context.hw->numHistogramWeights, 1);
> > +
> > + meteringModes_[meteringModeId] = weights;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +uint8_t Agc::predivider(Size &size)
>
> Agc::computeHistogramPredivider()
>
> (or s/compute/calculate/)
>
> > +{
> > + /*
> > + * The maximum number of pixels that could potentially be in one bin is
> > + * if all the pixels of the image are in it, multiplied by 3 for the
>
> I think you should pass the histogram mode to this function, to pick the
> right multiplier. We currently hardcode usage of the Y histogram, so the
> *3 factor below will result in higher predividers and loss of accuracy.
Ah, indeed.
>
> I think we should also take the weights into account.
How so? The documentation doesn't mention having to do so.
>
> > + * three color channels. The counter for each bin is 16 bits wide, so
> > + * `factor` thus contains the number of times we'd wrap around. This is
> > + * obviously the number of pixels that we need to skip to make sure
> > + * that we don't wrap around, but we compute the square root of it
> > + * instead, as the skip that we need to program is for both the x and y
> > + * directions.
> > + *
> > + * There's a bit of extra rounding math to make sure the rounding goes
> > + * the correct direction so that the square of the step is big enough
> > + * to encompass the `factor` number of pixels that we need to skip.
> > + */
> > + double factor = size.width * size.height * 3 / 65535.0;
> > + double root = std::sqrt(factor);
> > + uint8_t ret;
>
> s/ret/predivider/
>
> > +
> > + if (std::pow(std::floor(root), 2) + 0.01 < factor)
> > + ret = static_cast<uint8_t>(std::ceil(root));
> > + else
> > + ret = static_cast<uint8_t>(std::floor(root));
>
> I have trouble validating the math here. Let's assume we have an image
> size of 760x460 pixels. This leads to
>
> factor = 760 * 460* 3 / 65535.0 = 16.003662165255207
>
> (I'm using Python to do the math, I don't think the differences in
> rounding with C++ double, if any, will make a difference here)
>
> root = 4.000457744465652
> std::pow(std::floor(root), 2) + 0.01 = 16.01
>
> This is higher than factor, so
>
> predivider = static_cast<uint8_t>(std::floor(root)) = 4
>
> Skipping by a factor of 4 in both directions gives us 190x115 pixels,
> and multiplied by 3, that's 65550, which overflows. It may not be the
> biggest deal, as the hardware clamps the histogram values instead of
> rolling over, and the risk of *all* pixels being in the same bin is
> virtually 0, but it's still not correct.
Turns out I didn't need the +0.01.
Paul
>
> > +
> > + return std::clamp<uint8_t>(ret, 3, 127);
> > +}
> > +
> > Agc::Agc()
> > {
> > supportsRaw_ = true;
> > @@ -59,6 +131,10 @@ int Agc::init(IPAContext &context, const YamlObject &tuningData)
> > if (ret)
> > return ret;
> >
> > + ret = parseMeteringModes(context, tuningData, "AeMeteringMode");
> > + if (ret)
> > + return ret;
> > +
> > context.ctrlMap.merge(controls());
> >
> > return 0;
> > @@ -160,6 +236,7 @@ void Agc::prepare(IPAContext &context, const uint32_t frame,
> > frameContext.agc.gain = context.activeState.agc.automatic.gain;
> > }
> >
> > + /* \todo Remove this when we can set the below with controls */
>
> Patch 3/3 enables setting the metering mode through controls, but
> doesn't remove this. Is that intended ? Note that we should reprogram
> the histogram engine only in the case where the metering mode has
> actually changed, we shouldn't do it on every frame.
>
> > if (frame > 0)
> > return;
> >
> > @@ -178,14 +255,25 @@ void Agc::prepare(IPAContext &context, const uint32_t frame,
> > params->meas.hst_config.meas_window = context.configuration.agc.measureWindow;
> > /* Produce the luminance histogram. */
> > params->meas.hst_config.mode = RKISP1_CIF_ISP_HISTOGRAM_MODE_Y_HISTOGRAM;
> > +
> > /* Set an average weighted histogram. */
> > Span<uint8_t> weights{
> > params->meas.hst_config.hist_weight,
> > context.hw->numHistogramWeights
> > };
> > - std::fill(weights.begin(), weights.end(), 1);
> > - /* Step size can't be less than 3. */
> > - params->meas.hst_config.histogram_predivider = 4;
> > + /* \todo Get this from control */
> > + std::vector<uint8_t> &modeWeights = meteringModes_.at(controls::MeteringMatrix);
> > + std::copy(modeWeights.begin(), modeWeights.end(), weights.begin());
> > +
> > + std::stringstream str;
> > + str << "Histogram weights : ";
> > + for (size_t i = 0; i < context.hw->numHistogramWeights; i++)
> > + str << (int)params->meas.hst_config.hist_weight[i] << " ";
> > + LOG(RkISP1Agc, Debug) << str.str();
>
> You will construct the string unconditionally, which is not very nice.
> I'd drop this. If you want to log the weight I would do it at init()
> time, and only log the metering mode here.
>
> > +
> > + /* \todo Add a control for this? */
>
> I don't think that's needed, but I may be missing something. What would
> be the rationale ? It should be captured in the todo comment if we think
> it can be useful.
>
> > + params->meas.hst_config.histogram_predivider =
> > + predivider(context.configuration.sensor.size);
>
> Shouldn't you pass the measurement window size here, not the sensor size
> ?
>
> >
> > /* Update the configuration for histogram. */
> > params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_HST;
> > diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h
> > index 04b3247e1..4ab56ead5 100644
> > --- a/src/ipa/rkisp1/algorithms/agc.h
> > +++ b/src/ipa/rkisp1/algorithms/agc.h
> > @@ -44,11 +44,17 @@ public:
> > ControlList &metadata) override;
> >
> > private:
> > + int parseMeteringModes(IPAContext &context, const YamlObject &tuningData,
> > + const char *prop);
> > + uint8_t predivider(Size &size);
> > +
> > void fillMetadata(IPAContext &context, IPAFrameContext &frameContext,
> > ControlList &metadata);
> > double estimateLuminance(double gain) const override;
> >
> > Span<const uint8_t> expMeans_;
> > +
> > + std::map<int32_t, std::vector<uint8_t>> meteringModes_;
> > };
> >
> > } /* namespace ipa::rkisp1::algorithms */
More information about the libcamera-devel
mailing list