| Message ID | 20260810201957.19623-4-mzamazal@redhat.com |
|---|---|
| State | Superseded |
| Headers | show |
| Series |
|
| Related | show |
2026. 08. 10. 22:19 keltezéssel, Milan Zamazal írta: > From: Xander Pronk <xander.c.pronk@gmail.com> > > The algorithm is based on the common libipa lens shading correction > implementation. The grid values obtained from the libipa algorithm are > passed to the debayer algorithm as an array and used as an an RGB > texture. > > Notes on the implementation: > > - The overall idea is to keep things simple, to not make the LSC > computation unnecessarily expensive. > > - LscAlgorithm accepts only quantised types. UQ<2,6> is used, to be > converted to float in debayering. > > - The limit of 100 degrees to consider a temperature change noticeable > is arbitrary. > > Co-developed-by: Rick ten Wolde <rick_libcamera@wolde.info> > Signed-off-by: Rick ten Wolde <rick_libcamera@wolde.info> > Signed-off-by: Xander Pronk <xander.c.pronk@gmail.com> > Signed-off-by: Milan Zamazal <mzamazal@redhat.com> > --- > .../internal/software_isp/debayer_params.h | 12 ++- > src/ipa/simple/algorithms/lsc.cpp | 96 +++++++++++++++++++ > src/ipa/simple/algorithms/lsc.h | 55 +++++++++++ > src/ipa/simple/algorithms/meson.build | 1 + > src/ipa/simple/ipa_context.h | 5 + > src/ipa/simple/soft_simple.cpp | 2 + > src/libcamera/software_isp/debayer.cpp | 8 ++ > 7 files changed, 178 insertions(+), 1 deletion(-) > create mode 100644 src/ipa/simple/algorithms/lsc.cpp > create mode 100644 src/ipa/simple/algorithms/lsc.h > > diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h > index 93dcf42e6..b7860c9dd 100644 > --- a/include/libcamera/internal/software_isp/debayer_params.h > +++ b/include/libcamera/internal/software_isp/debayer_params.h > @@ -35,7 +35,17 @@ struct DebayerParams { > static constexpr unsigned int kLscValuesPerCell = 4; > using LscLookupTable = > std::array<uint8_t, kLscGridSize * kLscGridSize * kLscValuesPerCell>; > - LscLookupTable lscLut{}; > + static constexpr auto identityLscLut = [] { > + LscLookupTable lut = {}; > + /* lut.fill(64) could be used, but it fails with older gcc versions */ > + for (size_t i = 0; kLscValuesPerCell * i < lut.size(); i++) { > + lut[i * kLscValuesPerCell + 0] = 64; /* == UQ<2, 6>(1.0f).quantized() */ > + lut[i * kLscValuesPerCell + 1] = 64; > + lut[i * kLscValuesPerCell + 2] = 64; > + } > + return lut; > + }(); > + LscLookupTable lscLut = identityLscLut; I feel like this and the initilization part could go into the previous change, but it's probably not a big deal. > uint64_t lscLutVersion = 0; > }; > > diff --git a/src/ipa/simple/algorithms/lsc.cpp b/src/ipa/simple/algorithms/lsc.cpp > new file mode 100644 > index 000000000..d515be3c2 > --- /dev/null > +++ b/src/ipa/simple/algorithms/lsc.cpp > @@ -0,0 +1,96 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Lens shading correction > + */ > + > +#include "lsc.h" > + > +#include <libcamera/base/log.h> > + > +namespace libcamera { > + > +namespace ipa::soft::algorithms { > + > +LOG_DEFINE_CATEGORY(IPASoftLsc) > + > +int Lsc::init(IPAContext &context, const ValueNode &tuningData) > +{ > + static constexpr unsigned int kGridSize = DebayerParams::kLscGridSize; > + > + for (unsigned int i = 0; i < kGridSize; i++) > + gridPos_.push_back(static_cast<double>(i) / (kGridSize - 1)); > + > + return lscAlgo_.init(tuningData, context.ctrlMap, > + { .keys = { "r", "g", "b" }, > + .numHSamples = kGridSize, > + .numVSamples = kGridSize, > + .sensorSize = context.sensorInfo.activeAreaSize }); > +} > + > +int Lsc::configure(IPAContext &context, > + [[maybe_unused]] const IPAConfigInfo &configInfo) > +{ > + return lscAlgo_.configure(context.activeState.lsc, > + context.sensorInfo.analogCrop, > + gridPos_, gridPos_); > +} > + > +void Lsc::prepare([[maybe_unused]] IPAContext &context, > + [[maybe_unused]] const uint32_t frame, > + IPAFrameContext &frameContext, > + DebayerParams *params) > +{ > + unsigned int ct = frameContext.awb.colourTemperature; > + constexpr unsigned int minTemperatureChange = 100; > + > + if (!frameContext.lsc.enabled) { > + if (lastAppliedCt_ != 0) { > + params->lscLut = DebayerParams::identityLscLut; > + lastAppliedCt_ = 0; I'm a bit confused now. Shouldn't every change to `lscLut` also imply a change to `lscLutVersion`? It was there in the previous version. Otherwise the debayering part won't update the texture, no? > + } > + return; > + } > + > + if (utils::abs_diff(ct, lastAppliedCt_) < minTemperatureChange) > + return; > + > + const auto &set = lscAlgo_.interpolateComponents(ct); > + > + const auto &red = set.at("r"); > + const auto &green = set.at("g"); > + const auto &blue = set.at("b"); > + > + DebayerParams::LscLookupTable &lut = params->lscLut; > + constexpr unsigned int gridSize = DebayerParams::kLscGridSize; > + for (unsigned int i = 0, j = 0; i < gridSize * gridSize; i++) { > + lut[j++] = red[i]; > + lut[j++] = green[i]; > + lut[j++] = blue[i]; > + lut[j++] = 0; /* padding */ > + } > + params->lscLutVersion++; > + > + lastAppliedCt_ = ct; > +} > + > +void Lsc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame, > + IPAFrameContext &frameContext, const ControlList &controls) > +{ > + lscAlgo_.queueRequest(context.activeState.lsc, frameContext.lsc, > + controls); > +} > + > +void Lsc::process([[maybe_unused]] IPAContext &context, > + [[maybe_unused]] const uint32_t frame, > + IPAFrameContext &frameContext, > + [[maybe_unused]] const SwIspStats *stats, > + ControlList &metadata) > +{ > + lscAlgo_.process(frameContext.lsc, metadata); > +} > + > +REGISTER_IPA_ALGORITHM(Lsc, "Lsc") > + > +} /* namespace ipa::soft::algorithms */ > + > +} /* namespace libcamera */ > [...]
Hi Barnabás, thank you for reviews. Barnabás Pőcze <barnabas.pocze@ideasonboard.com> writes: > 2026. 08. 10. 22:19 keltezéssel, Milan Zamazal írta: >> From: Xander Pronk <xander.c.pronk@gmail.com> >> The algorithm is based on the common libipa lens shading correction >> implementation. The grid values obtained from the libipa algorithm are >> passed to the debayer algorithm as an array and used as an an RGB >> texture. >> Notes on the implementation: >> - The overall idea is to keep things simple, to not make the LSC >> computation unnecessarily expensive. >> - LscAlgorithm accepts only quantised types. UQ<2,6> is used, to be >> converted to float in debayering. >> - The limit of 100 degrees to consider a temperature change noticeable >> is arbitrary. >> Co-developed-by: Rick ten Wolde <rick_libcamera@wolde.info> >> Signed-off-by: Rick ten Wolde <rick_libcamera@wolde.info> >> Signed-off-by: Xander Pronk <xander.c.pronk@gmail.com> >> Signed-off-by: Milan Zamazal <mzamazal@redhat.com> >> --- >> .../internal/software_isp/debayer_params.h | 12 ++- >> src/ipa/simple/algorithms/lsc.cpp | 96 +++++++++++++++++++ >> src/ipa/simple/algorithms/lsc.h | 55 +++++++++++ >> src/ipa/simple/algorithms/meson.build | 1 + >> src/ipa/simple/ipa_context.h | 5 + >> src/ipa/simple/soft_simple.cpp | 2 + >> src/libcamera/software_isp/debayer.cpp | 8 ++ >> 7 files changed, 178 insertions(+), 1 deletion(-) >> create mode 100644 src/ipa/simple/algorithms/lsc.cpp >> create mode 100644 src/ipa/simple/algorithms/lsc.h >> diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h >> index 93dcf42e6..b7860c9dd 100644 >> --- a/include/libcamera/internal/software_isp/debayer_params.h >> +++ b/include/libcamera/internal/software_isp/debayer_params.h >> @@ -35,7 +35,17 @@ struct DebayerParams { >> static constexpr unsigned int kLscValuesPerCell = 4; >> using LscLookupTable = >> std::array<uint8_t, kLscGridSize * kLscGridSize * kLscValuesPerCell>; >> - LscLookupTable lscLut{}; >> + static constexpr auto identityLscLut = [] { >> + LscLookupTable lut = {}; >> + /* lut.fill(64) could be used, but it fails with older gcc versions */ >> + for (size_t i = 0; kLscValuesPerCell * i < lut.size(); i++) { >> + lut[i * kLscValuesPerCell + 0] = 64; /* == UQ<2, 6>(1.0f).quantized() */ >> + lut[i * kLscValuesPerCell + 1] = 64; >> + lut[i * kLscValuesPerCell + 2] = 64; >> + } >> + return lut; >> + }(); >> + LscLookupTable lscLut = identityLscLut; > > I feel like this and the initilization part could go into the previous change, > but it's probably not a big deal. I think it's clearer here why it is initialised how it is but no big deal either. > >> uint64_t lscLutVersion = 0; >> }; >> diff --git a/src/ipa/simple/algorithms/lsc.cpp b/src/ipa/simple/algorithms/lsc.cpp >> new file mode 100644 >> index 000000000..d515be3c2 >> --- /dev/null >> +++ b/src/ipa/simple/algorithms/lsc.cpp >> @@ -0,0 +1,96 @@ >> +/* SPDX-License-Identifier: LGPL-2.1-or-later */ >> +/* >> + * Lens shading correction >> + */ >> + >> +#include "lsc.h" >> + >> +#include <libcamera/base/log.h> >> + >> +namespace libcamera { >> + >> +namespace ipa::soft::algorithms { >> + >> +LOG_DEFINE_CATEGORY(IPASoftLsc) >> + >> +int Lsc::init(IPAContext &context, const ValueNode &tuningData) >> +{ >> + static constexpr unsigned int kGridSize = DebayerParams::kLscGridSize; >> + >> + for (unsigned int i = 0; i < kGridSize; i++) >> + gridPos_.push_back(static_cast<double>(i) / (kGridSize - 1)); >> + >> + return lscAlgo_.init(tuningData, context.ctrlMap, >> + { .keys = { "r", "g", "b" }, >> + .numHSamples = kGridSize, >> + .numVSamples = kGridSize, >> + .sensorSize = context.sensorInfo.activeAreaSize }); >> +} >> + >> +int Lsc::configure(IPAContext &context, >> + [[maybe_unused]] const IPAConfigInfo &configInfo) >> +{ >> + return lscAlgo_.configure(context.activeState.lsc, >> + context.sensorInfo.analogCrop, >> + gridPos_, gridPos_); >> +} >> + >> +void Lsc::prepare([[maybe_unused]] IPAContext &context, >> + [[maybe_unused]] const uint32_t frame, >> + IPAFrameContext &frameContext, >> + DebayerParams *params) >> +{ >> + unsigned int ct = frameContext.awb.colourTemperature; >> + constexpr unsigned int minTemperatureChange = 100; >> + >> + if (!frameContext.lsc.enabled) { >> + if (lastAppliedCt_ != 0) { >> + params->lscLut = DebayerParams::identityLscLut; >> + lastAppliedCt_ = 0; > > I'm a bit confused now. Shouldn't every change to `lscLut` also imply > a change to `lscLutVersion`? It was there in the previous version. Otherwise > the debayering part won't update the texture, no? I think so, probably omitted by mistake. > >> + } >> + return; >> + } >> + >> + if (utils::abs_diff(ct, lastAppliedCt_) < minTemperatureChange) >> + return; >> + >> + const auto &set = lscAlgo_.interpolateComponents(ct); >> + >> + const auto &red = set.at("r"); >> + const auto &green = set.at("g"); >> + const auto &blue = set.at("b"); >> + >> + DebayerParams::LscLookupTable &lut = params->lscLut; >> + constexpr unsigned int gridSize = DebayerParams::kLscGridSize; >> + for (unsigned int i = 0, j = 0; i < gridSize * gridSize; i++) { >> + lut[j++] = red[i]; >> + lut[j++] = green[i]; >> + lut[j++] = blue[i]; >> + lut[j++] = 0; /* padding */ >> + } >> + params->lscLutVersion++; >> + >> + lastAppliedCt_ = ct; >> +} >> + >> +void Lsc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame, >> + IPAFrameContext &frameContext, const ControlList &controls) >> +{ >> + lscAlgo_.queueRequest(context.activeState.lsc, frameContext.lsc, >> + controls); >> +} >> + >> +void Lsc::process([[maybe_unused]] IPAContext &context, >> + [[maybe_unused]] const uint32_t frame, >> + IPAFrameContext &frameContext, >> + [[maybe_unused]] const SwIspStats *stats, >> + ControlList &metadata) >> +{ >> + lscAlgo_.process(frameContext.lsc, metadata); >> +} >> + >> +REGISTER_IPA_ALGORITHM(Lsc, "Lsc") >> + >> +} /* namespace ipa::soft::algorithms */ >> + >> +} /* namespace libcamera */ >> [...]
diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h index 93dcf42e6..b7860c9dd 100644 --- a/include/libcamera/internal/software_isp/debayer_params.h +++ b/include/libcamera/internal/software_isp/debayer_params.h @@ -35,7 +35,17 @@ struct DebayerParams { static constexpr unsigned int kLscValuesPerCell = 4; using LscLookupTable = std::array<uint8_t, kLscGridSize * kLscGridSize * kLscValuesPerCell>; - LscLookupTable lscLut{}; + static constexpr auto identityLscLut = [] { + LscLookupTable lut = {}; + /* lut.fill(64) could be used, but it fails with older gcc versions */ + for (size_t i = 0; kLscValuesPerCell * i < lut.size(); i++) { + lut[i * kLscValuesPerCell + 0] = 64; /* == UQ<2, 6>(1.0f).quantized() */ + lut[i * kLscValuesPerCell + 1] = 64; + lut[i * kLscValuesPerCell + 2] = 64; + } + return lut; + }(); + LscLookupTable lscLut = identityLscLut; uint64_t lscLutVersion = 0; }; diff --git a/src/ipa/simple/algorithms/lsc.cpp b/src/ipa/simple/algorithms/lsc.cpp new file mode 100644 index 000000000..d515be3c2 --- /dev/null +++ b/src/ipa/simple/algorithms/lsc.cpp @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Lens shading correction + */ + +#include "lsc.h" + +#include <libcamera/base/log.h> + +namespace libcamera { + +namespace ipa::soft::algorithms { + +LOG_DEFINE_CATEGORY(IPASoftLsc) + +int Lsc::init(IPAContext &context, const ValueNode &tuningData) +{ + static constexpr unsigned int kGridSize = DebayerParams::kLscGridSize; + + for (unsigned int i = 0; i < kGridSize; i++) + gridPos_.push_back(static_cast<double>(i) / (kGridSize - 1)); + + return lscAlgo_.init(tuningData, context.ctrlMap, + { .keys = { "r", "g", "b" }, + .numHSamples = kGridSize, + .numVSamples = kGridSize, + .sensorSize = context.sensorInfo.activeAreaSize }); +} + +int Lsc::configure(IPAContext &context, + [[maybe_unused]] const IPAConfigInfo &configInfo) +{ + return lscAlgo_.configure(context.activeState.lsc, + context.sensorInfo.analogCrop, + gridPos_, gridPos_); +} + +void Lsc::prepare([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + DebayerParams *params) +{ + unsigned int ct = frameContext.awb.colourTemperature; + constexpr unsigned int minTemperatureChange = 100; + + if (!frameContext.lsc.enabled) { + if (lastAppliedCt_ != 0) { + params->lscLut = DebayerParams::identityLscLut; + lastAppliedCt_ = 0; + } + return; + } + + if (utils::abs_diff(ct, lastAppliedCt_) < minTemperatureChange) + return; + + const auto &set = lscAlgo_.interpolateComponents(ct); + + const auto &red = set.at("r"); + const auto &green = set.at("g"); + const auto &blue = set.at("b"); + + DebayerParams::LscLookupTable &lut = params->lscLut; + constexpr unsigned int gridSize = DebayerParams::kLscGridSize; + for (unsigned int i = 0, j = 0; i < gridSize * gridSize; i++) { + lut[j++] = red[i]; + lut[j++] = green[i]; + lut[j++] = blue[i]; + lut[j++] = 0; /* padding */ + } + params->lscLutVersion++; + + lastAppliedCt_ = ct; +} + +void Lsc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const ControlList &controls) +{ + lscAlgo_.queueRequest(context.activeState.lsc, frameContext.lsc, + controls); +} + +void Lsc::process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] const SwIspStats *stats, + ControlList &metadata) +{ + lscAlgo_.process(frameContext.lsc, metadata); +} + +REGISTER_IPA_ALGORITHM(Lsc, "Lsc") + +} /* namespace ipa::soft::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/simple/algorithms/lsc.h b/src/ipa/simple/algorithms/lsc.h new file mode 100644 index 000000000..13d9f2bca --- /dev/null +++ b/src/ipa/simple/algorithms/lsc.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Lens shading correction + */ + +#pragma once + +#include <libipa/interpolator.h> + +#include "libipa/fixedpoint.h" +#include "libipa/lsc.h" + +#include "algorithm.h" +#include "ipa_context.h" + +namespace libcamera { + +namespace ipa { + +namespace soft::algorithms { + +class Lsc : public Algorithm +{ +public: + Lsc() = default; + ~Lsc() = default; + + int init(IPAContext &context, const ValueNode &tuningData) override; + int configure(IPAContext &context, + const IPAConfigInfo &configInfo) override; + void queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const ControlList &controls) override; + void prepare(IPAContext &context, + const uint32_t frame, + IPAFrameContext &frameContext, + DebayerParams *params) override; + void process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] const SwIspStats *stats, + ControlList &metadata) override; + +private: + LscAlgorithm<UQ<2, 6>> lscAlgo_; + + std::vector<double> gridPos_; + + unsigned int lastAppliedCt_ = 0; +}; + +} /* namespace soft::algorithms */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/simple/algorithms/meson.build b/src/ipa/simple/algorithms/meson.build index 73c637220..c9f6e5590 100644 --- a/src/ipa/simple/algorithms/meson.build +++ b/src/ipa/simple/algorithms/meson.build @@ -6,4 +6,5 @@ soft_simple_ipa_algorithms = files([ 'agc.cpp', 'blc.cpp', 'ccm.cpp', + 'lsc.cpp', ]) diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index ff312ae8f..23c2cfd0a 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -19,6 +19,7 @@ #include <libipa/awb.h> #include <libipa/ccm.h> #include <libipa/fc_queue.h> +#include "libipa/lsc.h" #include "core_ipa_interface.h" @@ -61,6 +62,8 @@ struct IPAActiveState { std::optional<float> contrast; std::optional<float> saturation; } knobs; + + ipa::lsc::ActiveState lsc; }; struct IPAFrameContext : public FrameContext { @@ -75,6 +78,7 @@ struct IPAFrameContext : public FrameContext { float gamma; std::optional<float> contrast; std::optional<float> saturation; + ipa::lsc::FrameContext lsc; }; struct IPAContext { @@ -89,6 +93,7 @@ struct IPAContext { FCQueue<IPAFrameContext> frameContexts; ControlInfoMap::Map ctrlMap; bool ccmEnabled = false; + ipa::lsc::ActiveState lsc; }; } /* namespace ipa::soft */ diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp index 629e1a32d..f48ef76a5 100644 --- a/src/ipa/simple/soft_simple.cpp +++ b/src/ipa/simple/soft_simple.cpp @@ -163,6 +163,8 @@ int IPASoftSimple::init(const IPASettings &settings, params_->gamma = 1.0 / algorithms::kDefaultGamma; params_->contrastExp = 1.0; params_->gains = { { 1.0, 1.0, 1.0 } }; + params_->lscLutVersion = 0; + params_->lscLut = DebayerParams::identityLscLut; /* combinedMatrix is reset for each frame. */ } diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp index 82cdf1dc2..6ebf7ed1a 100644 --- a/src/libcamera/software_isp/debayer.cpp +++ b/src/libcamera/software_isp/debayer.cpp @@ -66,6 +66,14 @@ namespace libcamera { * \brief Lens shading lookup table */ +/** + * \var DebayerParams::identityLscLut + * \brief Lens shading lookup table of identity mapping + * + * This table can be used to initialise the lens shading lookup table or to + * not apply any real lens shading correction. + */ + /** * \var DebayerParams::lscLutVersion * \brief Incremented on each \a lscLut change