diff options
Diffstat (limited to 'src/ipa/rkisp1/algorithms')
-rw-r--r-- | src/ipa/rkisp1/algorithms/agc.cpp | 386 | ||||
-rw-r--r-- | src/ipa/rkisp1/algorithms/agc.h | 39 | ||||
-rw-r--r-- | src/ipa/rkisp1/algorithms/algorithm.h | 14 | ||||
-rw-r--r-- | src/ipa/rkisp1/algorithms/awb.cpp | 321 | ||||
-rw-r--r-- | src/ipa/rkisp1/algorithms/awb.h | 21 | ||||
-rw-r--r-- | src/ipa/rkisp1/algorithms/blc.cpp | 57 | ||||
-rw-r--r-- | src/ipa/rkisp1/algorithms/blc.h | 20 | ||||
-rw-r--r-- | src/ipa/rkisp1/algorithms/cproc.cpp | 111 | ||||
-rw-r--r-- | src/ipa/rkisp1/algorithms/cproc.h | 33 | ||||
-rw-r--r-- | src/ipa/rkisp1/algorithms/dpcc.cpp | 251 | ||||
-rw-r--r-- | src/ipa/rkisp1/algorithms/dpcc.h | 32 | ||||
-rw-r--r-- | src/ipa/rkisp1/algorithms/dpf.cpp | 260 | ||||
-rw-r--r-- | src/ipa/rkisp1/algorithms/dpf.h | 38 | ||||
-rw-r--r-- | src/ipa/rkisp1/algorithms/filter.cpp | 216 | ||||
-rw-r--r-- | src/ipa/rkisp1/algorithms/filter.h | 33 | ||||
-rw-r--r-- | src/ipa/rkisp1/algorithms/gsl.cpp | 146 | ||||
-rw-r--r-- | src/ipa/rkisp1/algorithms/gsl.h | 35 | ||||
-rw-r--r-- | src/ipa/rkisp1/algorithms/lsc.cpp | 342 | ||||
-rw-r--r-- | src/ipa/rkisp1/algorithms/lsc.h | 59 | ||||
-rw-r--r-- | src/ipa/rkisp1/algorithms/meson.build | 6 |
20 files changed, 2074 insertions, 346 deletions
diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index a1bb7d97..50e0690f 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * agc.cpp - AGC/AEC mean-based control algorithm + * AGC/AEC mean-based control algorithm */ #include "agc.h" @@ -14,6 +14,7 @@ #include <libcamera/base/log.h> #include <libcamera/base/utils.h> +#include <libcamera/control_ids.h> #include <libcamera/ipa/core_ipa_interface.h> #include "libipa/histogram.h" @@ -35,32 +36,32 @@ namespace ipa::rkisp1::algorithms { LOG_DEFINE_CATEGORY(RkISP1Agc) -/* Limits for analogue gain values */ -static constexpr double kMinAnalogueGain = 1.0; -static constexpr double kMaxAnalogueGain = 8.0; - -/* \todo Honour the FrameDurationLimits control instead of hardcoding a limit */ -static constexpr utils::Duration kMaxShutterSpeed = 60ms; - -/* Number of frames to wait before calculating stats on minimum exposure */ -static constexpr uint32_t kNumStartupFrames = 10; - -/* Target value to reach for the top 2% of the histogram */ -static constexpr double kEvGainTarget = 0.5; +Agc::Agc() +{ + supportsRaw_ = true; +} -/* - * Relative luminance target. +/** + * \brief Initialise the AGC algorithm from tuning files + * \param[in] context The shared IPA context + * \param[in] tuningData The YamlObject containing Agc tuning data * - * It's a number that's chosen so that, when the camera points at a grey - * target, the resulting image brightness is considered right. + * This function calls the base class' tuningData parsers to discover which + * control values are supported. * - * \todo Why is the value different between IPU3 and RkISP1 ? + * \return 0 on success or errors from the base class */ -static constexpr double kRelativeLuminanceTarget = 0.4; - -Agc::Agc() - : frameCount_(0), numCells_(0), numHistBins_(0), filteredExposure_(0s) +int Agc::init(IPAContext &context, const YamlObject &tuningData) { + int ret; + + ret = parseTuningData(tuningData); + if (ret) + return ret; + + context.ctrlMap.merge(controls()); + + return 0; } /** @@ -73,21 +74,15 @@ Agc::Agc() int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) { /* Configure the default exposure and gain. */ - context.frameContext.agc.gain = std::max(context.configuration.agc.minAnalogueGain, kMinAnalogueGain); - context.frameContext.agc.exposure = 10ms / context.configuration.sensor.lineDuration; + context.activeState.agc.automatic.gain = context.configuration.sensor.minAnalogueGain; + context.activeState.agc.automatic.exposure = + 10ms / context.configuration.sensor.lineDuration; + context.activeState.agc.manual.gain = context.activeState.agc.automatic.gain; + context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure; + context.activeState.agc.autoEnabled = !context.configuration.raw; - /* - * According to the RkISP1 documentation: - * - versions < V12 have RKISP1_CIF_ISP_AE_MEAN_MAX_V10 entries, - * - versions >= V12 have RKISP1_CIF_ISP_AE_MEAN_MAX_V12 entries. - */ - if (context.configuration.hw.revision < RKISP1_V12) { - numCells_ = RKISP1_CIF_ISP_AE_MEAN_MAX_V10; - numHistBins_ = RKISP1_CIF_ISP_HIST_BIN_N_MAX_V10; - } else { - numCells_ = RKISP1_CIF_ISP_AE_MEAN_MAX_V12; - numHistBins_ = RKISP1_CIF_ISP_HIST_BIN_N_MAX_V12; - } + context.activeState.agc.constraintMode = constraintModes().begin()->first; + context.activeState.agc.exposureMode = exposureModeHelpers().begin()->first; /* * Define the measurement window for AGC as a centered rectangle @@ -98,131 +93,125 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) context.configuration.agc.measureWindow.h_size = 3 * configInfo.outputSize.width / 4; context.configuration.agc.measureWindow.v_size = 3 * configInfo.outputSize.height / 4; - /* \todo Use actual frame index by populating it in the frameContext. */ - frameCount_ = 0; + /* \todo Run this again when FrameDurationLimits is passed in */ + setLimits(context.configuration.sensor.minShutterSpeed, + context.configuration.sensor.maxShutterSpeed, + context.configuration.sensor.minAnalogueGain, + context.configuration.sensor.maxAnalogueGain); + + resetFrameCount(); + return 0; } /** - * \brief Apply a filter on the exposure value to limit the speed of changes - * \param[in] exposureValue The target exposure from the AGC algorithm - * - * The speed of the filter is adaptive, and will produce the target quicker - * during startup, or when the target exposure is within 20% of the most recent - * filter output. - * - * \return The filtered exposure + * \copydoc libcamera::ipa::Algorithm::queueRequest */ -utils::Duration Agc::filterExposure(utils::Duration exposureValue) +void Agc::queueRequest(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) { - double speed = 0.2; + auto &agc = context.activeState.agc; - /* Adapt instantly if we are in startup phase. */ - if (frameCount_ < kNumStartupFrames) - speed = 1.0; + if (!context.configuration.raw) { + const auto &agcEnable = controls.get(controls::AeEnable); + if (agcEnable && *agcEnable != agc.autoEnabled) { + agc.autoEnabled = *agcEnable; - /* - * If we are close to the desired result, go faster to avoid making - * multiple micro-adjustments. - * \todo Make this customisable? - */ - if (filteredExposure_ < 1.2 * exposureValue && - filteredExposure_ > 0.8 * exposureValue) - speed = sqrt(speed); + LOG(RkISP1Agc, Debug) + << (agc.autoEnabled ? "Enabling" : "Disabling") + << " AGC"; + } + } - filteredExposure_ = speed * exposureValue + - filteredExposure_ * (1.0 - speed); + const auto &exposure = controls.get(controls::ExposureTime); + if (exposure && !agc.autoEnabled) { + agc.manual.exposure = *exposure * 1.0us + / context.configuration.sensor.lineDuration; + + LOG(RkISP1Agc, Debug) + << "Set exposure to " << agc.manual.exposure; + } + + const auto &gain = controls.get(controls::AnalogueGain); + if (gain && !agc.autoEnabled) { + agc.manual.gain = *gain; + + LOG(RkISP1Agc, Debug) << "Set gain to " << agc.manual.gain; + } - LOG(RkISP1Agc, Debug) << "After filtering, exposure " << filteredExposure_; + frameContext.agc.autoEnabled = agc.autoEnabled; - return filteredExposure_; + if (!frameContext.agc.autoEnabled) { + frameContext.agc.exposure = agc.manual.exposure; + frameContext.agc.gain = agc.manual.gain; + } } /** - * \brief Estimate the new exposure and gain values - * \param[inout] frameContext The shared IPA frame Context - * \param[in] yGain The gain calculated on the current brightness level - * \param[in] iqMeanGain The gain calculated based on the relative luminance target + * \copydoc libcamera::ipa::Algorithm::prepare */ -void Agc::computeExposure(IPAContext &context, double yGain, double iqMeanGain) +void Agc::prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, rkisp1_params_cfg *params) { - IPASessionConfiguration &configuration = context.configuration; - IPAFrameContext &frameContext = context.frameContext; - - /* Get the effective exposure and gain applied on the sensor. */ - uint32_t exposure = frameContext.sensor.exposure; - double analogueGain = frameContext.sensor.gain; - - /* Use the highest of the two gain estimates. */ - double evGain = std::max(yGain, iqMeanGain); - - utils::Duration minShutterSpeed = configuration.agc.minShutterSpeed; - utils::Duration maxShutterSpeed = std::min(configuration.agc.maxShutterSpeed, - kMaxShutterSpeed); - - double minAnalogueGain = std::max(configuration.agc.minAnalogueGain, - kMinAnalogueGain); - double maxAnalogueGain = std::min(configuration.agc.maxAnalogueGain, - kMaxAnalogueGain); + if (frameContext.agc.autoEnabled) { + frameContext.agc.exposure = context.activeState.agc.automatic.exposure; + frameContext.agc.gain = context.activeState.agc.automatic.gain; + } - /* Consider within 1% of the target as correctly exposed. */ - if (utils::abs_diff(evGain, 1.0) < 0.01) + if (frame > 0) return; - /* extracted from Rpi::Agc::computeTargetExposure. */ - - /* Calculate the shutter time in seconds. */ - utils::Duration currentShutter = exposure * configuration.sensor.lineDuration; - - /* - * Update the exposure value for the next computation using the values - * of exposure and gain really used by the sensor. - */ - utils::Duration effectiveExposureValue = currentShutter * analogueGain; - - LOG(RkISP1Agc, Debug) << "Actual total exposure " << currentShutter * analogueGain - << " Shutter speed " << currentShutter - << " Gain " << analogueGain - << " Needed ev gain " << evGain; - - /* - * Calculate the current exposure value for the scene as the latest - * exposure value applied multiplied by the new estimated gain. - */ - utils::Duration exposureValue = effectiveExposureValue * evGain; + /* Configure the measurement window. */ + params->meas.aec_config.meas_window = context.configuration.agc.measureWindow; + /* Use a continuous method for measure. */ + params->meas.aec_config.autostop = RKISP1_CIF_ISP_EXP_CTRL_AUTOSTOP_0; + /* Estimate Y as (R + G + B) x (85/256). */ + params->meas.aec_config.mode = RKISP1_CIF_ISP_EXP_MEASURING_MODE_1; - /* Clamp the exposure value to the min and max authorized. */ - utils::Duration maxTotalExposure = maxShutterSpeed * maxAnalogueGain; - exposureValue = std::min(exposureValue, maxTotalExposure); - LOG(RkISP1Agc, Debug) << "Target total exposure " << exposureValue - << ", maximum is " << maxTotalExposure; + params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_AEC; + params->module_ens |= RKISP1_CIF_ISP_MODULE_AEC; + params->module_en_update |= RKISP1_CIF_ISP_MODULE_AEC; - /* - * Divide the exposure value as new exposure and gain values. - * \todo estimate if we need to desaturate - */ - exposureValue = filterExposure(exposureValue); + /* Configure histogram. */ + 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; - /* - * Push the shutter time up to the maximum first, and only then - * increase the gain. - */ - utils::Duration shutterTime = std::clamp<utils::Duration>(exposureValue / minAnalogueGain, - minShutterSpeed, maxShutterSpeed); - double stepGain = std::clamp(exposureValue / shutterTime, - minAnalogueGain, maxAnalogueGain); - LOG(RkISP1Agc, Debug) << "Divided up shutter and gain are " - << shutterTime << " and " - << stepGain; + /* Update the configuration for histogram. */ + params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_HST; + /* Enable the histogram measure unit. */ + params->module_ens |= RKISP1_CIF_ISP_MODULE_HST; + params->module_en_update |= RKISP1_CIF_ISP_MODULE_HST; +} - /* Update the estimated exposure and gain. */ - frameContext.agc.exposure = shutterTime / configuration.sensor.lineDuration; - frameContext.agc.gain = stepGain; +void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext, + ControlList &metadata) +{ + utils::Duration exposureTime = context.configuration.sensor.lineDuration + * frameContext.sensor.exposure; + metadata.set(controls::AnalogueGain, frameContext.sensor.gain); + metadata.set(controls::ExposureTime, exposureTime.get<std::micro>()); + + /* \todo Use VBlank value calculated from each frame exposure. */ + uint32_t vTotal = context.configuration.sensor.size.height + + context.configuration.sensor.defVBlank; + utils::Duration frameDuration = context.configuration.sensor.lineDuration + * vTotal; + metadata.set(controls::FrameDuration, frameDuration.get<std::micro>()); } /** * \brief Estimate the relative luminance of the frame with a given gain - * \param[in] ae The RkISP1 statistics and ISP results * \param[in] gain The gain to apply to the frame * * This function estimates the average relative luminance of the frame that @@ -236,8 +225,6 @@ void Agc::computeExposure(IPAContext &context, double yGain, double iqMeanGain) * YUV doesn't take into account the fact that the R, G and B components * contribute differently to the relative luminance. * - * \todo Have a dedicated YUV algorithm ? - * * The values are normalized to the [0.0, 1.0] range, where 1.0 corresponds to a * theoretical perfect reflector of 100% reference white. * @@ -246,113 +233,82 @@ void Agc::computeExposure(IPAContext &context, double yGain, double iqMeanGain) * * \return The relative luminance */ -double Agc::estimateLuminance(const rkisp1_cif_isp_ae_stat *ae, - double gain) +double Agc::estimateLuminance(double gain) const { double ySum = 0.0; /* Sum the averages, saturated to 255. */ - for (unsigned int aeCell = 0; aeCell < numCells_; aeCell++) - ySum += std::min(ae->exp_mean[aeCell] * gain, 255.0); + for (uint8_t expMean : expMeans_) + ySum += std::min(expMean * gain, 255.0); /* \todo Weight with the AWB gains */ - return ySum / numCells_ / 255; -} - -/** - * \brief Estimate the mean value of the top 2% of the histogram - * \param[in] hist The histogram statistics computed by the ImgU - * \return The mean value of the top 2% of the histogram - */ -double Agc::measureBrightness(const rkisp1_cif_isp_hist_stat *hist) const -{ - Histogram histogram{ Span<const uint32_t>(hist->hist_bins, numHistBins_) }; - /* Estimate the quantile mean of the top 2% of the histogram. */ - return histogram.interQuantileMean(0.98, 1.0); + return ySum / expMeans_.size() / 255; } /** * \brief Process RkISP1 statistics, and run AGC operations * \param[in] context The shared IPA context + * \param[in] frame The frame context sequence number + * \param[in] frameContext The current frame context * \param[in] stats The RKISP1 statistics and ISP results + * \param[out] metadata Metadata for the frame, to be filled by the algorithm * * Identify the current image brightness, and use that to estimate the optimal * new exposure and gain for the scene. */ -void Agc::process(IPAContext &context, - [[maybe_unused]] IPAFrameContext *frameContext, - const rkisp1_stat_buffer *stats) +void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const rkisp1_stat_buffer *stats, + ControlList &metadata) { + if (!stats) { + fillMetadata(context, frameContext, metadata); + return; + } + + /* + * \todo Verify that the exposure and gain applied by the sensor for + * this frame match what has been requested. This isn't a hard + * requirement for stability of the AGC (the guarantee we need in + * automatic mode is a perfect match between the frame and the values + * we receive), but is important in manual mode. + */ + const rkisp1_cif_isp_stat *params = &stats->params; ASSERT(stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP); - const rkisp1_cif_isp_ae_stat *ae = ¶ms->ae; - const rkisp1_cif_isp_hist_stat *hist = ¶ms->hist; - - double iqMean = measureBrightness(hist); - double iqMeanGain = kEvGainTarget * numHistBins_ / iqMean; + /* The lower 4 bits are fractional and meant to be discarded. */ + Histogram hist({ params->hist.hist_bins, context.hw->numHistogramBins }, + [](uint32_t x) { return x >> 4; }); + expMeans_ = { params->ae.exp_mean, context.hw->numAeCells }; /* - * Estimate the gain needed to achieve a relative luminance target. To - * account for non-linearity caused by saturation, the value needs to be - * estimated in an iterative process, as multiplying by a gain will not - * increase the relative luminance by the same factor if some image - * regions are saturated. + * The Agc algorithm needs to know the effective exposure value that was + * applied to the sensor when the statistics were collected. */ - double yGain = 1.0; - double yTarget = kRelativeLuminanceTarget; - - for (unsigned int i = 0; i < 8; i++) { - double yValue = estimateLuminance(ae, yGain); - double extra_gain = std::min(10.0, yTarget / (yValue + .001)); - - yGain *= extra_gain; - LOG(RkISP1Agc, Debug) << "Y value: " << yValue - << ", Y target: " << yTarget - << ", gives gain " << yGain; - if (extra_gain < 1.01) - break; - } - - computeExposure(context, yGain, iqMeanGain); - frameCount_++; -} + utils::Duration exposureTime = context.configuration.sensor.lineDuration + * frameContext.sensor.exposure; + double analogueGain = frameContext.sensor.gain; + utils::Duration effectiveExposureValue = exposureTime * analogueGain; -/** - * \copydoc libcamera::ipa::Algorithm::prepare - */ -void Agc::prepare(IPAContext &context, rkisp1_params_cfg *params) -{ - if (context.frameContext.frameCount > 0) - return; + utils::Duration shutterTime; + double aGain, dGain; + std::tie(shutterTime, aGain, dGain) = + calculateNewEv(context.activeState.agc.constraintMode, + context.activeState.agc.exposureMode, + hist, effectiveExposureValue); - /* Configure the measurement window. */ - params->meas.aec_config.meas_window = context.configuration.agc.measureWindow; - /* Use a continuous method for measure. */ - params->meas.aec_config.autostop = RKISP1_CIF_ISP_EXP_CTRL_AUTOSTOP_0; - /* Estimate Y as (R + G + B) x (85/256). */ - params->meas.aec_config.mode = RKISP1_CIF_ISP_EXP_MEASURING_MODE_1; + LOG(RkISP1Agc, Debug) + << "Divided up shutter, analogue gain and digital gain are " + << shutterTime << ", " << aGain << " and " << dGain; - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_AEC; - params->module_ens |= RKISP1_CIF_ISP_MODULE_AEC; - params->module_en_update |= RKISP1_CIF_ISP_MODULE_AEC; - - /* Configure histogram. */ - 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. */ - for (unsigned int histBin = 0; histBin < numHistBins_; histBin++) - params->meas.hst_config.hist_weight[histBin] = 1; - /* Step size can't be less than 3. */ - params->meas.hst_config.histogram_predivider = 4; + IPAActiveState &activeState = context.activeState; + /* Update the estimated exposure and gain. */ + activeState.agc.automatic.exposure = shutterTime / context.configuration.sensor.lineDuration; + activeState.agc.automatic.gain = aGain; - /* Update the configuration for histogram. */ - params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_HST; - /* Enable the histogram measure unit. */ - params->module_ens |= RKISP1_CIF_ISP_MODULE_HST; - params->module_en_update |= RKISP1_CIF_ISP_MODULE_HST; + fillMetadata(context, frameContext, metadata); + expMeans_ = {}; } REGISTER_IPA_ALGORITHM(Agc, "Agc") diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h index 22c02779..04b3247e 100644 --- a/src/ipa/rkisp1/algorithms/agc.h +++ b/src/ipa/rkisp1/algorithms/agc.h @@ -2,48 +2,53 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * agc.h - RkISP1 AGC/AEC mean-based control algorithm + * RkISP1 AGC/AEC mean-based control algorithm */ #pragma once #include <linux/rkisp1-config.h> +#include <libcamera/base/span.h> #include <libcamera/base/utils.h> #include <libcamera/geometry.h> +#include "libipa/agc_mean_luminance.h" +#include "libipa/histogram.h" + #include "algorithm.h" namespace libcamera { -struct IPACameraSensorInfo; - namespace ipa::rkisp1::algorithms { -class Agc : public Algorithm +class Agc : public Algorithm, public AgcMeanLuminance { public: Agc(); ~Agc() = default; + int init(IPAContext &context, const YamlObject &tuningData) override; int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; - void prepare(IPAContext &context, rkisp1_params_cfg *params) override; - void process(IPAContext &context, IPAFrameContext *frameContext, - const rkisp1_stat_buffer *stats) override; + void queueRequest(IPAContext &context, + const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + rkisp1_params_cfg *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const rkisp1_stat_buffer *stats, + ControlList &metadata) override; private: - void computeExposure(IPAContext &Context, double yGain, double iqMeanGain); - utils::Duration filterExposure(utils::Duration exposureValue); - double estimateLuminance(const rkisp1_cif_isp_ae_stat *ae, double gain); - double measureBrightness(const rkisp1_cif_isp_hist_stat *hist) const; - - uint64_t frameCount_; - - uint32_t numCells_; - uint32_t numHistBins_; + void fillMetadata(IPAContext &context, IPAFrameContext &frameContext, + ControlList &metadata); + double estimateLuminance(double gain) const override; - utils::Duration filteredExposure_; + Span<const uint8_t> expMeans_; }; } /* namespace ipa::rkisp1::algorithms */ diff --git a/src/ipa/rkisp1/algorithms/algorithm.h b/src/ipa/rkisp1/algorithms/algorithm.h index c3212cff..715cfcd8 100644 --- a/src/ipa/rkisp1/algorithms/algorithm.h +++ b/src/ipa/rkisp1/algorithms/algorithm.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2021, Ideas On Board * - * algorithm.h - RkISP1 control algorithm interface + * RkISP1 control algorithm interface */ #pragma once @@ -15,7 +15,17 @@ namespace libcamera { namespace ipa::rkisp1 { -using Algorithm = libcamera::ipa::Algorithm<Module>; +class Algorithm : public libcamera::ipa::Algorithm<Module> +{ +public: + Algorithm() + : disabled_(false), supportsRaw_(false) + { + } + + bool disabled_; + bool supportsRaw_; +}; } /* namespace ipa::rkisp1 */ diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp index 9f00364d..a01fe5d9 100644 --- a/src/ipa/rkisp1/algorithms/awb.cpp +++ b/src/ipa/rkisp1/algorithms/awb.cpp @@ -2,16 +2,18 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * awb.cpp - AWB control algorithm + * AWB control algorithm */ #include "awb.h" #include <algorithm> #include <cmath> +#include <iomanip> #include <libcamera/base/log.h> +#include <libcamera/control_ids.h> #include <libcamera/ipa/core_ipa_interface.h> /** @@ -29,15 +31,27 @@ namespace ipa::rkisp1::algorithms { LOG_DEFINE_CATEGORY(RkISP1Awb) +/* Minimum mean value below which AWB can't operate. */ +constexpr double kMeanMinThreshold = 2.0; + +Awb::Awb() + : rgbMode_(false) +{ +} + /** * \copydoc libcamera::ipa::Algorithm::configure */ int Awb::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) { - context.frameContext.awb.gains.red = 1.0; - context.frameContext.awb.gains.blue = 1.0; - context.frameContext.awb.gains.green = 1.0; + context.activeState.awb.gains.manual.red = 1.0; + context.activeState.awb.gains.manual.blue = 1.0; + context.activeState.awb.gains.manual.green = 1.0; + context.activeState.awb.gains.automatic.red = 1.0; + context.activeState.awb.gains.automatic.blue = 1.0; + context.activeState.awb.gains.automatic.green = 1.0; + context.activeState.awb.autoEnabled = true; /* * Define the measurement window for AWB as a centered rectangle @@ -48,131 +62,264 @@ int Awb::configure(IPAContext &context, context.configuration.awb.measureWindow.h_size = 3 * configInfo.outputSize.width / 4; context.configuration.awb.measureWindow.v_size = 3 * configInfo.outputSize.height / 4; + context.configuration.awb.enabled = true; + return 0; } -uint32_t Awb::estimateCCT(double red, double green, double blue) +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void Awb::queueRequest(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) { - /* Convert the RGB values to CIE tristimulus values (XYZ) */ - double X = (-0.14282) * (red) + (1.54924) * (green) + (-0.95641) * (blue); - double Y = (-0.32466) * (red) + (1.57837) * (green) + (-0.73191) * (blue); - double Z = (-0.68202) * (red) + (0.77073) * (green) + (0.56332) * (blue); + auto &awb = context.activeState.awb; - /* Calculate the normalized chromaticity values */ - double x = X / (X + Y + Z); - double y = Y / (X + Y + Z); + const auto &awbEnable = controls.get(controls::AwbEnable); + if (awbEnable && *awbEnable != awb.autoEnabled) { + awb.autoEnabled = *awbEnable; - /* Calculate CCT */ - double n = (x - 0.3320) / (0.1858 - y); - return 449 * n * n * n + 3525 * n * n + 6823.3 * n + 5520.33; + LOG(RkISP1Awb, Debug) + << (*awbEnable ? "Enabling" : "Disabling") << " AWB"; + } + + const auto &colourGains = controls.get(controls::ColourGains); + if (colourGains && !awb.autoEnabled) { + awb.gains.manual.red = (*colourGains)[0]; + awb.gains.manual.blue = (*colourGains)[1]; + + LOG(RkISP1Awb, Debug) + << "Set colour gains to red: " << awb.gains.manual.red + << ", blue: " << awb.gains.manual.blue; + } + + frameContext.awb.autoEnabled = awb.autoEnabled; + + if (!awb.autoEnabled) { + frameContext.awb.gains.red = awb.gains.manual.red; + frameContext.awb.gains.green = 1.0; + frameContext.awb.gains.blue = awb.gains.manual.blue; + } } /** * \copydoc libcamera::ipa::Algorithm::prepare */ -void Awb::prepare(IPAContext &context, rkisp1_params_cfg *params) +void Awb::prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, rkisp1_params_cfg *params) { - params->others.awb_gain_config.gain_green_b = 256 * context.frameContext.awb.gains.green; - params->others.awb_gain_config.gain_blue = 256 * context.frameContext.awb.gains.blue; - params->others.awb_gain_config.gain_red = 256 * context.frameContext.awb.gains.red; - params->others.awb_gain_config.gain_green_r = 256 * context.frameContext.awb.gains.green; + /* + * This is the latest time we can read the active state. This is the + * most up-to-date automatic values we can read. + */ + if (frameContext.awb.autoEnabled) { + frameContext.awb.gains.red = context.activeState.awb.gains.automatic.red; + frameContext.awb.gains.green = context.activeState.awb.gains.automatic.green; + frameContext.awb.gains.blue = context.activeState.awb.gains.automatic.blue; + } + + params->others.awb_gain_config.gain_green_b = 256 * frameContext.awb.gains.green; + params->others.awb_gain_config.gain_blue = 256 * frameContext.awb.gains.blue; + params->others.awb_gain_config.gain_red = 256 * frameContext.awb.gains.red; + params->others.awb_gain_config.gain_green_r = 256 * frameContext.awb.gains.green; /* Update the gains. */ params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_AWB_GAIN; - /* If we already have configured the gains and window, return. */ - if (context.frameContext.frameCount > 0) + /* If we have already set the AWB measurement parameters, return. */ + if (frame > 0) return; - /* Configure the gains to apply. */ + rkisp1_cif_isp_awb_meas_config &awb_config = params->meas.awb_meas_config; + + /* Configure the measure window for AWB. */ + awb_config.awb_wnd = context.configuration.awb.measureWindow; + + /* Number of frames to use to estimate the means (0 means 1 frame). */ + awb_config.frames = 0; + + /* Select RGB or YCbCr means measurement. */ + if (rgbMode_) { + awb_config.awb_mode = RKISP1_CIF_ISP_AWB_MODE_RGB; + + /* + * For RGB-based measurements, pixels are selected with maximum + * red, green and blue thresholds that are set in the + * awb_ref_cr, awb_min_y and awb_ref_cb respectively. The other + * values are not used, set them to 0. + */ + awb_config.awb_ref_cr = 250; + awb_config.min_y = 250; + awb_config.awb_ref_cb = 250; + + awb_config.max_y = 0; + awb_config.min_c = 0; + awb_config.max_csum = 0; + } else { + awb_config.awb_mode = RKISP1_CIF_ISP_AWB_MODE_YCBCR; + + /* Set the reference Cr and Cb (AWB target) to white. */ + awb_config.awb_ref_cb = 128; + awb_config.awb_ref_cr = 128; + + /* + * Filter out pixels based on luminance and chrominance values. + * The acceptable luma values are specified as a [16, 250] + * range, while the acceptable chroma values are specified with + * a minimum of 16 and a maximum Cb+Cr sum of 250. + */ + awb_config.min_y = 16; + awb_config.max_y = 250; + awb_config.min_c = 16; + awb_config.max_csum = 250; + } + + /* Enable the AWB gains. */ params->module_en_update |= RKISP1_CIF_ISP_MODULE_AWB_GAIN; - /* Update the ISP to apply the gains configured. */ params->module_ens |= RKISP1_CIF_ISP_MODULE_AWB_GAIN; - /* Configure the measure window for AWB. */ - params->meas.awb_meas_config.awb_wnd = context.configuration.awb.measureWindow; - /* - * Measure Y, Cr and Cb means. - * \todo RGB is not working, the kernel seems to not configure it ? - */ - params->meas.awb_meas_config.awb_mode = RKISP1_CIF_ISP_AWB_MODE_YCBCR; - /* Reference Cr and Cb. */ - params->meas.awb_meas_config.awb_ref_cb = 128; - params->meas.awb_meas_config.awb_ref_cr = 128; - /* Y values to include are between min_y and max_y only. */ - params->meas.awb_meas_config.min_y = 16; - params->meas.awb_meas_config.max_y = 250; - /* Maximum Cr+Cb value to take into account for awb. */ - params->meas.awb_meas_config.max_csum = 250; - /* Minimum Cr and Cb values to take into account. */ - params->meas.awb_meas_config.min_c = 16; - /* Number of frames to use to estimate the mean (0 means 1 frame). */ - params->meas.awb_meas_config.frames = 0; - - /* Update AWB measurement unit configuration. */ + /* Update the AWB measurement parameters and enable the AWB module. */ params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_AWB; - /* Make sure the ISP is measuring the means for the next frame. */ params->module_en_update |= RKISP1_CIF_ISP_MODULE_AWB; params->module_ens |= RKISP1_CIF_ISP_MODULE_AWB; } +uint32_t Awb::estimateCCT(double red, double green, double blue) +{ + /* Convert the RGB values to CIE tristimulus values (XYZ) */ + double X = (-0.14282) * (red) + (1.54924) * (green) + (-0.95641) * (blue); + double Y = (-0.32466) * (red) + (1.57837) * (green) + (-0.73191) * (blue); + double Z = (-0.68202) * (red) + (0.77073) * (green) + (0.56332) * (blue); + + /* Calculate the normalized chromaticity values */ + double x = X / (X + Y + Z); + double y = Y / (X + Y + Z); + + /* Calculate CCT */ + double n = (x - 0.3320) / (0.1858 - y); + return 449 * n * n * n + 3525 * n * n + 6823.3 * n + 5520.33; +} + /** * \copydoc libcamera::ipa::Algorithm::process */ -void Awb::process([[maybe_unused]] IPAContext &context, - [[maybe_unused]] IPAFrameContext *frameCtx, - const rkisp1_stat_buffer *stats) +void Awb::process(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const rkisp1_stat_buffer *stats, + ControlList &metadata) { const rkisp1_cif_isp_stat *params = &stats->params; const rkisp1_cif_isp_awb_stat *awb = ¶ms->awb; - IPAFrameContext &frameContext = context.frameContext; - - /* Get the YCbCr mean values */ - double yMean = awb->awb_mean[0].mean_y_or_g; - double crMean = awb->awb_mean[0].mean_cr_or_r; - double cbMean = awb->awb_mean[0].mean_cb_or_b; + IPAActiveState &activeState = context.activeState; + double greenMean; + double redMean; + double blueMean; + + if (rgbMode_) { + greenMean = awb->awb_mean[0].mean_y_or_g; + redMean = awb->awb_mean[0].mean_cr_or_r; + blueMean = awb->awb_mean[0].mean_cb_or_b; + } else { + /* Get the YCbCr mean values */ + double yMean = awb->awb_mean[0].mean_y_or_g; + double cbMean = awb->awb_mean[0].mean_cb_or_b; + double crMean = awb->awb_mean[0].mean_cr_or_r; + + /* + * Convert from YCbCr to RGB. + * The hardware uses the following formulas: + * Y = 16 + 0.2500 R + 0.5000 G + 0.1094 B + * Cb = 128 - 0.1406 R - 0.2969 G + 0.4375 B + * Cr = 128 + 0.4375 R - 0.3750 G - 0.0625 B + * + * The inverse matrix is thus: + * [[1,1636, -0,0623, 1,6008] + * [1,1636, -0,4045, -0,7949] + * [1,1636, 1,9912, -0,0250]] + */ + yMean -= 16; + cbMean -= 128; + crMean -= 128; + redMean = 1.1636 * yMean - 0.0623 * cbMean + 1.6008 * crMean; + greenMean = 1.1636 * yMean - 0.4045 * cbMean - 0.7949 * crMean; + blueMean = 1.1636 * yMean + 1.9912 * cbMean - 0.0250 * crMean; + + /* + * Due to hardware rounding errors in the YCbCr means, the + * calculated RGB means may be negative. This would lead to + * negative gains, messing up calculation. Prevent this by + * clamping the means to positive values. + */ + redMean = std::max(redMean, 0.0); + greenMean = std::max(greenMean, 0.0); + blueMean = std::max(blueMean, 0.0); + } /* - * Convert from YCbCr to RGB. - * The hardware uses the following formulas: - * Y = 16 + 0.2500 R + 0.5000 G + 0.1094 B - * Cb = 128 - 0.1406 R - 0.2969 G + 0.4375 B - * Cr = 128 + 0.4375 R - 0.3750 G - 0.0625 B - * - * The inverse matrix is thus: - * [[1,1636, -0,0623, 1,6008] - * [1,1636, -0,4045, -0,7949] - * [1,1636, 1,9912, -0,0250]] + * The ISP computes the AWB means after applying the colour gains, + * divide by the gains that were used to get the raw means from the + * sensor. */ - yMean -= 16; - cbMean -= 128; - crMean -= 128; - double redMean = 1.1636 * yMean - 0.0623 * cbMean + 1.6008 * crMean; - double greenMean = 1.1636 * yMean - 0.4045 * cbMean - 0.7949 * crMean; - double blueMean = 1.1636 * yMean + 1.9912 * cbMean - 0.0250 * crMean; + redMean /= frameContext.awb.gains.red; + greenMean /= frameContext.awb.gains.green; + blueMean /= frameContext.awb.gains.blue; - /* Estimate the red and blue gains to apply in a grey world. */ - double redGain = greenMean / (redMean + 1); - double blueGain = greenMean / (blueMean + 1); + /* + * If the means are too small we don't have enough information to + * meaningfully calculate gains. Freeze the algorithm in that case. + */ + if (redMean < kMeanMinThreshold && greenMean < kMeanMinThreshold && + blueMean < kMeanMinThreshold) { + frameContext.awb.temperatureK = activeState.awb.temperatureK; + return; + } - /* Filter the values to avoid oscillations. */ - double speed = 0.2; - redGain = speed * redGain + (1 - speed) * frameContext.awb.gains.red; - blueGain = speed * blueGain + (1 - speed) * frameContext.awb.gains.blue; + activeState.awb.temperatureK = estimateCCT(redMean, greenMean, blueMean); /* - * Gain values are unsigned integer value, range 0 to 4 with 8 bit - * fractional part. + * Estimate the red and blue gains to apply in a grey world. The green + * gain is hardcoded to 1.0. Avoid divisions by zero by clamping the + * divisor to a minimum value of 1.0. */ - frameContext.awb.gains.red = std::clamp(redGain, 0.0, 1023.0 / 256); - frameContext.awb.gains.blue = std::clamp(blueGain, 0.0, 1023.0 / 256); - /* Hardcode the green gain to 1.0. */ - frameContext.awb.gains.green = 1.0; + double redGain = greenMean / std::max(redMean, 1.0); + double blueGain = greenMean / std::max(blueMean, 1.0); - frameContext.awb.temperatureK = estimateCCT(redMean, greenMean, blueMean); + /* + * Clamp the gain values to the hardware, which expresses gains as Q2.8 + * unsigned integer values. Set the minimum just above zero to avoid + * divisions by zero when computing the raw means in subsequent + * iterations. + */ + redGain = std::clamp(redGain, 1.0 / 256, 1023.0 / 256); + blueGain = std::clamp(blueGain, 1.0 / 256, 1023.0 / 256); - LOG(RkISP1Awb, Debug) << "Gain found for red: " << context.frameContext.awb.gains.red - << " and for blue: " << context.frameContext.awb.gains.blue; + /* Filter the values to avoid oscillations. */ + double speed = 0.2; + redGain = speed * redGain + (1 - speed) * activeState.awb.gains.automatic.red; + blueGain = speed * blueGain + (1 - speed) * activeState.awb.gains.automatic.blue; + + activeState.awb.gains.automatic.red = redGain; + activeState.awb.gains.automatic.blue = blueGain; + activeState.awb.gains.automatic.green = 1.0; + + frameContext.awb.temperatureK = activeState.awb.temperatureK; + + metadata.set(controls::AwbEnable, frameContext.awb.autoEnabled); + metadata.set(controls::ColourGains, { + static_cast<float>(frameContext.awb.gains.red), + static_cast<float>(frameContext.awb.gains.blue) + }); + metadata.set(controls::ColourTemperature, frameContext.awb.temperatureK); + + LOG(RkISP1Awb, Debug) << std::showpoint + << "Means [" << redMean << ", " << greenMean << ", " << blueMean + << "], gains [" << activeState.awb.gains.automatic.red << ", " + << activeState.awb.gains.automatic.green << ", " + << activeState.awb.gains.automatic.blue << "], temp " + << frameContext.awb.temperatureK << "K"; } REGISTER_IPA_ALGORITHM(Awb, "Awb") diff --git a/src/ipa/rkisp1/algorithms/awb.h b/src/ipa/rkisp1/algorithms/awb.h index 7647842f..06c92896 100644 --- a/src/ipa/rkisp1/algorithms/awb.h +++ b/src/ipa/rkisp1/algorithms/awb.h @@ -2,13 +2,11 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * awb.h - AWB control algorithm + * AWB control algorithm */ #pragma once -#include <linux/rkisp1-config.h> - #include "algorithm.h" namespace libcamera { @@ -18,16 +16,25 @@ namespace ipa::rkisp1::algorithms { class Awb : public Algorithm { public: - Awb() = default; + Awb(); ~Awb() = default; int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; - void prepare(IPAContext &context, rkisp1_params_cfg *params) override; - void process(IPAContext &context, IPAFrameContext *frameCtx, - const rkisp1_stat_buffer *stats) override; + void queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + rkisp1_params_cfg *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const rkisp1_stat_buffer *stats, + ControlList &metadata) override; private: uint32_t estimateCCT(double red, double green, double blue); + + bool rgbMode_; }; } /* namespace ipa::rkisp1::algorithms */ diff --git a/src/ipa/rkisp1/algorithms/blc.cpp b/src/ipa/rkisp1/algorithms/blc.cpp index 3542f61c..d2e74354 100644 --- a/src/ipa/rkisp1/algorithms/blc.cpp +++ b/src/ipa/rkisp1/algorithms/blc.cpp @@ -2,11 +2,15 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * blc.cpp - RkISP1 Black Level Correction control + * RkISP1 Black Level Correction control */ #include "blc.h" +#include <libcamera/base/log.h> + +#include "libcamera/internal/yaml_parser.h" + /** * \file blc.h */ @@ -29,23 +33,54 @@ namespace ipa::rkisp1::algorithms { * isn't currently supported. */ +LOG_DEFINE_CATEGORY(RkISP1Blc) + +BlackLevelCorrection::BlackLevelCorrection() + : tuningParameters_(false) +{ +} + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context, + const YamlObject &tuningData) +{ + blackLevelRed_ = tuningData["R"].get<int16_t>(256); + blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256); + blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256); + blackLevelBlue_ = tuningData["B"].get<int16_t>(256); + + tuningParameters_ = true; + + LOG(RkISP1Blc, Debug) + << "Black levels: red " << blackLevelRed_ + << ", green (red) " << blackLevelGreenR_ + << ", green (blue) " << blackLevelGreenB_ + << ", blue " << blackLevelBlue_; + + return 0; +} + /** * \copydoc libcamera::ipa::Algorithm::prepare */ -void BlackLevelCorrection::prepare(IPAContext &context, +void BlackLevelCorrection::prepare([[maybe_unused]] IPAContext &context, + const uint32_t frame, + [[maybe_unused]] IPAFrameContext &frameContext, rkisp1_params_cfg *params) { - if (context.frameContext.frameCount > 0) + if (frame > 0) + return; + + if (!tuningParameters_) return; - /* - * Substract fixed values taken from imx219 tuning file. - * \todo Use a configuration file for it ? - */ + params->others.bls_config.enable_auto = 0; - params->others.bls_config.fixed_val.r = 256; - params->others.bls_config.fixed_val.gr = 256; - params->others.bls_config.fixed_val.gb = 256; - params->others.bls_config.fixed_val.b = 256; + params->others.bls_config.fixed_val.r = blackLevelRed_; + params->others.bls_config.fixed_val.gr = blackLevelGreenR_; + params->others.bls_config.fixed_val.gb = blackLevelGreenB_; + params->others.bls_config.fixed_val.b = blackLevelBlue_; params->module_en_update |= RKISP1_CIF_ISP_MODULE_BLS; params->module_ens |= RKISP1_CIF_ISP_MODULE_BLS; diff --git a/src/ipa/rkisp1/algorithms/blc.h b/src/ipa/rkisp1/algorithms/blc.h index 69874d8f..460ebcc1 100644 --- a/src/ipa/rkisp1/algorithms/blc.h +++ b/src/ipa/rkisp1/algorithms/blc.h @@ -2,28 +2,34 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * blc.h - RkISP1 Black Level Correction control + * RkISP1 Black Level Correction control */ #pragma once -#include <linux/rkisp1-config.h> - #include "algorithm.h" namespace libcamera { -struct IPACameraSensorInfo; - namespace ipa::rkisp1::algorithms { class BlackLevelCorrection : public Algorithm { public: - BlackLevelCorrection() = default; + BlackLevelCorrection(); ~BlackLevelCorrection() = default; - void prepare(IPAContext &context, rkisp1_params_cfg *params) override; + int init(IPAContext &context, const YamlObject &tuningData) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + rkisp1_params_cfg *params) override; + +private: + bool tuningParameters_; + int16_t blackLevelRed_; + int16_t blackLevelGreenR_; + int16_t blackLevelGreenB_; + int16_t blackLevelBlue_; }; } /* namespace ipa::rkisp1::algorithms */ diff --git a/src/ipa/rkisp1/algorithms/cproc.cpp b/src/ipa/rkisp1/algorithms/cproc.cpp new file mode 100644 index 00000000..68bb8180 --- /dev/null +++ b/src/ipa/rkisp1/algorithms/cproc.cpp @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2021-2022, Ideas On Board + * + * RkISP1 Color Processing control + */ + +#include "cproc.h" + +#include <algorithm> +#include <cmath> + +#include <libcamera/base/log.h> + +#include <libcamera/control_ids.h> + +/** + * \file cproc.h + */ + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +/** + * \class ColorProcessing + * \brief RkISP1 Color Processing control + * + * The ColorProcessing algorithm is responsible for applying brightness, + * contrast and saturation corrections. The values are directly provided + * through requests by the corresponding controls. + */ + +LOG_DEFINE_CATEGORY(RkISP1CProc) + +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void ColorProcessing::queueRequest(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) +{ + auto &cproc = context.activeState.cproc; + bool update = false; + + const auto &brightness = controls.get(controls::Brightness); + if (brightness) { + int value = std::clamp<int>(std::lround(*brightness * 128), -128, 127); + if (cproc.brightness != value) { + cproc.brightness = value; + update = true; + } + + LOG(RkISP1CProc, Debug) << "Set brightness to " << value; + } + + const auto &contrast = controls.get(controls::Contrast); + if (contrast) { + int value = std::clamp<int>(std::lround(*contrast * 128), 0, 255); + if (cproc.contrast != value) { + cproc.contrast = value; + update = true; + } + + LOG(RkISP1CProc, Debug) << "Set contrast to " << value; + } + + const auto saturation = controls.get(controls::Saturation); + if (saturation) { + int value = std::clamp<int>(std::lround(*saturation * 128), 0, 255); + if (cproc.saturation != value) { + cproc.saturation = value; + update = true; + } + + LOG(RkISP1CProc, Debug) << "Set saturation to " << value; + } + + frameContext.cproc.brightness = cproc.brightness; + frameContext.cproc.contrast = cproc.contrast; + frameContext.cproc.saturation = cproc.saturation; + frameContext.cproc.update = update; +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void ColorProcessing::prepare([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + rkisp1_params_cfg *params) +{ + /* Check if the algorithm configuration has been updated. */ + if (!frameContext.cproc.update) + return; + + params->others.cproc_config.brightness = frameContext.cproc.brightness; + params->others.cproc_config.contrast = frameContext.cproc.contrast; + params->others.cproc_config.sat = frameContext.cproc.saturation; + + params->module_en_update |= RKISP1_CIF_ISP_MODULE_CPROC; + params->module_ens |= RKISP1_CIF_ISP_MODULE_CPROC; + params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_CPROC; +} + +REGISTER_IPA_ALGORITHM(ColorProcessing, "ColorProcessing") + +} /* namespace ipa::rkisp1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/cproc.h b/src/ipa/rkisp1/algorithms/cproc.h new file mode 100644 index 00000000..bafba5cc --- /dev/null +++ b/src/ipa/rkisp1/algorithms/cproc.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2021-2022, Ideas On Board + * + * RkISP1 Color Processing control + */ + +#pragma once + +#include <sys/types.h> + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +class ColorProcessing : public Algorithm +{ +public: + ColorProcessing() = default; + ~ColorProcessing() = default; + + void queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + rkisp1_params_cfg *params) override; +}; + +} /* namespace ipa::rkisp1::algorithms */ +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/dpcc.cpp b/src/ipa/rkisp1/algorithms/dpcc.cpp new file mode 100644 index 00000000..b5a339e9 --- /dev/null +++ b/src/ipa/rkisp1/algorithms/dpcc.cpp @@ -0,0 +1,251 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2021-2022, Ideas On Board + * + * RkISP1 Defect Pixel Cluster Correction control + */ + +#include "dpcc.h" + +#include <libcamera/base/log.h> + +#include "libcamera/internal/yaml_parser.h" + +#include "linux/rkisp1-config.h" + +/** + * \file dpcc.h + */ + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +/** + * \class DefectPixelClusterCorrection + * \brief RkISP1 Defect Pixel Cluster Correction control + * + * Depending of the sensor quality, some pixels can be defective and then + * appear significantly brighter or darker than the other pixels. + * + * The Defect Pixel Cluster Correction algorithms is responsible to minimize + * the impact of the pixels. This can be done with algorithms applied at run + * time (on-the-fly method) or with a table of defective pixels. Only the first + * method is supported for the moment. + */ + +LOG_DEFINE_CATEGORY(RkISP1Dpcc) + +DefectPixelClusterCorrection::DefectPixelClusterCorrection() + : config_({}) +{ +} + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context, + const YamlObject &tuningData) +{ + config_.mode = RKISP1_CIF_ISP_DPCC_MODE_STAGE1_ENABLE; + config_.output_mode = RKISP1_CIF_ISP_DPCC_OUTPUT_MODE_STAGE1_INCL_G_CENTER + | RKISP1_CIF_ISP_DPCC_OUTPUT_MODE_STAGE1_INCL_RB_CENTER; + + config_.set_use = tuningData["fixed-set"].get<bool>(false) + ? RKISP1_CIF_ISP_DPCC_SET_USE_STAGE1_USE_FIX_SET : 0; + + /* Get all defined sets to apply (up to 3). */ + const YamlObject &setsObject = tuningData["sets"]; + if (!setsObject.isList()) { + LOG(RkISP1Dpcc, Error) + << "'sets' parameter not found in tuning file"; + return -EINVAL; + } + + if (setsObject.size() > RKISP1_CIF_ISP_DPCC_METHODS_MAX) { + LOG(RkISP1Dpcc, Error) + << "'sets' size in tuning file (" << setsObject.size() + << ") exceeds the maximum hardware capacity (3)"; + return -EINVAL; + } + + for (std::size_t i = 0; i < setsObject.size(); ++i) { + struct rkisp1_cif_isp_dpcc_methods_config &method = config_.methods[i]; + const YamlObject &set = setsObject[i]; + uint16_t value; + + /* Enable set if described in YAML tuning file. */ + config_.set_use |= 1 << i; + + /* PG Method */ + const YamlObject &pgObject = set["pg-factor"]; + + if (pgObject.contains("green")) { + method.method |= + RKISP1_CIF_ISP_DPCC_METHODS_SET_PG_GREEN_ENABLE; + + value = pgObject["green"].get<uint16_t>(0); + method.pg_fac |= RKISP1_CIF_ISP_DPCC_PG_FAC_G(value); + } + + if (pgObject.contains("red-blue")) { + method.method |= + RKISP1_CIF_ISP_DPCC_METHODS_SET_PG_RED_BLUE_ENABLE; + + value = pgObject["red-blue"].get<uint16_t>(0); + method.pg_fac |= RKISP1_CIF_ISP_DPCC_PG_FAC_RB(value); + } + + /* RO Method */ + const YamlObject &roObject = set["ro-limits"]; + + if (roObject.contains("green")) { + method.method |= + RKISP1_CIF_ISP_DPCC_METHODS_SET_RO_GREEN_ENABLE; + + value = roObject["green"].get<uint16_t>(0); + config_.ro_limits |= + RKISP1_CIF_ISP_DPCC_RO_LIMITS_n_G(i, value); + } + + if (roObject.contains("red-blue")) { + method.method |= + RKISP1_CIF_ISP_DPCC_METHODS_SET_RO_RED_BLUE_ENABLE; + + value = roObject["red-blue"].get<uint16_t>(0); + config_.ro_limits |= + RKISP1_CIF_ISP_DPCC_RO_LIMITS_n_RB(i, value); + } + + /* RG Method */ + const YamlObject &rgObject = set["rg-factor"]; + method.rg_fac = 0; + + if (rgObject.contains("green")) { + method.method |= + RKISP1_CIF_ISP_DPCC_METHODS_SET_RG_GREEN_ENABLE; + + value = rgObject["green"].get<uint16_t>(0); + method.rg_fac |= RKISP1_CIF_ISP_DPCC_RG_FAC_G(value); + } + + if (rgObject.contains("red-blue")) { + method.method |= + RKISP1_CIF_ISP_DPCC_METHODS_SET_RG_RED_BLUE_ENABLE; + + value = rgObject["red-blue"].get<uint16_t>(0); + method.rg_fac |= RKISP1_CIF_ISP_DPCC_RG_FAC_RB(value); + } + + /* RND Method */ + const YamlObject &rndOffsetsObject = set["rnd-offsets"]; + + if (rndOffsetsObject.contains("green")) { + method.method |= + RKISP1_CIF_ISP_DPCC_METHODS_SET_RND_GREEN_ENABLE; + + value = rndOffsetsObject["green"].get<uint16_t>(0); + config_.rnd_offs |= + RKISP1_CIF_ISP_DPCC_RND_OFFS_n_G(i, value); + } + + if (rndOffsetsObject.contains("red-blue")) { + method.method |= + RKISP1_CIF_ISP_DPCC_METHODS_SET_RND_RED_BLUE_ENABLE; + + value = rndOffsetsObject["red-blue"].get<uint16_t>(0); + config_.rnd_offs |= + RKISP1_CIF_ISP_DPCC_RND_OFFS_n_RB(i, value); + } + + const YamlObject &rndThresholdObject = set["rnd-threshold"]; + method.rnd_thresh = 0; + + if (rndThresholdObject.contains("green")) { + method.method |= + RKISP1_CIF_ISP_DPCC_METHODS_SET_RND_GREEN_ENABLE; + + value = rndThresholdObject["green"].get<uint16_t>(0); + method.rnd_thresh |= + RKISP1_CIF_ISP_DPCC_RND_THRESH_G(value); + } + + if (rndThresholdObject.contains("red-blue")) { + method.method |= + RKISP1_CIF_ISP_DPCC_METHODS_SET_RND_RED_BLUE_ENABLE; + + value = rndThresholdObject["red-blue"].get<uint16_t>(0); + method.rnd_thresh |= + RKISP1_CIF_ISP_DPCC_RND_THRESH_RB(value); + } + + /* LC Method */ + const YamlObject &lcThresholdObject = set["line-threshold"]; + method.line_thresh = 0; + + if (lcThresholdObject.contains("green")) { + method.method |= + RKISP1_CIF_ISP_DPCC_METHODS_SET_LC_GREEN_ENABLE; + + value = lcThresholdObject["green"].get<uint16_t>(0); + method.line_thresh |= + RKISP1_CIF_ISP_DPCC_LINE_THRESH_G(value); + } + + if (lcThresholdObject.contains("red-blue")) { + method.method |= + RKISP1_CIF_ISP_DPCC_METHODS_SET_LC_RED_BLUE_ENABLE; + + value = lcThresholdObject["red-blue"].get<uint16_t>(0); + method.line_thresh |= + RKISP1_CIF_ISP_DPCC_LINE_THRESH_RB(value); + } + + const YamlObject &lcTMadFactorObject = set["line-mad-factor"]; + method.line_mad_fac = 0; + + if (lcTMadFactorObject.contains("green")) { + method.method |= + RKISP1_CIF_ISP_DPCC_METHODS_SET_LC_GREEN_ENABLE; + + value = lcTMadFactorObject["green"].get<uint16_t>(0); + method.line_mad_fac |= + RKISP1_CIF_ISP_DPCC_LINE_MAD_FAC_G(value); + } + + if (lcTMadFactorObject.contains("red-blue")) { + method.method |= + RKISP1_CIF_ISP_DPCC_METHODS_SET_LC_RED_BLUE_ENABLE; + + value = lcTMadFactorObject["red-blue"].get<uint16_t>(0); + method.line_mad_fac |= + RKISP1_CIF_ISP_DPCC_LINE_MAD_FAC_RB(value); + } + } + + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void DefectPixelClusterCorrection::prepare([[maybe_unused]] IPAContext &context, + const uint32_t frame, + [[maybe_unused]] IPAFrameContext &frameContext, + rkisp1_params_cfg *params) +{ + if (frame > 0) + return; + + params->others.dpcc_config = config_; + + params->module_en_update |= RKISP1_CIF_ISP_MODULE_DPCC; + params->module_ens |= RKISP1_CIF_ISP_MODULE_DPCC; + params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_DPCC; +} + +REGISTER_IPA_ALGORITHM(DefectPixelClusterCorrection, "DefectPixelClusterCorrection") + +} /* namespace ipa::rkisp1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/dpcc.h b/src/ipa/rkisp1/algorithms/dpcc.h new file mode 100644 index 00000000..d39b7bed --- /dev/null +++ b/src/ipa/rkisp1/algorithms/dpcc.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2021-2022, Ideas On Board + * + * RkISP1 Defect Pixel Cluster Correction control + */ + +#pragma once + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +class DefectPixelClusterCorrection : public Algorithm +{ +public: + DefectPixelClusterCorrection(); + ~DefectPixelClusterCorrection() = default; + + int init(IPAContext &context, const YamlObject &tuningData) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + rkisp1_params_cfg *params) override; + +private: + rkisp1_cif_isp_dpcc_config config_; +}; + +} /* namespace ipa::rkisp1::algorithms */ +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/dpf.cpp b/src/ipa/rkisp1/algorithms/dpf.cpp new file mode 100644 index 00000000..abf95728 --- /dev/null +++ b/src/ipa/rkisp1/algorithms/dpf.cpp @@ -0,0 +1,260 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2021-2022, Ideas On Board + * + * RkISP1 Denoise Pre-Filter control + */ + +#include "dpf.h" + +#include <cmath> + +#include <libcamera/base/log.h> + +#include <libcamera/control_ids.h> + +#include "linux/rkisp1-config.h" + +/** + * \file dpf.h + */ + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +/** + * \class Dpf + * \brief RkISP1 Denoise Pre-Filter control + * + * The denoise pre-filter algorithm is a bilateral filter which combines a + * range filter and a domain filter. The denoise pre-filter is applied before + * demosaicing. + */ + +LOG_DEFINE_CATEGORY(RkISP1Dpf) + +Dpf::Dpf() + : config_({}), strengthConfig_({}) +{ +} + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Dpf::init([[maybe_unused]] IPAContext &context, + const YamlObject &tuningData) +{ + std::vector<uint8_t> values; + + /* + * The domain kernel is configured with a 9x9 kernel for the green + * pixels, and a 13x9 or 9x9 kernel for red and blue pixels. + */ + const YamlObject &dFObject = tuningData["DomainFilter"]; + + /* + * For the green component, we have the 9x9 kernel specified + * as 6 coefficients: + * Y + * ^ + * 4 | 6 5 4 5 6 + * 3 | 5 3 3 5 + * 2 | 5 3 2 3 5 + * 1 | 3 1 1 3 + * 0 - 4 2 0 2 4 + * -1 | 3 1 1 3 + * -2 | 5 3 2 3 5 + * -3 | 5 3 3 5 + * -4 | 6 5 4 5 6 + * +---------|--------> X + * -4....-1 0 1 2 3 4 + */ + values = dFObject["g"].getList<uint8_t>().value_or(std::vector<uint8_t>{}); + if (values.size() != RKISP1_CIF_ISP_DPF_MAX_SPATIAL_COEFFS) { + LOG(RkISP1Dpf, Error) + << "Invalid 'DomainFilter:g': expected " + << RKISP1_CIF_ISP_DPF_MAX_SPATIAL_COEFFS + << " elements, got " << values.size(); + return -EINVAL; + } + + std::copy_n(values.begin(), values.size(), + std::begin(config_.g_flt.spatial_coeff)); + + config_.g_flt.gr_enable = true; + config_.g_flt.gb_enable = true; + + /* + * For the red and blue components, we have the 13x9 kernel specified + * as 6 coefficients: + * + * Y + * ^ + * 4 | 6 5 4 3 4 5 6 + * | + * 2 | 5 4 2 1 2 4 5 + * | + * 0 - 5 3 1 0 1 3 5 + * | + * -2 | 5 4 2 1 2 4 5 + * | + * -4 | 6 5 4 3 4 5 6 + * +-------------|------------> X + * -6 -4 -2 0 2 4 6 + * + * For a 9x9 kernel, columns -6 and 6 are dropped, so coefficient + * number 6 is not used. + */ + values = dFObject["rb"].getList<uint8_t>().value_or(std::vector<uint8_t>{}); + if (values.size() != RKISP1_CIF_ISP_DPF_MAX_SPATIAL_COEFFS && + values.size() != RKISP1_CIF_ISP_DPF_MAX_SPATIAL_COEFFS - 1) { + LOG(RkISP1Dpf, Error) + << "Invalid 'DomainFilter:rb': expected " + << RKISP1_CIF_ISP_DPF_MAX_SPATIAL_COEFFS - 1 + << " or " << RKISP1_CIF_ISP_DPF_MAX_SPATIAL_COEFFS + << " elements, got " << values.size(); + return -EINVAL; + } + + config_.rb_flt.fltsize = values.size() == RKISP1_CIF_ISP_DPF_MAX_SPATIAL_COEFFS + ? RKISP1_CIF_ISP_DPF_RB_FILTERSIZE_13x9 + : RKISP1_CIF_ISP_DPF_RB_FILTERSIZE_9x9; + + std::copy_n(values.begin(), values.size(), + std::begin(config_.rb_flt.spatial_coeff)); + + config_.rb_flt.r_enable = true; + config_.rb_flt.b_enable = true; + + /* + * The range kernel is configured with a noise level lookup table (NLL) + * which stores a piecewise linear function that characterizes the + * sensor noise profile as a noise level function curve (NLF). + */ + const YamlObject &rFObject = tuningData["NoiseLevelFunction"]; + + std::vector<uint16_t> nllValues; + nllValues = rFObject["coeff"].getList<uint16_t>().value_or(std::vector<uint16_t>{}); + if (nllValues.size() != RKISP1_CIF_ISP_DPF_MAX_NLF_COEFFS) { + LOG(RkISP1Dpf, Error) + << "Invalid 'RangeFilter:coeff': expected " + << RKISP1_CIF_ISP_DPF_MAX_NLF_COEFFS + << " elements, got " << nllValues.size(); + return -EINVAL; + } + + std::copy_n(nllValues.begin(), nllValues.size(), + std::begin(config_.nll.coeff)); + + std::string scaleMode = rFObject["scale-mode"].get<std::string>(""); + if (scaleMode == "linear") { + config_.nll.scale_mode = RKISP1_CIF_ISP_NLL_SCALE_LINEAR; + } else if (scaleMode == "logarithmic") { + config_.nll.scale_mode = RKISP1_CIF_ISP_NLL_SCALE_LOGARITHMIC; + } else { + LOG(RkISP1Dpf, Error) + << "Invalid 'RangeFilter:scale-mode': expected " + << "'linear' or 'logarithmic' value, got " + << scaleMode; + return -EINVAL; + } + + const YamlObject &fSObject = tuningData["FilterStrength"]; + + strengthConfig_.r = fSObject["r"].get<uint16_t>(64); + strengthConfig_.g = fSObject["g"].get<uint16_t>(64); + strengthConfig_.b = fSObject["b"].get<uint16_t>(64); + + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void Dpf::queueRequest(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) +{ + auto &dpf = context.activeState.dpf; + bool update = false; + + const auto &denoise = controls.get(controls::draft::NoiseReductionMode); + if (denoise) { + LOG(RkISP1Dpf, Debug) << "Set denoise to " << *denoise; + + switch (*denoise) { + case controls::draft::NoiseReductionModeOff: + if (dpf.denoise) { + dpf.denoise = false; + update = true; + } + break; + case controls::draft::NoiseReductionModeMinimal: + case controls::draft::NoiseReductionModeHighQuality: + case controls::draft::NoiseReductionModeFast: + if (!dpf.denoise) { + dpf.denoise = true; + update = true; + } + break; + default: + LOG(RkISP1Dpf, Error) + << "Unsupported denoise value " + << *denoise; + break; + } + } + + frameContext.dpf.denoise = dpf.denoise; + frameContext.dpf.update = update; +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void Dpf::prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, rkisp1_params_cfg *params) +{ + if (frame == 0) { + params->others.dpf_config = config_; + params->others.dpf_strength_config = strengthConfig_; + + const auto &awb = context.configuration.awb; + const auto &lsc = context.configuration.lsc; + auto &mode = params->others.dpf_config.gain.mode; + + /* + * The DPF needs to take into account the total amount of + * digital gain, which comes from the AWB and LSC modules. The + * DPF hardware can be programmed with a digital gain value + * manually, but can also use the gains supplied by the AWB and + * LSC modules automatically when they are enabled. Use that + * mode of operation as it simplifies control of the DPF. + */ + if (awb.enabled && lsc.enabled) + mode = RKISP1_CIF_ISP_DPF_GAIN_USAGE_AWB_LSC_GAINS; + else if (awb.enabled) + mode = RKISP1_CIF_ISP_DPF_GAIN_USAGE_AWB_GAINS; + else if (lsc.enabled) + mode = RKISP1_CIF_ISP_DPF_GAIN_USAGE_LSC_GAINS; + else + mode = RKISP1_CIF_ISP_DPF_GAIN_USAGE_DISABLED; + + params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_DPF | + RKISP1_CIF_ISP_MODULE_DPF_STRENGTH; + } + + if (frameContext.dpf.update) { + params->module_en_update |= RKISP1_CIF_ISP_MODULE_DPF; + if (frameContext.dpf.denoise) + params->module_ens |= RKISP1_CIF_ISP_MODULE_DPF; + } +} + +REGISTER_IPA_ALGORITHM(Dpf, "Dpf") + +} /* namespace ipa::rkisp1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/dpf.h b/src/ipa/rkisp1/algorithms/dpf.h new file mode 100644 index 00000000..da0115ba --- /dev/null +++ b/src/ipa/rkisp1/algorithms/dpf.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2021-2022, Ideas On Board + * + * RkISP1 Denoise Pre-Filter control + */ + +#pragma once + +#include <sys/types.h> + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +class Dpf : public Algorithm +{ +public: + Dpf(); + ~Dpf() = default; + + int init(IPAContext &context, const YamlObject &tuningData) override; + void queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + rkisp1_params_cfg *params) override; + +private: + struct rkisp1_cif_isp_dpf_config config_; + struct rkisp1_cif_isp_dpf_strength_config strengthConfig_; +}; + +} /* namespace ipa::rkisp1::algorithms */ +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/filter.cpp b/src/ipa/rkisp1/algorithms/filter.cpp new file mode 100644 index 00000000..9752248a --- /dev/null +++ b/src/ipa/rkisp1/algorithms/filter.cpp @@ -0,0 +1,216 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2021-2022, Ideas On Board + * + * RkISP1 Filter control + */ + +#include "filter.h" + +#include <cmath> + +#include <libcamera/base/log.h> + +#include <libcamera/control_ids.h> + +/** + * \file filter.h + */ + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +/** + * \class Filter + * \brief RkISP1 Filter control + * + * Denoise and Sharpness filters will be applied by RkISP1 during the + * demosaicing step. The denoise filter is responsible for removing noise from + * the image, while the sharpness filter will enhance its acutance. + * + * \todo In current version the denoise and sharpness control is based on user + * controls. In a future version it should be controlled automatically by the + * algorithm. + */ + +LOG_DEFINE_CATEGORY(RkISP1Filter) + +static constexpr uint32_t kFiltLumWeightDefault = 0x00022040; +static constexpr uint32_t kFiltModeDefault = 0x000004f2; + +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void Filter::queueRequest(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) +{ + auto &filter = context.activeState.filter; + bool update = false; + + const auto &sharpness = controls.get(controls::Sharpness); + if (sharpness) { + unsigned int value = std::round(std::clamp(*sharpness, 0.0f, 10.0f)); + + if (filter.sharpness != value) { + filter.sharpness = value; + update = true; + } + + LOG(RkISP1Filter, Debug) << "Set sharpness to " << *sharpness; + } + + const auto &denoise = controls.get(controls::draft::NoiseReductionMode); + if (denoise) { + LOG(RkISP1Filter, Debug) << "Set denoise to " << *denoise; + + switch (*denoise) { + case controls::draft::NoiseReductionModeOff: + if (filter.denoise != 0) { + filter.denoise = 0; + update = true; + } + break; + case controls::draft::NoiseReductionModeMinimal: + if (filter.denoise != 1) { + filter.denoise = 1; + update = true; + } + break; + case controls::draft::NoiseReductionModeHighQuality: + case controls::draft::NoiseReductionModeFast: + if (filter.denoise != 3) { + filter.denoise = 3; + update = true; + } + break; + default: + LOG(RkISP1Filter, Error) + << "Unsupported denoise value " + << *denoise; + break; + } + } + + frameContext.filter.denoise = filter.denoise; + frameContext.filter.sharpness = filter.sharpness; + frameContext.filter.update = update; +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void Filter::prepare([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, rkisp1_params_cfg *params) +{ + /* Check if the algorithm configuration has been updated. */ + if (!frameContext.filter.update) + return; + + static constexpr uint16_t filt_fac_sh0[] = { + 0x04, 0x07, 0x0a, 0x0c, 0x10, 0x14, 0x1a, 0x1e, 0x24, 0x2a, 0x30 + }; + + static constexpr uint16_t filt_fac_sh1[] = { + 0x04, 0x08, 0x0c, 0x10, 0x16, 0x1b, 0x20, 0x26, 0x2c, 0x30, 0x3f + }; + + static constexpr uint16_t filt_fac_mid[] = { + 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x10, 0x13, 0x17, 0x1d, 0x22, 0x28 + }; + + static constexpr uint16_t filt_fac_bl0[] = { + 0x02, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x10, 0x15, 0x1a, 0x24 + }; + + static constexpr uint16_t filt_fac_bl1[] = { + 0x00, 0x00, 0x00, 0x02, 0x04, 0x04, 0x06, 0x08, 0x0d, 0x14, 0x20 + }; + + static constexpr uint16_t filt_thresh_sh0[] = { + 0, 18, 26, 36, 41, 75, 90, 120, 170, 250, 1023 + }; + + static constexpr uint16_t filt_thresh_sh1[] = { + 0, 33, 44, 51, 67, 100, 120, 150, 200, 300, 1023 + }; + + static constexpr uint16_t filt_thresh_bl0[] = { + 0, 8, 13, 23, 26, 50, 60, 80, 140, 180, 1023 + }; + + static constexpr uint16_t filt_thresh_bl1[] = { + 0, 2, 5, 10, 15, 20, 26, 51, 100, 150, 1023 + }; + + static constexpr uint16_t stage1_select[] = { + 6, 6, 4, 4, 3, 3, 2, 2, 2, 1, 0 + }; + + static constexpr uint16_t filt_chr_v_mode[] = { + 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 + }; + + static constexpr uint16_t filt_chr_h_mode[] = { + 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 + }; + + uint8_t denoise = frameContext.filter.denoise; + uint8_t sharpness = frameContext.filter.sharpness; + auto &flt_config = params->others.flt_config; + + flt_config.fac_sh0 = filt_fac_sh0[sharpness]; + flt_config.fac_sh1 = filt_fac_sh1[sharpness]; + flt_config.fac_mid = filt_fac_mid[sharpness]; + flt_config.fac_bl0 = filt_fac_bl0[sharpness]; + flt_config.fac_bl1 = filt_fac_bl1[sharpness]; + + flt_config.lum_weight = kFiltLumWeightDefault; + flt_config.mode = kFiltModeDefault; + flt_config.thresh_sh0 = filt_thresh_sh0[denoise]; + flt_config.thresh_sh1 = filt_thresh_sh1[denoise]; + flt_config.thresh_bl0 = filt_thresh_bl0[denoise]; + flt_config.thresh_bl1 = filt_thresh_bl1[denoise]; + flt_config.grn_stage1 = stage1_select[denoise]; + flt_config.chr_v_mode = filt_chr_v_mode[denoise]; + flt_config.chr_h_mode = filt_chr_h_mode[denoise]; + + /* + * Combined high denoising and high sharpening requires some + * adjustments to the configuration of the filters. A first stage + * filter with a lower strength must be selected, and the blur factors + * must be decreased. + */ + if (denoise == 9) { + if (sharpness > 3) + flt_config.grn_stage1 = 2; + } else if (denoise == 10) { + if (sharpness > 5) + flt_config.grn_stage1 = 2; + else if (sharpness > 3) + flt_config.grn_stage1 = 1; + } + + if (denoise > 7) { + if (sharpness > 7) { + flt_config.fac_bl0 /= 2; + flt_config.fac_bl1 /= 4; + } else if (sharpness > 4) { + flt_config.fac_bl0 = flt_config.fac_bl0 * 3 / 4; + flt_config.fac_bl1 /= 2; + } + } + + params->module_en_update |= RKISP1_CIF_ISP_MODULE_FLT; + params->module_ens |= RKISP1_CIF_ISP_MODULE_FLT; + params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_FLT; +} + +REGISTER_IPA_ALGORITHM(Filter, "Filter") + +} /* namespace ipa::rkisp1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/filter.h b/src/ipa/rkisp1/algorithms/filter.h new file mode 100644 index 00000000..d595811d --- /dev/null +++ b/src/ipa/rkisp1/algorithms/filter.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2021-2022, Ideas On Board + * + * RkISP1 Filter control + */ + +#pragma once + +#include <sys/types.h> + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +class Filter : public Algorithm +{ +public: + Filter() = default; + ~Filter() = default; + + void queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + rkisp1_params_cfg *params) override; +}; + +} /* namespace ipa::rkisp1::algorithms */ +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/gsl.cpp b/src/ipa/rkisp1/algorithms/gsl.cpp new file mode 100644 index 00000000..9b056c6e --- /dev/null +++ b/src/ipa/rkisp1/algorithms/gsl.cpp @@ -0,0 +1,146 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2021-2022, Ideas On Board + * + * RkISP1 Gamma Sensor Linearization control + */ + +#include "gsl.h" + +#include <libcamera/base/log.h> +#include <libcamera/base/utils.h> + +#include "libcamera/internal/yaml_parser.h" + +#include "linux/rkisp1-config.h" + +/** + * \file gsl.h + */ + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +/** + * \class GammaSensorLinearization + * \brief RkISP1 Gamma Sensor Linearization control + * + * This algorithm linearizes the sensor output to compensate the sensor + * non-linearities by applying piecewise linear functions to the red, green and + * blue channels. + * + * The curves are specified in the tuning data and defined using 17 points. + * + * - The X coordinates are expressed using 16 intervals, with the first point + * at X coordinate 0. Each interval is expressed as a 2-bit value DX (from + * GAMMA_DX_1 to GAMMA_DX_16), stored in the RKISP1_CIF_ISP_GAMMA_DX_LO and + * RKISP1_CIF_ISP_GAMMA_DX_HI registers. The real interval is equal to + * \f$2^{dx+4}\f$. X coordinates are shared between the red, green and blue + * curves. + * + * - The Y coordinates are specified as 17 values separately for the + * red, green and blue channels, with a 12-bit resolution. Each value must be + * in the [-2048, 2047] range compared to the previous value. + */ + +LOG_DEFINE_CATEGORY(RkISP1Gsl) + +static constexpr unsigned int kDegammaXIntervals = 16; + +GammaSensorLinearization::GammaSensorLinearization() +{ +} + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int GammaSensorLinearization::init([[maybe_unused]] IPAContext &context, + const YamlObject &tuningData) +{ + std::vector<uint16_t> xIntervals = + tuningData["x-intervals"].getList<uint16_t>().value_or(std::vector<uint16_t>{}); + if (xIntervals.size() != kDegammaXIntervals) { + LOG(RkISP1Gsl, Error) + << "Invalid 'x' coordinates: expected " + << kDegammaXIntervals << " elements, got " + << xIntervals.size(); + + return -EINVAL; + } + + /* Compute gammaDx_ intervals from xIntervals values */ + gammaDx_[0] = 0; + gammaDx_[1] = 0; + for (unsigned int i = 0; i < kDegammaXIntervals; ++i) + gammaDx_[i / 8] |= (xIntervals[i] & 0x07) << ((i % 8) * 4); + + const YamlObject &yObject = tuningData["y"]; + if (!yObject.isDictionary()) { + LOG(RkISP1Gsl, Error) + << "Issue while parsing 'y' in tuning file: " + << "entry must be a dictionary"; + return -EINVAL; + } + + curveYr_ = yObject["red"].getList<uint16_t>().value_or(std::vector<uint16_t>{}); + if (curveYr_.size() != RKISP1_CIF_ISP_DEGAMMA_CURVE_SIZE) { + LOG(RkISP1Gsl, Error) + << "Invalid 'y:red' coordinates: expected " + << RKISP1_CIF_ISP_DEGAMMA_CURVE_SIZE + << " elements, got " << curveYr_.size(); + return -EINVAL; + } + + curveYg_ = yObject["green"].getList<uint16_t>().value_or(std::vector<uint16_t>{}); + if (curveYg_.size() != RKISP1_CIF_ISP_DEGAMMA_CURVE_SIZE) { + LOG(RkISP1Gsl, Error) + << "Invalid 'y:green' coordinates: expected " + << RKISP1_CIF_ISP_DEGAMMA_CURVE_SIZE + << " elements, got " << curveYg_.size(); + return -EINVAL; + } + + curveYb_ = yObject["blue"].getList<uint16_t>().value_or(std::vector<uint16_t>{}); + if (curveYb_.size() != RKISP1_CIF_ISP_DEGAMMA_CURVE_SIZE) { + LOG(RkISP1Gsl, Error) + << "Invalid 'y:blue' coordinates: expected " + << RKISP1_CIF_ISP_DEGAMMA_CURVE_SIZE + << " elements, got " << curveYb_.size(); + return -EINVAL; + } + + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void GammaSensorLinearization::prepare([[maybe_unused]] IPAContext &context, + const uint32_t frame, + [[maybe_unused]] IPAFrameContext &frameContext, + rkisp1_params_cfg *params) +{ + if (frame > 0) + return; + + params->others.sdg_config.xa_pnts.gamma_dx0 = gammaDx_[0]; + params->others.sdg_config.xa_pnts.gamma_dx1 = gammaDx_[1]; + + std::copy(curveYr_.begin(), curveYr_.end(), + params->others.sdg_config.curve_r.gamma_y); + std::copy(curveYg_.begin(), curveYg_.end(), + params->others.sdg_config.curve_g.gamma_y); + std::copy(curveYb_.begin(), curveYb_.end(), + params->others.sdg_config.curve_b.gamma_y); + + params->module_en_update |= RKISP1_CIF_ISP_MODULE_SDG; + params->module_ens |= RKISP1_CIF_ISP_MODULE_SDG; + params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_SDG; +} + +REGISTER_IPA_ALGORITHM(GammaSensorLinearization, "GammaSensorLinearization") + +} /* namespace ipa::rkisp1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/gsl.h b/src/ipa/rkisp1/algorithms/gsl.h new file mode 100644 index 00000000..c404105e --- /dev/null +++ b/src/ipa/rkisp1/algorithms/gsl.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2021-2022, Ideas On Board + * + * RkISP1 Gamma Sensor Linearization control + */ + +#pragma once + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +class GammaSensorLinearization : public Algorithm +{ +public: + GammaSensorLinearization(); + ~GammaSensorLinearization() = default; + + int init(IPAContext &context, const YamlObject &tuningData) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + rkisp1_params_cfg *params) override; + +private: + uint32_t gammaDx_[2]; + std::vector<uint16_t> curveYr_; + std::vector<uint16_t> curveYg_; + std::vector<uint16_t> curveYb_; +}; + +} /* namespace ipa::rkisp1::algorithms */ +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp new file mode 100644 index 00000000..161183fc --- /dev/null +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -0,0 +1,342 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2021-2022, Ideas On Board + * + * RkISP1 Lens Shading Correction control + */ + +#include "lsc.h" + +#include <algorithm> +#include <cmath> +#include <numeric> + +#include <libcamera/base/log.h> +#include <libcamera/base/utils.h> + +#include "libcamera/internal/yaml_parser.h" + +#include "linux/rkisp1-config.h" + +/** + * \file lsc.h + */ + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +/** + * \class LensShadingCorrection + * \brief RkISP1 Lens Shading Correction control + * + * Due to the optical characteristics of the lens, the light intensity received + * by the sensor is not uniform. + * + * The Lens Shading Correction algorithm applies multipliers to all pixels + * to compensate for the lens shading effect. The coefficients are + * specified in a downscaled table in the YAML tuning file. + */ + +LOG_DEFINE_CATEGORY(RkISP1Lsc) + +static std::vector<double> parseSizes(const YamlObject &tuningData, + const char *prop) +{ + std::vector<double> sizes = + tuningData[prop].getList<double>().value_or(std::vector<double>{}); + if (sizes.size() != RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE) { + LOG(RkISP1Lsc, Error) + << "Invalid '" << prop << "' values: expected " + << RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE + << " elements, got " << sizes.size(); + return {}; + } + + /* + * The sum of all elements must be 0.5 to satisfy hardware constraints. + * Validate it here, allowing a 1% tolerance as rounding errors may + * prevent an exact match (further adjustments will be performed in + * LensShadingCorrection::prepare()). + */ + double sum = std::accumulate(sizes.begin(), sizes.end(), 0.0); + if (sum < 0.495 || sum > 0.505) { + LOG(RkISP1Lsc, Error) + << "Invalid '" << prop << "' values: sum of the elements" + << " should be 0.5, got " << sum; + return {}; + } + + return sizes; +} + +static std::vector<uint16_t> parseTable(const YamlObject &tuningData, + const char *prop) +{ + static constexpr unsigned int kLscNumSamples = + RKISP1_CIF_ISP_LSC_SAMPLES_MAX * RKISP1_CIF_ISP_LSC_SAMPLES_MAX; + + std::vector<uint16_t> table = + tuningData[prop].getList<uint16_t>().value_or(std::vector<uint16_t>{}); + if (table.size() != kLscNumSamples) { + LOG(RkISP1Lsc, Error) + << "Invalid '" << prop << "' values: expected " + << kLscNumSamples + << " elements, got " << table.size(); + return {}; + } + + return table; +} + +LensShadingCorrection::LensShadingCorrection() + : lastCt_({ 0, 0 }) +{ +} + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, + const YamlObject &tuningData) +{ + xSize_ = parseSizes(tuningData, "x-size"); + ySize_ = parseSizes(tuningData, "y-size"); + + if (xSize_.empty() || ySize_.empty()) + return -EINVAL; + + /* Get all defined sets to apply. */ + const YamlObject &yamlSets = tuningData["sets"]; + if (!yamlSets.isList()) { + LOG(RkISP1Lsc, Error) + << "'sets' parameter not found in tuning file"; + return -EINVAL; + } + + const auto &sets = yamlSets.asList(); + for (const auto &yamlSet : sets) { + uint32_t ct = yamlSet["ct"].get<uint32_t>(0); + + if (sets_.count(ct)) { + LOG(RkISP1Lsc, Error) + << "Multiple sets found for color temperature " + << ct; + return -EINVAL; + } + + Components &set = sets_[ct]; + + set.ct = ct; + set.r = parseTable(yamlSet, "r"); + set.gr = parseTable(yamlSet, "gr"); + set.gb = parseTable(yamlSet, "gb"); + set.b = parseTable(yamlSet, "b"); + + if (set.r.empty() || set.gr.empty() || + set.gb.empty() || set.b.empty()) { + LOG(RkISP1Lsc, Error) + << "Set for color temperature " << ct + << " is missing tables"; + return -EINVAL; + } + } + + if (sets_.empty()) { + LOG(RkISP1Lsc, Error) << "Failed to load any sets"; + return -EINVAL; + } + + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ +int LensShadingCorrection::configure(IPAContext &context, + [[maybe_unused]] const IPACameraSensorInfo &configInfo) +{ + const Size &size = context.configuration.sensor.size; + Size totalSize{}; + + for (unsigned int i = 0; i < RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE; ++i) { + xSizes_[i] = xSize_[i] * size.width; + ySizes_[i] = ySize_[i] * size.height; + + /* + * To prevent unexpected behavior of the ISP, the sum of x_size_tbl and + * y_size_tbl items shall be equal to respectively size.width/2 and + * size.height/2. Enforce it by computing the last tables value to avoid + * rounding-induced errors. + */ + if (i == RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE - 1) { + xSizes_[i] = size.width / 2 - totalSize.width; + ySizes_[i] = size.height / 2 - totalSize.height; + } + + totalSize.width += xSizes_[i]; + totalSize.height += ySizes_[i]; + + xGrad_[i] = std::round(32768 / xSizes_[i]); + yGrad_[i] = std::round(32768 / ySizes_[i]); + } + + context.configuration.lsc.enabled = true; + return 0; +} + +void LensShadingCorrection::setParameters(rkisp1_params_cfg *params) +{ + struct rkisp1_cif_isp_lsc_config &config = params->others.lsc_config; + + memcpy(config.x_grad_tbl, xGrad_, sizeof(config.x_grad_tbl)); + memcpy(config.y_grad_tbl, yGrad_, sizeof(config.y_grad_tbl)); + memcpy(config.x_size_tbl, xSizes_, sizeof(config.x_size_tbl)); + memcpy(config.y_size_tbl, ySizes_, sizeof(config.y_size_tbl)); + + params->module_en_update |= RKISP1_CIF_ISP_MODULE_LSC; + params->module_ens |= RKISP1_CIF_ISP_MODULE_LSC; + params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_LSC; +} + +void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config, + const Components &set) +{ + std::copy(set.r.begin(), set.r.end(), &config.r_data_tbl[0][0]); + std::copy(set.gr.begin(), set.gr.end(), &config.gr_data_tbl[0][0]); + std::copy(set.gb.begin(), set.gb.end(), &config.gb_data_tbl[0][0]); + std::copy(set.b.begin(), set.b.end(), &config.b_data_tbl[0][0]); +} + +/* + * Interpolate LSC parameters based on color temperature value. + */ +void LensShadingCorrection::interpolateTable(rkisp1_cif_isp_lsc_config &config, + const Components &set0, + const Components &set1, + const uint32_t ct) +{ + double coeff0 = (set1.ct - ct) / static_cast<double>(set1.ct - set0.ct); + double coeff1 = (ct - set0.ct) / static_cast<double>(set1.ct - set0.ct); + + for (unsigned int i = 0; i < RKISP1_CIF_ISP_LSC_SAMPLES_MAX; ++i) { + for (unsigned int j = 0; j < RKISP1_CIF_ISP_LSC_SAMPLES_MAX; ++j) { + unsigned int sample = i * RKISP1_CIF_ISP_LSC_SAMPLES_MAX + j; + + config.r_data_tbl[i][j] = + set0.r[sample] * coeff0 + + set1.r[sample] * coeff1; + + config.gr_data_tbl[i][j] = + set0.gr[sample] * coeff0 + + set1.gr[sample] * coeff1; + + config.gb_data_tbl[i][j] = + set0.gb[sample] * coeff0 + + set1.gb[sample] * coeff1; + + config.b_data_tbl[i][j] = + set0.b[sample] * coeff0 + + set1.b[sample] * coeff1; + } + } +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void LensShadingCorrection::prepare(IPAContext &context, + const uint32_t frame, + [[maybe_unused]] IPAFrameContext &frameContext, + rkisp1_params_cfg *params) +{ + struct rkisp1_cif_isp_lsc_config &config = params->others.lsc_config; + + /* + * If there is only one set, the configuration has already been done + * for first frame. + */ + if (sets_.size() == 1 && frame > 0) + return; + + /* + * If there is only one set, pick it. We can ignore lastCt_, as it will + * never be relevant. + */ + if (sets_.size() == 1) { + setParameters(params); + copyTable(config, sets_.cbegin()->second); + return; + } + + uint32_t ct = context.activeState.awb.temperatureK; + ct = std::clamp(ct, sets_.cbegin()->first, sets_.crbegin()->first); + + /* + * If the original is the same, then it means the same adjustment would + * be made. If the adjusted is the same, then it means that it's the + * same as what was actually applied. Thus in these cases we can skip + * reprogramming the LSC. + * + * original == adjusted can only happen if an interpolation + * happened, or if original has an exact entry in sets_. This means + * that if original != adjusted, then original was adjusted to + * the nearest available entry in sets_, resulting in adjusted. + * Clearly, any ct value that is in between original and adjusted + * will be adjusted to the same adjusted value, so we can skip + * reprogramming the LSC table. + * + * We also skip updating the original value, as the last one had a + * larger bound and thus a larger range of ct values that will be + * adjusted to the same adjusted. + */ + if ((lastCt_.original <= ct && ct <= lastCt_.adjusted) || + (lastCt_.adjusted <= ct && ct <= lastCt_.original)) + return; + + setParameters(params); + + /* + * The color temperature matches exactly one of the available LSC tables. + */ + if (sets_.count(ct)) { + copyTable(config, sets_[ct]); + lastCt_ = { ct, ct }; + return; + } + + /* No shortcuts left; we need to round or interpolate */ + auto iter = sets_.upper_bound(ct); + const Components &set1 = iter->second; + const Components &set0 = (--iter)->second; + uint32_t ct0 = set0.ct; + uint32_t ct1 = set1.ct; + uint32_t diff0 = ct - ct0; + uint32_t diff1 = ct1 - ct; + static constexpr double kThreshold = 0.1; + float threshold = kThreshold * (ct1 - ct0); + + if (diff0 < threshold || diff1 < threshold) { + const Components &set = diff0 < diff1 ? set0 : set1; + LOG(RkISP1Lsc, Debug) << "using LSC table for " << set.ct; + copyTable(config, set); + lastCt_ = { ct, set.ct }; + return; + } + + /* + * ct is not within 10% of the difference between the neighbouring + * color temperatures, so we need to interpolate. + */ + LOG(RkISP1Lsc, Debug) + << "ct is " << ct << ", interpolating between " + << ct0 << " and " << ct1; + interpolateTable(config, set0, set1, ct); + lastCt_ = { ct, ct }; +} + +REGISTER_IPA_ALGORITHM(LensShadingCorrection, "LensShadingCorrection") + +} /* namespace ipa::rkisp1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h new file mode 100644 index 00000000..5baf5927 --- /dev/null +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2021-2022, Ideas On Board + * + * RkISP1 Lens Shading Correction control + */ + +#pragma once + +#include <map> + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::rkisp1::algorithms { + +class LensShadingCorrection : public Algorithm +{ +public: + LensShadingCorrection(); + ~LensShadingCorrection() = default; + + int init(IPAContext &context, const YamlObject &tuningData) override; + int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + rkisp1_params_cfg *params) override; + +private: + struct Components { + uint32_t ct; + std::vector<uint16_t> r; + std::vector<uint16_t> gr; + std::vector<uint16_t> gb; + std::vector<uint16_t> b; + }; + + void setParameters(rkisp1_params_cfg *params); + void copyTable(rkisp1_cif_isp_lsc_config &config, const Components &set0); + void interpolateTable(rkisp1_cif_isp_lsc_config &config, + const Components &set0, const Components &set1, + const uint32_t ct); + + std::map<uint32_t, Components> sets_; + std::vector<double> xSize_; + std::vector<double> ySize_; + uint16_t xGrad_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; + uint16_t yGrad_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; + uint16_t xSizes_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; + uint16_t ySizes_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; + struct { + uint32_t original; + uint32_t adjusted; + } lastCt_; +}; + +} /* namespace ipa::rkisp1::algorithms */ +} /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/meson.build b/src/ipa/rkisp1/algorithms/meson.build index 7ec53d89..93a48329 100644 --- a/src/ipa/rkisp1/algorithms/meson.build +++ b/src/ipa/rkisp1/algorithms/meson.build @@ -4,4 +4,10 @@ rkisp1_ipa_algorithms = files([ 'agc.cpp', 'awb.cpp', 'blc.cpp', + 'cproc.cpp', + 'dpcc.cpp', + 'dpf.cpp', + 'filter.cpp', + 'gsl.cpp', + 'lsc.cpp', ]) |