[libcamera-devel] [PATCH v2 02/11] ipa: Add class that implements base AF control algorithm
Jacopo Mondi
jacopo at jmondi.org
Thu Jul 14 18:33:24 CEST 2022
Kate in cc for real this time!
On Thu, Jul 14, 2022 at 06:17:23PM +0200, Jacopo Mondi via libcamera-devel wrote:
> Hi Daniel
>
> On Wed, Jul 13, 2022 at 10:43:08AM +0200, Daniel Semkowicz via libcamera-devel wrote:
> > Move the code that was common for IPU3 and RPi AF algorithms to
> > a separate class independent of platform specific code.
> > This way each platform can just implement contrast calculation and
> > run the AF control loop basing on this class.
> >
>
> This is exactly the purpose of having algorithms in libipa. I'm
> excited it's the first "generic" one we have, or that I know of at
> least!
>
> This mean the very IPU3 implementation this algorithm is based on
> (Kate in cc) can now be generalized, and the same for the RPi one we
> have on the list!
>
> > Signed-off-by: Daniel Semkowicz <dse at thaumatec.com>
> > ---
> > .../libipa/algorithms/af_hill_climbing.cpp | 89 +++++++
> > src/ipa/libipa/algorithms/af_hill_climbing.h | 251 ++++++++++++++++++
> > src/ipa/libipa/algorithms/meson.build | 2 +
> > 3 files changed, 342 insertions(+)
> > create mode 100644 src/ipa/libipa/algorithms/af_hill_climbing.cpp
> > create mode 100644 src/ipa/libipa/algorithms/af_hill_climbing.h
> >
> > diff --git a/src/ipa/libipa/algorithms/af_hill_climbing.cpp b/src/ipa/libipa/algorithms/af_hill_climbing.cpp
> > new file mode 100644
> > index 00000000..f666c6c2
> > --- /dev/null
> > +++ b/src/ipa/libipa/algorithms/af_hill_climbing.cpp
> > @@ -0,0 +1,89 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2021, Red Hat
> > + * Copyright (C) 2022, Ideas On Board
> > + * Copyright (C) 2022, Theobroma Systems
> > + *
> > + * af_hill_climbing.cpp - AF Hill Climbing common algorithm
> > + */
>
> Any particular reason why the implementation is in the .h file ?
>
> > +
> > +#include "af_hill_climbing.h"
> > +
> > +/**
> > + * \file af_hill_climbing.h
> > + * \brief AF Hill Climbing common algorithm
> > + */
> > +
> > +namespace libcamera {
> > +
> > +LOG_DEFINE_CATEGORY(Af)
> > +
> > +namespace ipa::common::algorithms {
> > +
> > +/**
> > + * \class AfHillClimbing
> > + * \brief The base class implementing hill climbing AF control algorithm
> > + * \tparam Module The IPA module type for this class of algorithms
> > + *
> > + * Control part of auto focus algorithm. It calculates the lens position basing
> > + * on contrast measure supplied by the higher level. This way it is independent
> > + * from the platform.
> > + *
> > + * Derived class should call processAutofocus() for each measured contrast value
> > + * and set the lens to the calculated position.
> > + */
> > +
> > +/**
> > + * \fn AfHillClimbing::setMode()
> > + * \copydoc libcamera::ipa::common::algorithms::AfAlgorithm::setMode
> > + */
> > +
> > +/**
> > + * \fn AfHillClimbing::setRange()
> > + * \copydoc libcamera::ipa::common::algorithms::AfAlgorithm::setRange
> > + */
> > +
> > +/**
> > + * \fn AfHillClimbing::setSpeed()
> > + * \copydoc libcamera::ipa::common::algorithms::AfAlgorithm::setSpeed
> > + */
> > +
> > +/**
> > + * \fn AfHillClimbing::setMetering()
> > + * \copydoc libcamera::ipa::common::algorithms::AfAlgorithm::setMetering
> > + */
> > +
> > +/**
> > + * \fn AfHillClimbing::setWindows()
> > + * \copydoc libcamera::ipa::common::algorithms::AfAlgorithm::setWindows
> > + */
> > +
> > +/**
> > + * \fn AfHillClimbing::setTrigger()
> > + * \copydoc libcamera::ipa::common::algorithms::AfAlgorithm::setTrigger
> > + */
> > +
> > +/**
> > + * \fn AfHillClimbing::setPause()
> > + * \copydoc libcamera::ipa::common::algorithms::AfAlgorithm::setPause
> > + */
> > +
> > +/**
> > + * \fn AfHillClimbing::setLensPosition()
> > + * \copydoc libcamera::ipa::common::algorithms::AfAlgorithm::setLensPosition
> > + */
> > +
> > +/**
> > + * \fn AfHillClimbing::processAutofocus()
> > + * \brief Run the auto focus algorithm loop
> > + * \param[in] currentContrast New value of contrast measured for current frame
> > + *
> > + * This method should be called for each new contrast value that was measured,
> > + * usually in the process() method.
> > + *
> > + * \return New lens position calculated by AF algorithm
> > + */
> > +
> > +} /* namespace ipa::common::algorithms */
> > +
> > +} /* namespace libcamera */
> > diff --git a/src/ipa/libipa/algorithms/af_hill_climbing.h b/src/ipa/libipa/algorithms/af_hill_climbing.h
> > new file mode 100644
> > index 00000000..db9fc058
> > --- /dev/null
> > +++ b/src/ipa/libipa/algorithms/af_hill_climbing.h
> > @@ -0,0 +1,251 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2021, Red Hat
> > + * Copyright (C) 2022, Ideas On Board
> > + * Copyright (C) 2022, Theobroma Systems
> > + *
> > + * af_hill_climbing.h - AF Hill Climbing common algorithm
> > + */
> > +
> > +#pragma once
> > +
> > +#include <libcamera/base/log.h>
> > +
> > +#include "af_algorithm.h"
> > +
> > +namespace libcamera {
> > +
> > +LOG_DECLARE_CATEGORY(Af)
> > +
> > +namespace ipa::common::algorithms {
> > +
> > +template<typename Module>
> > +class AfHillClimbing : public AfAlgorithm<Module>
> > +{
> > +public:
> > + AfHillClimbing()
> > + : mode_(controls::AfModeAuto), state_(controls::AfStateIdle),
> > + pauseState_(controls::AfPauseStateRunning),
> > + lensPosition_(0), bestPosition_(0), currentContrast_(0.0),
> > + previousContrast_(0.0), maxContrast_(0.0), maxStep_(0),
> > + coarseCompleted_(false), fineCompleted_(false),
> > + lowStep_(0), highStep_(kMaxFocusSteps)
> > + {
> > + }
>
> Let's look at the implementation details later but I have one question
>
> > +
> > + virtual ~AfHillClimbing() {}
> > +
> > + void setMode(controls::AfModeEnum mode) final
> > + {
> > + if (mode != mode_) {
> > + LOG(Af, Debug) << "Switched AF mode from " << mode_
> > + << " to " << mode;
> > + pauseState_ = libcamera::controls::AfPauseStateRunning;
> > + mode_ = mode;
> > + }
> > + }
> > +
> > + void setRange([[maybe_unused]] controls::AfRangeEnum range) final
> > + {
> > + LOG(Af, Error) << __FUNCTION__ << " not implemented!";
> > + }
> > +
> > + void setSpeed([[maybe_unused]] controls::AfSpeedEnum speed) final
> > + {
> > + LOG(Af, Error) << __FUNCTION__ << " not implemented!";
> > + }
> > +
> > + void setTrigger(controls::AfTriggerEnum trigger) final
> > + {
> > + LOG(Af, Debug) << "Trigger called in mode " << mode_
> > + << " with " << trigger;
> > + if (mode_ == libcamera::controls::AfModeAuto) {
> > + if (trigger == libcamera::controls::AfTriggerStart)
> > + afReset();
>
> This seems out of place..
> Whenever I trigger an autofocus scan I get the lens reset to 0 causing
> a not very nice effect. Why are you resetting the lens position here?
>
> > + else
> > + state_ = libcamera::controls::AfStateIdle;
> > + }
> > + }
> > +
> > + void setPause(controls::AfPauseEnum pause) final
> > + {
> > + /* \todo: add the AfPauseDeferred mode */
> > + if (mode_ == libcamera::controls::AfModeContinuous) {
> > + if (pause == libcamera::controls::AfPauseImmediate)
> > + pauseState_ = libcamera::controls::AfPauseStatePaused;
> > + else if (pause == libcamera::controls::AfPauseResume)
> > + pauseState_ = libcamera::controls::AfPauseStateRunning;
> > + }
> > + }
> > +
> > + void setLensPosition([[maybe_unused]] float lensPosition) final
> > + {
> > + LOG(Af, Error) << __FUNCTION__ << " not implemented!";
> > + }
> > +
> > + /* These methods should be implemented by derived class */
> > + virtual void setMetering(controls::AfMeteringEnum metering) = 0;
> > + virtual void setWindows(Span<const Rectangle> windows) = 0;
> > +
> > +protected:
> > + uint32_t processAutofocus(double currentContrast)
> > + {
> > + currentContrast_ = currentContrast;
> > +
> > + /* If we are in a paused state, we won't process the stats */
> > + if (pauseState_ == libcamera::controls::AfPauseStatePaused)
> > + return lensPosition_;
> > +
> > + /* Depending on the mode, we may or may not process the stats */
> > + if (state_ == libcamera::controls::AfStateIdle)
> > + return lensPosition_;
> > +
> > + if (state_ != libcamera::controls::AfStateFocused) {
> > + afCoarseScan();
> > + afFineScan();
> > + } else {
> > + /* We can re-start the scan at any moment in AfModeContinuous */
> > + if (mode_ == libcamera::controls::AfModeContinuous)
> > + if (afIsOutOfFocus())
> > + afReset();
> > + }
> > +
> > + return lensPosition_;
>
> Let me recap one point. We are still missing a generic definition for
> the lens position value. As you can see we use
> V4L2_CID_FOCUS_ABSOLUTE which transports the actual value to be set in
> the v4l2 control. This can't work here and we are planning to remove
> V4L2 controls from the IPA in general, but while for other controls we
> have defined generic ranges, for the lens position we are still
> missing a way to compute a "generic" value in the IPA, send it to the
> pipeline handler by using libcamera::controls::LensPosition and have
> the CameraLens class translate it to the device-specific value.
>
> I'm a bit lazy and I'm not chasing how lensPosition_ is here computed,
> but have you considered how this could be generalized already ? As an
> example, it seems to me the values I get are in the 0-180 range, while
> in example the lens I'm using ranges from 0-4096...
>
> > + }
> > +
> > +private:
> > + void afCoarseScan()
> > + {
> > + if (coarseCompleted_)
> > + return;
> > +
> > + if (afScan(kCoarseSearchStep)) {
> > + coarseCompleted_ = true;
> > + maxContrast_ = 0;
> > + lensPosition_ = lensPosition_ - (lensPosition_ * kFineRange);
> > + previousContrast_ = 0;
> > + maxStep_ = std::clamp(lensPosition_ + static_cast<uint32_t>((lensPosition_ * kFineRange)),
> > + 0U, highStep_);
> > + }
> > + }
> > +
> > + void afFineScan()
> > + {
> > + if (!coarseCompleted_)
> > + return;
> > +
> > + if (afScan(kFineSearchStep)) {
> > + LOG(Af, Debug) << "AF found the best focus position !";
> > + state_ = libcamera::controls::AfStateFocused;
> > + fineCompleted_ = true;
> > + }
> > + }
> > +
> > + bool afScan(uint32_t minSteps)
> > + {
> > + if (lensPosition_ + minSteps > maxStep_) {
> > + /* If the max step is reached, move lens to the position. */
> > + lensPosition_ = bestPosition_;
> > + return true;
> > + } else {
> > + /*
> > + * Find the maximum of the variance by estimating its
> > + * derivative. If the direction changes, it means we have passed
> > + * a maximum one step before.
> > + */
> > + if ((currentContrast_ - maxContrast_) >= -(maxContrast_ * 0.1)) {
> > + /*
> > + * Positive and zero derivative:
> > + * The variance is still increasing. The focus could be
> > + * increased for the next comparison. Also, the max
> > + * variance and previous focus value are updated.
> > + */
> > + bestPosition_ = lensPosition_;
> > + lensPosition_ += minSteps;
> > + maxContrast_ = currentContrast_;
> > + } else {
> > + /*
> > + * Negative derivative:
> > + * The variance starts to decrease which means the maximum
> > + * variance is found. Set focus step to previous good one
> > + * then return immediately.
> > + */
> > + lensPosition_ = bestPosition_;
> > + return true;
> > + }
> > + }
> > +
> > + previousContrast_ = currentContrast_;
> > + LOG(Af, Debug) << " Previous step is " << bestPosition_
> > + << " Current step is " << lensPosition_;
> > + return false;
> > + }
> > +
> > + void afReset()
> > + {
> > + LOG(Af, Debug) << "Reset AF parameters";
> > + lensPosition_ = lowStep_;
> > + maxStep_ = highStep_;
> > + state_ = libcamera::controls::AfStateScanning;
> > + previousContrast_ = 0.0;
> > + coarseCompleted_ = false;
> > + fineCompleted_ = false;
> > + maxContrast_ = 0.0;
> > + }
> > +
> > + bool afIsOutOfFocus()
> > + {
> > + const uint32_t diff_var = std::abs(currentContrast_ -
> > + maxContrast_);
> > + const double var_ratio = diff_var / maxContrast_;
> > + LOG(Af, Debug) << "Variance change rate: " << var_ratio
> > + << " Current VCM step: " << lensPosition_;
> > + if (var_ratio > kMaxChange)
> > + return true;
> > + else
> > + return false;
> > + }
> > +
> > + controls::AfModeEnum mode_;
> > + controls::AfStateEnum state_;
> > + controls::AfPauseStateEnum pauseState_;
> > +
> > + /* VCM step configuration. It is the current setting of the VCM step. */
> > + uint32_t lensPosition_;
> > + /* The best VCM step. It is a local optimum VCM step during scanning. */
> > + uint32_t bestPosition_;
> > +
> > + /* Current AF statistic contrast. */
> > + double currentContrast_;
> > + /* It is used to determine the derivative during scanning */
> > + double previousContrast_;
> > + double maxContrast_;
> > + /* The designated maximum range of focus scanning. */
> > + uint32_t maxStep_;
> > + /* If the coarse scan completes, it is set to true. */
> > + bool coarseCompleted_;
> > + /* If the fine scan completes, it is set to true. */
> > + bool fineCompleted_;
> > +
> > + uint32_t lowStep_;
> > + uint32_t highStep_;
> > +
> > + /*
> > + * Maximum focus steps of the VCM control
> > + * \todo should be obtained from the VCM driver
> > + */
> > + static constexpr uint32_t kMaxFocusSteps = 1023;
> > +
> > + /* Minimum focus step for searching appropriate focus */
> > + static constexpr uint32_t kCoarseSearchStep = 30;
> > + static constexpr uint32_t kFineSearchStep = 1;
> > +
> > + /* Max ratio of variance change, 0.0 < kMaxChange < 1.0 */
> > + static constexpr double kMaxChange = 0.5;
> > +
> > + /* Fine scan range 0 < kFineRange < 1 */
> > + static constexpr double kFineRange = 0.05;
> > +};
> > +
> > +} /* namespace ipa::common::algorithms */
> > +} /* namespace libcamera */
> > diff --git a/src/ipa/libipa/algorithms/meson.build b/src/ipa/libipa/algorithms/meson.build
> > index ab8da13a..860dc199 100644
> > --- a/src/ipa/libipa/algorithms/meson.build
> > +++ b/src/ipa/libipa/algorithms/meson.build
> > @@ -2,8 +2,10 @@
> >
> > common_ipa_algorithms_headers = files([
> > 'af_algorithm.h',
> > + 'af_hill_climbing.h',
> > ])
> >
> > common_ipa_algorithms_sources = files([
> > 'af_algorithm.cpp',
> > + 'af_hill_climbing.cpp',
> > ])
> > --
> > 2.34.1
> >
More information about the libcamera-devel
mailing list