From e8054c758c3dd87b4a8a5579398d0ab971ed97b0 Mon Sep 17 00:00:00 2001 From: Stefan Klug Date: Wed, 28 Jan 2026 17:00:30 +0100 Subject: [PATCH] ipa: rkisp1: lsc: Resample polynomial lens shading tables at configure time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The lens shading correction is always applied based on the sensor crop bounds. This leads to incorrect lens shading correction for analog crops that do not cover the whole sensor. To fix that, we need to adapt the lens shading table for the selected analog crop at configure time. Introduce an abstract ShadingDescriptor class that holds the lens shading information that can then be sampled at configure time for a specific crop rectangle. Resampling for a specific crop is only implemented for polynomial lsc data. For tabular data, a warning is logged and the unmodified table is returned. This matches the current functionality for tabular data and is a huge improvement for polynomial data. Signed-off-by: Stefan Klug Reviewed-by: Kieran Bingham Reviewed-by: Barnabás Pőcze --- src/ipa/rkisp1/algorithms/lsc.cpp | 263 ++++++++++++++++++------------ src/ipa/rkisp1/algorithms/lsc.h | 13 ++ 2 files changed, 174 insertions(+), 102 deletions(-) diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index 761e5a76..c7a40a64 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -72,115 +72,54 @@ namespace { constexpr int kColourTemperatureQuantization = 10; -class LscPolynomialLoader +class LscPolynomialShadingDescriptor : public LensShadingCorrection::ShadingDescriptor { public: - LscPolynomialLoader(const Size &sensorSize, - const Rectangle &cropRectangle, - const std::vector &xSizes, - const std::vector &ySizes) - : sensorSize_(sensorSize), - cropRectangle_(cropRectangle), - xSizes_(xSizes), - ySizes_(ySizes) + LscPolynomialShadingDescriptor(const LscPolynomial &pr, const LscPolynomial &pgr, + const LscPolynomial &pgb, const LscPolynomial &pb) + : pr_(pr), pgr_(pgr), pgb_(pgb), pb_(pb) { } - int parseLscData(const YamlObject &yamlSets, - std::map &lscData); + LensShadingCorrection::Components sampleForCrop(const Rectangle &cropRectangle, + Span xSizes, + Span ySizes) override; private: - std::vector sizesListToPositions(const std::vector &sizes); std::vector samplePolynomial(const LscPolynomial &poly, Span xPositions, Span yPositions, const Rectangle &cropRectangle); - Size sensorSize_; - Rectangle cropRectangle_; - const std::vector &xSizes_; - const std::vector &ySizes_; + std::vector sizesListToPositions(Span sizes); + + LscPolynomial pr_; + LscPolynomial pgr_; + LscPolynomial pgb_; + LscPolynomial pb_; }; -int LscPolynomialLoader::parseLscData(const YamlObject &yamlSets, - std::map &lscData) +LensShadingCorrection::Components +LscPolynomialShadingDescriptor::sampleForCrop(const Rectangle &cropRectangle, + Span xSizes, + Span ySizes) { - const auto &sets = yamlSets.asList(); - for (const auto &yamlSet : sets) { - std::optional pr, pgr, pgb, pb; - uint32_t ct = yamlSet["ct"].get(0); + std::vector xPos = sizesListToPositions(xSizes); + std::vector yPos = sizesListToPositions(ySizes); - if (lscData.count(ct)) { - LOG(RkISP1Lsc, Error) - << "Multiple sets found for " - << "color temperature " << ct; - return -EINVAL; - } - - LensShadingCorrection::Components &set = lscData[ct]; - pr = yamlSet["r"].get(); - pgr = yamlSet["gr"].get(); - pgb = yamlSet["gb"].get(); - pb = yamlSet["b"].get(); - - if (!(pr || pgr || pgb || pb)) { - LOG(RkISP1Lsc, Error) - << "Failed to parse polynomial for " - << "colour temperature " << ct; - return -EINVAL; - } - - pr->setReferenceImageSize(sensorSize_); - pgr->setReferenceImageSize(sensorSize_); - pgb->setReferenceImageSize(sensorSize_); - pb->setReferenceImageSize(sensorSize_); - - std::vector xPos = sizesListToPositions(xSizes_); - std::vector yPos = sizesListToPositions(ySizes_); - set.r = samplePolynomial(*pr, xPos, yPos, cropRectangle_); - set.gr = samplePolynomial(*pgr, xPos, yPos, cropRectangle_); - set.gb = samplePolynomial(*pgb, xPos, yPos, cropRectangle_); - set.b = samplePolynomial(*pb, xPos, yPos, cropRectangle_); - } - - if (lscData.empty()) { - LOG(RkISP1Lsc, Error) << "Failed to load any sets"; - return -EINVAL; - } - - return 0; + return { + .r = samplePolynomial(pr_, xPos, yPos, cropRectangle), + .gr = samplePolynomial(pgr_, xPos, yPos, cropRectangle), + .gb = samplePolynomial(pgb_, xPos, yPos, cropRectangle), + .b = samplePolynomial(pb_, xPos, yPos, cropRectangle) + }; } -/* - * The rkisp1 LSC grid spacing is defined by the cell sizes on the top-left - * quadrant of the grid. This is then mirrored in hardware to the other - * quadrants. See parseSizes() for further details. For easier handling, this - * function converts the cell sizes of half the grid to a list of position of - * the whole grid (on one axis). Example: - * - * input: | 0.2 | 0.3 | - * output: 0.0 0.2 0.5 0.8 1.0 - */ -std::vector LscPolynomialLoader::sizesListToPositions(const std::vector &sizes) -{ - const int half = sizes.size(); - std::vector positions(half * 2 + 1); - double x = 0.0; - - positions[half] = 0.5; - for (int i = 1; i <= half; i++) { - x += sizes[half - i]; - positions[half - i] = 0.5 - x; - positions[half + i] = 0.5 + x; - } - - return positions; -} - -std::vector LscPolynomialLoader::samplePolynomial(const LscPolynomial &poly, - Span xPositions, - Span yPositions, - const Rectangle &cropRectangle) +std::vector +LscPolynomialShadingDescriptor::samplePolynomial(const LscPolynomial &poly, + Span xPositions, + Span yPositions, + const Rectangle &cropRectangle) { double m = poly.getM(); double x0 = cropRectangle.x / m; @@ -203,18 +142,127 @@ std::vector LscPolynomialLoader::samplePolynomial(const LscPolynomial int v = static_cast( poly.sampleAtNormalizedPixelPos(xp, yp) * 1024); - v = std::min(std::max(v, 1024), 4095); + v = std::clamp(v, 1024, 4095); samples.push_back(v); } } return samples; } +/* + * The rkisp1 LSC grid spacing is defined by the cell sizes on the top-left + * quadrant of the grid. This is then mirrored in hardware to the other + * quadrants. See parseSizes() for further details. For easier handling, this + * function converts the cell sizes of half the grid to a list of position of + * the whole grid (on one axis). Example: + * + * input: | 0.2 | 0.3 | + * output: 0.0 0.2 0.5 0.8 1.0 + */ +std::vector +LscPolynomialShadingDescriptor::sizesListToPositions(Span sizes) +{ + const int half = sizes.size(); + std::vector positions(half * 2 + 1); + double x = 0.0; + + positions[half] = 0.5; + for (int i = 1; i <= half; i++) { + x += sizes[half - i]; + positions[half - i] = 0.5 - x; + positions[half + i] = 0.5 + x; + } + + return positions; +} + +class LscPolynomialLoader +{ +public: + LscPolynomialLoader(const Size &sensorSize) + : sensorSize_(sensorSize) + { + } + + int parseLscData(const YamlObject &yamlSets, + LensShadingCorrection::ShadingDescriptorMap &lscData); + +private: + Size sensorSize_; +}; + +int LscPolynomialLoader::parseLscData(const YamlObject &yamlSets, + LensShadingCorrection::ShadingDescriptorMap &lscData) +{ + const auto &sets = yamlSets.asList(); + for (const auto &yamlSet : sets) { + std::optional pr, pgr, pgb, pb; + uint32_t ct = yamlSet["ct"].get(0); + + if (lscData.count(ct)) { + LOG(RkISP1Lsc, Error) + << "Multiple sets found for " + << "color temperature " << ct; + return -EINVAL; + } + + pr = yamlSet["r"].get(); + pgr = yamlSet["gr"].get(); + pgb = yamlSet["gb"].get(); + pb = yamlSet["b"].get(); + + if (!(pr || pgr || pgb || pb)) { + LOG(RkISP1Lsc, Error) + << "Failed to parse polynomial for " + << "colour temperature " << ct; + return -EINVAL; + } + + pr->setReferenceImageSize(sensorSize_); + pgr->setReferenceImageSize(sensorSize_); + pgb->setReferenceImageSize(sensorSize_); + pb->setReferenceImageSize(sensorSize_); + + lscData.emplace( + ct, std::make_unique( + *pr, *pgr, *pgb, *pb)); + } + + if (lscData.empty()) { + LOG(RkISP1Lsc, Error) << "Failed to load any sets"; + return -EINVAL; + } + + return 0; +} + +class LscTableShadingDescriptor : public LensShadingCorrection::ShadingDescriptor +{ +public: + LscTableShadingDescriptor(LensShadingCorrection::Components components) + : lscData_(std::move(components)) + { + } + + LensShadingCorrection::Components + sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle, + [[maybe_unused]] Span xSizes, + [[maybe_unused]] Span ySizes) override + { + LOG(RkISP1Lsc, Warning) + << "Tabular LSC data doesn't support resampling"; + return lscData_; + } + +private: + LensShadingCorrection::Components lscData_; +}; + class LscTableLoader { public: int parseLscData(const YamlObject &yamlSets, - std::map &lscData); + LensShadingCorrection::ShadingDescriptorMap &lscData); private: std::vector parseTable(const YamlObject &tuningData, @@ -222,7 +270,7 @@ private: }; int LscTableLoader::parseLscData(const YamlObject &yamlSets, - std::map &lscData) + LensShadingCorrection::ShadingDescriptorMap &lscData) { const auto &sets = yamlSets.asList(); @@ -236,8 +284,7 @@ int LscTableLoader::parseLscData(const YamlObject &yamlSets, return -EINVAL; } - LensShadingCorrection::Components &set = lscData[ct]; - + LensShadingCorrection::Components set; set.r = parseTable(yamlSet, "r"); set.gr = parseTable(yamlSet, "gr"); set.gb = parseTable(yamlSet, "gb"); @@ -250,6 +297,9 @@ int LscTableLoader::parseLscData(const YamlObject &yamlSets, << " is missing tables"; return -EINVAL; } + + lscData.emplace( + ct, std::make_unique(std::move(set))); } if (lscData.empty()) { @@ -341,7 +391,7 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, return -EINVAL; } - std::map lscData; + ShadingDescriptorMap lscData; int ret = 0; std::string type = tuningData["type"].get("table"); @@ -351,10 +401,11 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, ret = loader.parseLscData(yamlSets, lscData); } else if (type == "polynomial") { LOG(RkISP1Lsc, Debug) << "Loading polynomial LSC data."; - auto loader = LscPolynomialLoader(context.sensorInfo.activeAreaSize, - context.sensorInfo.analogCrop, - xSize_, - ySize_); + /* + * \todo: Most likely the reference frame should be native_size. + * Let's wait how the internal discussions progress. + */ + auto loader = LscPolynomialLoader(context.sensorInfo.activeAreaSize); ret = loader.parseLscData(yamlSets, lscData); } else { LOG(RkISP1Lsc, Error) << "Unsupported LSC data type '" @@ -365,7 +416,7 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, if (ret) return ret; - sets_.setData(std::move(lscData)); + shadingDescriptors_ = std::move(lscData); return 0; } @@ -401,6 +452,14 @@ int LensShadingCorrection::configure(IPAContext &context, yGrad_[i] = std::round(32768 / ySizes_[i]); } + LOG(RkISP1Lsc, Debug) << "Sample LSC data for " << configInfo.analogCrop; + std::map shadingData; + for (const auto &[t, descriptor] : shadingDescriptors_) + shadingData[t] = descriptor->sampleForCrop(configInfo.analogCrop, + xSize_, ySize_); + + sets_.setData(std::move(shadingData)); + context.configuration.lsc.enabled = true; return 0; } diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h index 7b68dda1..3097740a 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include "libipa/interpolator.h" @@ -36,10 +37,22 @@ public: std::vector b; }; + class ShadingDescriptor + { + public: + virtual ~ShadingDescriptor() = default; + virtual Components sampleForCrop(const Rectangle &cropRectangle, + Span xSizes, + Span ySizes) = 0; + }; + + using ShadingDescriptorMap = std::map>; + private: void setParameters(rkisp1_cif_isp_lsc_config &config); void copyTable(rkisp1_cif_isp_lsc_config &config, const Components &set0); + ShadingDescriptorMap shadingDescriptors_; ipa::Interpolator sets_; std::vector xSize_; std::vector ySize_;