Message ID | 20250808141315.413839-9-stefan.klug@ideasonboard.com |
---|---|
State | New |
Headers | show |
Series |
|
Related | show |
Hi Stefan - thanks for the patch. This helper always hurts my brain. On 08/08/2025 15:12, Stefan Klug wrote: > Calculate the error introduced by quantization as "quantization gain" > and return it separately from splitExposure(). It is not included in the > digital gain, to not silently ignore the limits imposed by the AGC > configuration. > > Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com> > --- > src/ipa/ipu3/algorithms/agc.cpp | 4 +-- > src/ipa/libipa/agc_mean_luminance.cpp | 7 ++-- > src/ipa/libipa/agc_mean_luminance.h | 2 +- > src/ipa/libipa/exposure_mode_helper.cpp | 46 +++++++++++++++++-------- > src/ipa/libipa/exposure_mode_helper.h | 2 +- > src/ipa/rkisp1/algorithms/agc.cpp | 9 ++--- > 6 files changed, 44 insertions(+), 26 deletions(-) > > diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp > index 39d0aebb0838..da045640d569 100644 > --- a/src/ipa/ipu3/algorithms/agc.cpp > +++ b/src/ipa/ipu3/algorithms/agc.cpp > @@ -222,8 +222,8 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, > utils::Duration effectiveExposureValue = exposureTime * analogueGain; > > utils::Duration newExposureTime; > - double aGain, dGain; > - std::tie(newExposureTime, aGain, dGain) = > + double aGain, qGain, dGain; > + std::tie(newExposureTime, aGain, qGain, dGain) = > calculateNewEv(context.activeState.agc.constraintMode, > context.activeState.agc.exposureMode, hist, > effectiveExposureValue); > diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp > index 58c4dfc9add2..695453e7ceea 100644 > --- a/src/ipa/libipa/agc_mean_luminance.cpp > +++ b/src/ipa/libipa/agc_mean_luminance.cpp > @@ -566,11 +566,12 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue) > * > * Calculate a new exposure value to try to obtain the target. The calculated > * exposure value is filtered to prevent rapid changes from frame to frame, and > - * divided into exposure time, analogue and digital gain. > + * divided into exposure time, analogue, quantization and digital gain. > * > - * \return Tuple of exposure time, analogue gain, and digital gain > + * \return Tuple of exposure time, analogue gain, quantization gain and digital > + * gain > */ > -std::tuple<utils::Duration, double, double> > +std::tuple<utils::Duration, double, double, double> > AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex, > uint32_t exposureModeIndex, > const Histogram &yHist, > diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h > index a7c7e006af42..741ccf986625 100644 > --- a/src/ipa/libipa/agc_mean_luminance.h > +++ b/src/ipa/libipa/agc_mean_luminance.h > @@ -68,7 +68,7 @@ public: > return controls_; > } > > - std::tuple<utils::Duration, double, double> > + std::tuple<utils::Duration, double, double, double> > calculateNewEv(uint32_t constraintModeIndex, uint32_t exposureModeIndex, > const Histogram &yHist, utils::Duration effectiveExposureValue); > > diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp > index 7d470ebe487c..79b8e6758faf 100644 > --- a/src/ipa/libipa/exposure_mode_helper.cpp > +++ b/src/ipa/libipa/exposure_mode_helper.cpp > @@ -178,14 +178,23 @@ double ExposureModeHelper::clampGain(double gain, double *quantizationGain) cons > * required exposure, the helper falls-back to simply maximising the exposure > * time first, followed by analogue gain, followed by digital gain. > * > - * \return Tuple of exposure time, analogue gain, and digital gain > + * During the calculations the gain missed due to quantization is recorded and > + * returned as quantization gain. The quantization gain is not included in the > + * digital gain. So to exactly apply the given exposure, both quantization gain > + * and digital gain must be applied. > + * > + * \return Tuple of exposure time, analogue gain, quantization gain and digital > + * gain > */ > -std::tuple<utils::Duration, double, double> > +std::tuple<utils::Duration, double, double, double> > ExposureModeHelper::splitExposure(utils::Duration exposure) const > { > ASSERT(maxExposureTime_); > ASSERT(maxGain_); > > + utils::Duration exposureTime; > + double gain; > + double quantGain; > bool gainFixed = minGain_ == maxGain_; > bool exposureTimeFixed = minExposureTime_ == maxExposureTime_; > > @@ -193,16 +202,20 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const > * There's no point entering the loop if we cannot change either gain > * nor exposure time anyway. > */ > - if (exposureTimeFixed && gainFixed) > - return { minExposureTime_, minGain_, exposure / (minExposureTime_ * minGain_) }; > + if (exposureTimeFixed && gainFixed) { > + exposureTime = clampExposureTime(minExposureTime_, &quantGain); > + gain = clampGain(minGain_ * quantGain, &quantGain); So this rolls the gain lost to quantization of the exposure time into analogue gain - why go for that automatically, but not also roll the gain lost to quantization of the analogue gain into digital gain automatically? > + > + return { exposureTime, gain, quantGain, > + exposure / (minExposureTime_ * minGain_ * quantGain) }; I am surprised to see quantGain here...for example with the following values: exposure = 10000us minGain = 2.0 minExposureTime_ = 4000us lineLength_ = 30us Say 16 steps of analogue gain from 1x to 16x Then following clampExposureTime exposureTime = 3990us, quantGain = 1.00250626566 following clampGain() gain = 2.0 and quantGain = 1.00250626566 So I would expect digital gain to be calculated as 1.25 and for the algorithm to have to apply both dGain and qGain as digital gain such that 3990 * 2.0 * 1.00250626566 * 1.25 = 10000...is that not the intention? > + } > > - utils::Duration exposureTime; > double stageGain = clampGain(1.0); > double lastStageGain = stageGain; > - double gain; > > for (unsigned int stage = 0; stage < gains_.size(); stage++) { > - utils::Duration stageExposureTime = clampExposureTime(exposureTimes_[stage]); > + utils::Duration stageExposureTime = clampExposureTime(exposureTimes_[stage], > + &quantGain); > stageGain = clampGain(gains_[stage]); > > /* > @@ -215,18 +228,20 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const > > /* Clamp the gain to lastStageGain and regulate exposureTime. */ > if (stageExposureTime * lastStageGain >= exposure) { > - exposureTime = clampExposureTime(exposure / lastStageGain); > - gain = clampGain(exposure / exposureTime); > + exposureTime = clampExposureTime(exposure / lastStageGain, &quantGain); > + gain = clampGain((exposure / exposureTime) * quantGain, &quantGain); > > - return { exposureTime, gain, exposure / (exposureTime * gain) }; > + return { exposureTime, gain, quantGain, > + exposure / (exposureTime * gain * quantGain) }; > } > > /* Clamp the exposureTime to stageExposureTime and regulate gain. */ > if (stageExposureTime * stageGain >= exposure) { > exposureTime = stageExposureTime; > - gain = clampGain(exposure / exposureTime); > + gain = clampGain((exposure / exposureTime) * quantGain, &quantGain); > > - return { exposureTime, gain, exposure / (exposureTime * gain) }; > + return { exposureTime, gain, quantGain, > + exposure / (exposureTime * gain * quantGain) }; > } > > lastStageGain = stageGain; > @@ -239,10 +254,11 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const > * stages to use then the default stageGain of 1.0 is used so that > * exposure time is maxed before gain is touched at all. > */ > - exposureTime = clampExposureTime(exposure / stageGain); > - gain = clampGain(exposure / exposureTime); > + exposureTime = clampExposureTime(exposure / stageGain, &quantGain); > + gain = clampGain((exposure / exposureTime) * quantGain, &quantGain); > > - return { exposureTime, gain, exposure / (exposureTime * gain) }; > + return { exposureTime, gain, quantGain, > + exposure / (exposureTime * gain * quantGain) }; > } > > /** > diff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h > index 8701fae9a26e..cc811e9fde18 100644 > --- a/src/ipa/libipa/exposure_mode_helper.h > +++ b/src/ipa/libipa/exposure_mode_helper.h > @@ -30,7 +30,7 @@ public: > void setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime, > double minGain, double maxGain); > > - std::tuple<utils::Duration, double, double> > + std::tuple<utils::Duration, double, double, double> > splitExposure(utils::Duration exposure) const; > > utils::Duration minExposureTime() const { return minExposureTime_; } > diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp > index 35440b67e999..0a29326841fb 100644 > --- a/src/ipa/rkisp1/algorithms/agc.cpp > +++ b/src/ipa/rkisp1/algorithms/agc.cpp > @@ -567,15 +567,16 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, > setExposureCompensation(pow(2.0, frameContext.agc.exposureValue)); > > utils::Duration newExposureTime; > - double aGain, dGain; > - std::tie(newExposureTime, aGain, dGain) = > + double aGain, qGain, dGain; > + std::tie(newExposureTime, aGain, qGain, dGain) = I think you'll also have to fix this for the other users of AgcMeanLuminance; IPU3 and C55 at least. Thanks Dan > calculateNewEv(frameContext.agc.constraintMode, > frameContext.agc.exposureMode, > hist, effectiveExposureValue); > > LOG(RkISP1Agc, Debug) > - << "Divided up exposure time, analogue gain and digital gain are " > - << newExposureTime << ", " << aGain << " and " << dGain; > + << "Divided up exposure time, analogue gain, quantization gain" > + << " and digital gain are " << newExposureTime << ", " << aGain > + << ", " << qGain << " and " << dGain; > > IPAActiveState &activeState = context.activeState; > /* Update the estimated exposure and gain. */
Hi Dan, Thank you for the review. Quoting Dan Scally (2025-08-12 13:46:26) > Hi Stefan - thanks for the patch. This helper always hurts my brain. > > On 08/08/2025 15:12, Stefan Klug wrote: > > Calculate the error introduced by quantization as "quantization gain" > > and return it separately from splitExposure(). It is not included in the > > digital gain, to not silently ignore the limits imposed by the AGC > > configuration. > > > > Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com> > > --- > > src/ipa/ipu3/algorithms/agc.cpp | 4 +-- > > src/ipa/libipa/agc_mean_luminance.cpp | 7 ++-- > > src/ipa/libipa/agc_mean_luminance.h | 2 +- > > src/ipa/libipa/exposure_mode_helper.cpp | 46 +++++++++++++++++-------- > > src/ipa/libipa/exposure_mode_helper.h | 2 +- > > src/ipa/rkisp1/algorithms/agc.cpp | 9 ++--- > > 6 files changed, 44 insertions(+), 26 deletions(-) > > > > diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp > > index 39d0aebb0838..da045640d569 100644 > > --- a/src/ipa/ipu3/algorithms/agc.cpp > > +++ b/src/ipa/ipu3/algorithms/agc.cpp > > @@ -222,8 +222,8 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, > > utils::Duration effectiveExposureValue = exposureTime * analogueGain; > > > > utils::Duration newExposureTime; > > - double aGain, dGain; > > - std::tie(newExposureTime, aGain, dGain) = > > + double aGain, qGain, dGain; > > + std::tie(newExposureTime, aGain, qGain, dGain) = > > calculateNewEv(context.activeState.agc.constraintMode, > > context.activeState.agc.exposureMode, hist, > > effectiveExposureValue); > > diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp > > index 58c4dfc9add2..695453e7ceea 100644 > > --- a/src/ipa/libipa/agc_mean_luminance.cpp > > +++ b/src/ipa/libipa/agc_mean_luminance.cpp > > @@ -566,11 +566,12 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue) > > * > > * Calculate a new exposure value to try to obtain the target. The calculated > > * exposure value is filtered to prevent rapid changes from frame to frame, and > > - * divided into exposure time, analogue and digital gain. > > + * divided into exposure time, analogue, quantization and digital gain. > > * > > - * \return Tuple of exposure time, analogue gain, and digital gain > > + * \return Tuple of exposure time, analogue gain, quantization gain and digital > > + * gain > > */ > > -std::tuple<utils::Duration, double, double> > > +std::tuple<utils::Duration, double, double, double> > > AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex, > > uint32_t exposureModeIndex, > > const Histogram &yHist, > > diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h > > index a7c7e006af42..741ccf986625 100644 > > --- a/src/ipa/libipa/agc_mean_luminance.h > > +++ b/src/ipa/libipa/agc_mean_luminance.h > > @@ -68,7 +68,7 @@ public: > > return controls_; > > } > > > > - std::tuple<utils::Duration, double, double> > > + std::tuple<utils::Duration, double, double, double> > > calculateNewEv(uint32_t constraintModeIndex, uint32_t exposureModeIndex, > > const Histogram &yHist, utils::Duration effectiveExposureValue); > > > > diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp > > index 7d470ebe487c..79b8e6758faf 100644 > > --- a/src/ipa/libipa/exposure_mode_helper.cpp > > +++ b/src/ipa/libipa/exposure_mode_helper.cpp > > @@ -178,14 +178,23 @@ double ExposureModeHelper::clampGain(double gain, double *quantizationGain) cons > > * required exposure, the helper falls-back to simply maximising the exposure > > * time first, followed by analogue gain, followed by digital gain. > > * > > - * \return Tuple of exposure time, analogue gain, and digital gain > > + * During the calculations the gain missed due to quantization is recorded and > > + * returned as quantization gain. The quantization gain is not included in the > > + * digital gain. So to exactly apply the given exposure, both quantization gain > > + * and digital gain must be applied. > > + * > > + * \return Tuple of exposure time, analogue gain, quantization gain and digital > > + * gain > > */ > > -std::tuple<utils::Duration, double, double> > > +std::tuple<utils::Duration, double, double, double> > > ExposureModeHelper::splitExposure(utils::Duration exposure) const > > { > > ASSERT(maxExposureTime_); > > ASSERT(maxGain_); > > > > + utils::Duration exposureTime; > > + double gain; > > + double quantGain; > > bool gainFixed = minGain_ == maxGain_; > > bool exposureTimeFixed = minExposureTime_ == maxExposureTime_; > > > > @@ -193,16 +202,20 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const > > * There's no point entering the loop if we cannot change either gain > > * nor exposure time anyway. > > */ > > - if (exposureTimeFixed && gainFixed) > > - return { minExposureTime_, minGain_, exposure / (minExposureTime_ * minGain_) }; > > + if (exposureTimeFixed && gainFixed) { > > + exposureTime = clampExposureTime(minExposureTime_, &quantGain); > > + gain = clampGain(minGain_ * quantGain, &quantGain); > > So this rolls the gain lost to quantization of the exposure time into > analogue gain - why go for that automatically, but not also roll the > gain lost to quantization of the analogue gain into digital gain > automatically? In this case exposureTime and gain are fixed, so clamp will always return the same result. So (I hope I didn't miss something) passing the gain through clamp is the same as saying: double quantGain2; exposureTime = clampExposureTime(minExposureTime_, &quantGain2); gain = clampGain(minGain_ * quantGain, &quantGain); quantGain *= quantGain2; > > > + > > + return { exposureTime, gain, quantGain, > > + exposure / (minExposureTime_ * minGain_ * quantGain) }; > > I am surprised to see quantGain here...for example with the following > values: > > > exposure = 10000us > minGain = 2.0 > minExposureTime_ = 4000us > lineLength_ = 30us > Say 16 steps of analogue gain from 1x to 16x > > Then > > following clampExposureTime exposureTime = 3990us, quantGain = 1.00250626566 > following clampGain() gain = 2.0 and quantGain = 1.00250626566 > > So I would expect digital gain to be calculated as 1.25 and for the > algorithm to have to apply both dGain and qGain as digital gain such > that 3990 * 2.0 * 1.00250626566 * 1.25 = 10000...is that not the intention? Ohh, thanks, that is a good point. My conclusion is a bit different though. I think the return should look like this: return { exposureTime, gain, quantGain, exposure / (exposureTime * gain * quantGain) }; So that the digital gain is based on the values that are actually applied. Then the overall result is as you'd expect. > > > > + } > > > > - utils::Duration exposureTime; > > double stageGain = clampGain(1.0); > > double lastStageGain = stageGain; > > - double gain; > > > > for (unsigned int stage = 0; stage < gains_.size(); stage++) { > > - utils::Duration stageExposureTime = clampExposureTime(exposureTimes_[stage]); > > + utils::Duration stageExposureTime = clampExposureTime(exposureTimes_[stage], > > + &quantGain); > > stageGain = clampGain(gains_[stage]); > > > > /* > > @@ -215,18 +228,20 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const > > > > /* Clamp the gain to lastStageGain and regulate exposureTime. */ > > if (stageExposureTime * lastStageGain >= exposure) { > > - exposureTime = clampExposureTime(exposure / lastStageGain); > > - gain = clampGain(exposure / exposureTime); > > + exposureTime = clampExposureTime(exposure / lastStageGain, &quantGain); > > + gain = clampGain((exposure / exposureTime) * quantGain, &quantGain); > > > > - return { exposureTime, gain, exposure / (exposureTime * gain) }; > > + return { exposureTime, gain, quantGain, > > + exposure / (exposureTime * gain * quantGain) }; > > } > > > > /* Clamp the exposureTime to stageExposureTime and regulate gain. */ > > if (stageExposureTime * stageGain >= exposure) { > > exposureTime = stageExposureTime; > > - gain = clampGain(exposure / exposureTime); > > + gain = clampGain((exposure / exposureTime) * quantGain, &quantGain); > > > > - return { exposureTime, gain, exposure / (exposureTime * gain) }; > > + return { exposureTime, gain, quantGain, > > + exposure / (exposureTime * gain * quantGain) }; > > } > > > > lastStageGain = stageGain; > > @@ -239,10 +254,11 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const > > * stages to use then the default stageGain of 1.0 is used so that > > * exposure time is maxed before gain is touched at all. > > */ > > - exposureTime = clampExposureTime(exposure / stageGain); > > - gain = clampGain(exposure / exposureTime); > > + exposureTime = clampExposureTime(exposure / stageGain, &quantGain); > > + gain = clampGain((exposure / exposureTime) * quantGain, &quantGain); > > > > - return { exposureTime, gain, exposure / (exposureTime * gain) }; > > + return { exposureTime, gain, quantGain, > > + exposure / (exposureTime * gain * quantGain) }; > > } > > > > /** > > diff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h > > index 8701fae9a26e..cc811e9fde18 100644 > > --- a/src/ipa/libipa/exposure_mode_helper.h > > +++ b/src/ipa/libipa/exposure_mode_helper.h > > @@ -30,7 +30,7 @@ public: > > void setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime, > > double minGain, double maxGain); > > > > - std::tuple<utils::Duration, double, double> > > + std::tuple<utils::Duration, double, double, double> > > splitExposure(utils::Duration exposure) const; > > > > utils::Duration minExposureTime() const { return minExposureTime_; } > > diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp > > index 35440b67e999..0a29326841fb 100644 > > --- a/src/ipa/rkisp1/algorithms/agc.cpp > > +++ b/src/ipa/rkisp1/algorithms/agc.cpp > > @@ -567,15 +567,16 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, > > setExposureCompensation(pow(2.0, frameContext.agc.exposureValue)); > > > > utils::Duration newExposureTime; > > - double aGain, dGain; > > - std::tie(newExposureTime, aGain, dGain) = > > + double aGain, qGain, dGain; > > + std::tie(newExposureTime, aGain, qGain, dGain) = > > I think you'll also have to fix this for the other users of > AgcMeanLuminance; IPU3 and C55 at least. Yes, CI told me that also :-) > > Thanks > Dan Thanks, Stefan > > > calculateNewEv(frameContext.agc.constraintMode, > > frameContext.agc.exposureMode, > > hist, effectiveExposureValue); > > > > LOG(RkISP1Agc, Debug) > > - << "Divided up exposure time, analogue gain and digital gain are " > > - << newExposureTime << ", " << aGain << " and " << dGain; > > + << "Divided up exposure time, analogue gain, quantization gain" > > + << " and digital gain are " << newExposureTime << ", " << aGain > > + << ", " << qGain << " and " << dGain; > > > > IPAActiveState &activeState = context.activeState; > > /* Update the estimated exposure and gain. */ >
Hi Stefan On 13/08/2025 10:13, Stefan Klug wrote: > Hi Dan, > > Thank you for the review. > > Quoting Dan Scally (2025-08-12 13:46:26) >> Hi Stefan - thanks for the patch. This helper always hurts my brain. >> >> On 08/08/2025 15:12, Stefan Klug wrote: >>> Calculate the error introduced by quantization as "quantization gain" >>> and return it separately from splitExposure(). It is not included in the >>> digital gain, to not silently ignore the limits imposed by the AGC >>> configuration. >>> >>> Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com> >>> --- >>> src/ipa/ipu3/algorithms/agc.cpp | 4 +-- >>> src/ipa/libipa/agc_mean_luminance.cpp | 7 ++-- >>> src/ipa/libipa/agc_mean_luminance.h | 2 +- >>> src/ipa/libipa/exposure_mode_helper.cpp | 46 +++++++++++++++++-------- >>> src/ipa/libipa/exposure_mode_helper.h | 2 +- >>> src/ipa/rkisp1/algorithms/agc.cpp | 9 ++--- >>> 6 files changed, 44 insertions(+), 26 deletions(-) >>> >>> diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp >>> index 39d0aebb0838..da045640d569 100644 >>> --- a/src/ipa/ipu3/algorithms/agc.cpp >>> +++ b/src/ipa/ipu3/algorithms/agc.cpp >>> @@ -222,8 +222,8 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, >>> utils::Duration effectiveExposureValue = exposureTime * analogueGain; >>> >>> utils::Duration newExposureTime; >>> - double aGain, dGain; >>> - std::tie(newExposureTime, aGain, dGain) = >>> + double aGain, qGain, dGain; >>> + std::tie(newExposureTime, aGain, qGain, dGain) = >>> calculateNewEv(context.activeState.agc.constraintMode, >>> context.activeState.agc.exposureMode, hist, >>> effectiveExposureValue); >>> diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp >>> index 58c4dfc9add2..695453e7ceea 100644 >>> --- a/src/ipa/libipa/agc_mean_luminance.cpp >>> +++ b/src/ipa/libipa/agc_mean_luminance.cpp >>> @@ -566,11 +566,12 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue) >>> * >>> * Calculate a new exposure value to try to obtain the target. The calculated >>> * exposure value is filtered to prevent rapid changes from frame to frame, and >>> - * divided into exposure time, analogue and digital gain. >>> + * divided into exposure time, analogue, quantization and digital gain. >>> * >>> - * \return Tuple of exposure time, analogue gain, and digital gain >>> + * \return Tuple of exposure time, analogue gain, quantization gain and digital >>> + * gain >>> */ >>> -std::tuple<utils::Duration, double, double> >>> +std::tuple<utils::Duration, double, double, double> >>> AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex, >>> uint32_t exposureModeIndex, >>> const Histogram &yHist, >>> diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h >>> index a7c7e006af42..741ccf986625 100644 >>> --- a/src/ipa/libipa/agc_mean_luminance.h >>> +++ b/src/ipa/libipa/agc_mean_luminance.h >>> @@ -68,7 +68,7 @@ public: >>> return controls_; >>> } >>> >>> - std::tuple<utils::Duration, double, double> >>> + std::tuple<utils::Duration, double, double, double> >>> calculateNewEv(uint32_t constraintModeIndex, uint32_t exposureModeIndex, >>> const Histogram &yHist, utils::Duration effectiveExposureValue); >>> >>> diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp >>> index 7d470ebe487c..79b8e6758faf 100644 >>> --- a/src/ipa/libipa/exposure_mode_helper.cpp >>> +++ b/src/ipa/libipa/exposure_mode_helper.cpp >>> @@ -178,14 +178,23 @@ double ExposureModeHelper::clampGain(double gain, double *quantizationGain) cons >>> * required exposure, the helper falls-back to simply maximising the exposure >>> * time first, followed by analogue gain, followed by digital gain. >>> * >>> - * \return Tuple of exposure time, analogue gain, and digital gain >>> + * During the calculations the gain missed due to quantization is recorded and >>> + * returned as quantization gain. The quantization gain is not included in the >>> + * digital gain. So to exactly apply the given exposure, both quantization gain >>> + * and digital gain must be applied. >>> + * >>> + * \return Tuple of exposure time, analogue gain, quantization gain and digital >>> + * gain >>> */ >>> -std::tuple<utils::Duration, double, double> >>> +std::tuple<utils::Duration, double, double, double> >>> ExposureModeHelper::splitExposure(utils::Duration exposure) const >>> { >>> ASSERT(maxExposureTime_); >>> ASSERT(maxGain_); >>> >>> + utils::Duration exposureTime; >>> + double gain; >>> + double quantGain; >>> bool gainFixed = minGain_ == maxGain_; >>> bool exposureTimeFixed = minExposureTime_ == maxExposureTime_; >>> >>> @@ -193,16 +202,20 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const >>> * There's no point entering the loop if we cannot change either gain >>> * nor exposure time anyway. >>> */ >>> - if (exposureTimeFixed && gainFixed) >>> - return { minExposureTime_, minGain_, exposure / (minExposureTime_ * minGain_) }; >>> + if (exposureTimeFixed && gainFixed) { >>> + exposureTime = clampExposureTime(minExposureTime_, &quantGain); >>> + gain = clampGain(minGain_ * quantGain, &quantGain); >> >> So this rolls the gain lost to quantization of the exposure time into >> analogue gain - why go for that automatically, but not also roll the >> gain lost to quantization of the analogue gain into digital gain >> automatically? > > In this case exposureTime and gain are fixed, so clamp will always > return the same result. So (I hope I didn't miss something) passing the > gain through clamp is the same as saying: > > double quantGain2; > exposureTime = clampExposureTime(minExposureTime_, &quantGain2); > gain = clampGain(minGain_ * quantGain, &quantGain); > quantGain *= quantGain2; Yes I think so, but the same pattern is in the calculations within the loop too where the exposure time and gain aren't fixed. Let me move this conversation down there so it's in the right context... > >> >>> + >>> + return { exposureTime, gain, quantGain, >>> + exposure / (minExposureTime_ * minGain_ * quantGain) }; >> >> I am surprised to see quantGain here...for example with the following >> values: >> >> >> exposure = 10000us >> minGain = 2.0 >> minExposureTime_ = 4000us >> lineLength_ = 30us >> Say 16 steps of analogue gain from 1x to 16x >> >> Then >> >> following clampExposureTime exposureTime = 3990us, quantGain = 1.00250626566 >> following clampGain() gain = 2.0 and quantGain = 1.00250626566 >> >> So I would expect digital gain to be calculated as 1.25 and for the >> algorithm to have to apply both dGain and qGain as digital gain such >> that 3990 * 2.0 * 1.00250626566 * 1.25 = 10000...is that not the intention? > > Ohh, thanks, that is a good point. My conclusion is a bit different > though. I think the return should look like this: > > return { exposureTime, gain, quantGain, > exposure / (exposureTime * gain * quantGain) }; > > So that the digital gain is based on the values that are actually > applied. Then the overall result is as you'd expect. That also looks fine to me > >> >> >>> + } >>> >>> - utils::Duration exposureTime; >>> double stageGain = clampGain(1.0); >>> double lastStageGain = stageGain; >>> - double gain; >>> >>> for (unsigned int stage = 0; stage < gains_.size(); stage++) { >>> - utils::Duration stageExposureTime = clampExposureTime(exposureTimes_[stage]); >>> + utils::Duration stageExposureTime = clampExposureTime(exposureTimes_[stage], >>> + &quantGain); >>> stageGain = clampGain(gains_[stage]); >>> >>> /* >>> @@ -215,18 +228,20 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const >>> >>> /* Clamp the gain to lastStageGain and regulate exposureTime. */ >>> if (stageExposureTime * lastStageGain >= exposure) { >>> - exposureTime = clampExposureTime(exposure / lastStageGain); >>> - gain = clampGain(exposure / exposureTime); >>> + exposureTime = clampExposureTime(exposure / lastStageGain, &quantGain); >>> + gain = clampGain((exposure / exposureTime) * quantGain, &quantGain); >>> If we have things set like: exposure = 14380us lineLength_ = 30us Imx477 gain model: AnalogueGainLinear{ 0, 1024, -1, 1024 }; exposure steps = 1000, 2000, 4000, 8000, 16000 gain steps = 2.0, 3.8, 5.6, 7.4, 9.2 (settings chosen to exacerbate problems which probably wouldn't appear all that often...) Then after two iterations of the loop we run clampExposureTime(14380 / 3.8, &guantGain) and get exposureTime = 3780us quantGain = 1.00111389585 And then with the implementation as-is we run clampGain(3.80847032337, &quantGain); If the sensor were capable of perfectly granular exposure and gain control we'd be setting 3784us and 3.8x, which means the exposure value lost to quantisation of the exposure time has been added back as analogue gain. I don't have a problem with that, but it seems at odds with the not automatically adding gain lost to quantisation of analogue gain into the digital gain - why do one but not the other? I suppose particularly within this loop, since it shouldn't ever resort to digital gain to fulfil the requested exposure value under conditions of perfect granularity. Now that I put numbers into the formula though I wonder if that needs looking at...in the current implementation I think we would return here... exposureTime = 3780us aGain = 3.80669 qGain = 1.00046768278 dGain = 0.998887343535 And whilst that gets the right value it means that qGain * dGain = 0.999354505945 which is probably something we want to avoid. I start to lean towards the view that it's simplest to calculate the gain needed to correct the quantisation of exposure and analogue gain separately and combine them, without letting any correction for exposure quantisation go into analogue gain: exposureTime = clampExposureTime(exposure / lastStageGain, &quantGain1); gain = clampGain(exposure / (exposureTime * quantGain), &quantGain2); quantGain = quantGain1 * quantGain2; return { exposureTime, gain, quantGain, exposure / (exposureTime * gain * quantGain) }; Then we get: exposureTime = 3780, quantGain1 = 1.00111389585 aGain = 3.79259, quantGain2 = 1.00195380993 qGain = 1.00306988212 dGain = 1.0 Like I said, this helper hurts my brain...what do you think? Dan >>> - return { exposureTime, gain, exposure / (exposureTime * gain) }; >>> + return { exposureTime, gain, quantGain, >>> + exposure / (exposureTime * gain * quantGain) }; >>> } >>> >>> /* Clamp the exposureTime to stageExposureTime and regulate gain. */ >>> if (stageExposureTime * stageGain >= exposure) { >>> exposureTime = stageExposureTime; >>> - gain = clampGain(exposure / exposureTime); >>> + gain = clampGain((exposure / exposureTime) * quantGain, &quantGain); >>> >>> - return { exposureTime, gain, exposure / (exposureTime * gain) }; >>> + return { exposureTime, gain, quantGain, >>> + exposure / (exposureTime * gain * quantGain) }; >>> } >>> >>> lastStageGain = stageGain; >>> @@ -239,10 +254,11 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const >>> * stages to use then the default stageGain of 1.0 is used so that >>> * exposure time is maxed before gain is touched at all. >>> */ >>> - exposureTime = clampExposureTime(exposure / stageGain); >>> - gain = clampGain(exposure / exposureTime); >>> + exposureTime = clampExposureTime(exposure / stageGain, &quantGain); >>> + gain = clampGain((exposure / exposureTime) * quantGain, &quantGain); >>> >>> - return { exposureTime, gain, exposure / (exposureTime * gain) }; >>> + return { exposureTime, gain, quantGain, >>> + exposure / (exposureTime * gain * quantGain) }; >>> } >>> >>> /** >>> diff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h >>> index 8701fae9a26e..cc811e9fde18 100644 >>> --- a/src/ipa/libipa/exposure_mode_helper.h >>> +++ b/src/ipa/libipa/exposure_mode_helper.h >>> @@ -30,7 +30,7 @@ public: >>> void setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime, >>> double minGain, double maxGain); >>> >>> - std::tuple<utils::Duration, double, double> >>> + std::tuple<utils::Duration, double, double, double> >>> splitExposure(utils::Duration exposure) const; >>> >>> utils::Duration minExposureTime() const { return minExposureTime_; } >>> diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp >>> index 35440b67e999..0a29326841fb 100644 >>> --- a/src/ipa/rkisp1/algorithms/agc.cpp >>> +++ b/src/ipa/rkisp1/algorithms/agc.cpp >>> @@ -567,15 +567,16 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, >>> setExposureCompensation(pow(2.0, frameContext.agc.exposureValue)); >>> >>> utils::Duration newExposureTime; >>> - double aGain, dGain; >>> - std::tie(newExposureTime, aGain, dGain) = >>> + double aGain, qGain, dGain; >>> + std::tie(newExposureTime, aGain, qGain, dGain) = >> >> I think you'll also have to fix this for the other users of >> AgcMeanLuminance; IPU3 and C55 at least. > > Yes, CI told me that also :-) > >> >> Thanks >> Dan > > Thanks, > Stefan > >> >>> calculateNewEv(frameContext.agc.constraintMode, >>> frameContext.agc.exposureMode, >>> hist, effectiveExposureValue); >>> >>> LOG(RkISP1Agc, Debug) >>> - << "Divided up exposure time, analogue gain and digital gain are " >>> - << newExposureTime << ", " << aGain << " and " << dGain; >>> + << "Divided up exposure time, analogue gain, quantization gain" >>> + << " and digital gain are " << newExposureTime << ", " << aGain >>> + << ", " << qGain << " and " << dGain; >>> >>> IPAActiveState &activeState = context.activeState; >>> /* Update the estimated exposure and gain. */ >>
diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp index 39d0aebb0838..da045640d569 100644 --- a/src/ipa/ipu3/algorithms/agc.cpp +++ b/src/ipa/ipu3/algorithms/agc.cpp @@ -222,8 +222,8 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, utils::Duration effectiveExposureValue = exposureTime * analogueGain; utils::Duration newExposureTime; - double aGain, dGain; - std::tie(newExposureTime, aGain, dGain) = + double aGain, qGain, dGain; + std::tie(newExposureTime, aGain, qGain, dGain) = calculateNewEv(context.activeState.agc.constraintMode, context.activeState.agc.exposureMode, hist, effectiveExposureValue); diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index 58c4dfc9add2..695453e7ceea 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -566,11 +566,12 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue) * * Calculate a new exposure value to try to obtain the target. The calculated * exposure value is filtered to prevent rapid changes from frame to frame, and - * divided into exposure time, analogue and digital gain. + * divided into exposure time, analogue, quantization and digital gain. * - * \return Tuple of exposure time, analogue gain, and digital gain + * \return Tuple of exposure time, analogue gain, quantization gain and digital + * gain */ -std::tuple<utils::Duration, double, double> +std::tuple<utils::Duration, double, double, double> AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex, uint32_t exposureModeIndex, const Histogram &yHist, diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h index a7c7e006af42..741ccf986625 100644 --- a/src/ipa/libipa/agc_mean_luminance.h +++ b/src/ipa/libipa/agc_mean_luminance.h @@ -68,7 +68,7 @@ public: return controls_; } - std::tuple<utils::Duration, double, double> + std::tuple<utils::Duration, double, double, double> calculateNewEv(uint32_t constraintModeIndex, uint32_t exposureModeIndex, const Histogram &yHist, utils::Duration effectiveExposureValue); diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp index 7d470ebe487c..79b8e6758faf 100644 --- a/src/ipa/libipa/exposure_mode_helper.cpp +++ b/src/ipa/libipa/exposure_mode_helper.cpp @@ -178,14 +178,23 @@ double ExposureModeHelper::clampGain(double gain, double *quantizationGain) cons * required exposure, the helper falls-back to simply maximising the exposure * time first, followed by analogue gain, followed by digital gain. * - * \return Tuple of exposure time, analogue gain, and digital gain + * During the calculations the gain missed due to quantization is recorded and + * returned as quantization gain. The quantization gain is not included in the + * digital gain. So to exactly apply the given exposure, both quantization gain + * and digital gain must be applied. + * + * \return Tuple of exposure time, analogue gain, quantization gain and digital + * gain */ -std::tuple<utils::Duration, double, double> +std::tuple<utils::Duration, double, double, double> ExposureModeHelper::splitExposure(utils::Duration exposure) const { ASSERT(maxExposureTime_); ASSERT(maxGain_); + utils::Duration exposureTime; + double gain; + double quantGain; bool gainFixed = minGain_ == maxGain_; bool exposureTimeFixed = minExposureTime_ == maxExposureTime_; @@ -193,16 +202,20 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const * There's no point entering the loop if we cannot change either gain * nor exposure time anyway. */ - if (exposureTimeFixed && gainFixed) - return { minExposureTime_, minGain_, exposure / (minExposureTime_ * minGain_) }; + if (exposureTimeFixed && gainFixed) { + exposureTime = clampExposureTime(minExposureTime_, &quantGain); + gain = clampGain(minGain_ * quantGain, &quantGain); + + return { exposureTime, gain, quantGain, + exposure / (minExposureTime_ * minGain_ * quantGain) }; + } - utils::Duration exposureTime; double stageGain = clampGain(1.0); double lastStageGain = stageGain; - double gain; for (unsigned int stage = 0; stage < gains_.size(); stage++) { - utils::Duration stageExposureTime = clampExposureTime(exposureTimes_[stage]); + utils::Duration stageExposureTime = clampExposureTime(exposureTimes_[stage], + &quantGain); stageGain = clampGain(gains_[stage]); /* @@ -215,18 +228,20 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const /* Clamp the gain to lastStageGain and regulate exposureTime. */ if (stageExposureTime * lastStageGain >= exposure) { - exposureTime = clampExposureTime(exposure / lastStageGain); - gain = clampGain(exposure / exposureTime); + exposureTime = clampExposureTime(exposure / lastStageGain, &quantGain); + gain = clampGain((exposure / exposureTime) * quantGain, &quantGain); - return { exposureTime, gain, exposure / (exposureTime * gain) }; + return { exposureTime, gain, quantGain, + exposure / (exposureTime * gain * quantGain) }; } /* Clamp the exposureTime to stageExposureTime and regulate gain. */ if (stageExposureTime * stageGain >= exposure) { exposureTime = stageExposureTime; - gain = clampGain(exposure / exposureTime); + gain = clampGain((exposure / exposureTime) * quantGain, &quantGain); - return { exposureTime, gain, exposure / (exposureTime * gain) }; + return { exposureTime, gain, quantGain, + exposure / (exposureTime * gain * quantGain) }; } lastStageGain = stageGain; @@ -239,10 +254,11 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const * stages to use then the default stageGain of 1.0 is used so that * exposure time is maxed before gain is touched at all. */ - exposureTime = clampExposureTime(exposure / stageGain); - gain = clampGain(exposure / exposureTime); + exposureTime = clampExposureTime(exposure / stageGain, &quantGain); + gain = clampGain((exposure / exposureTime) * quantGain, &quantGain); - return { exposureTime, gain, exposure / (exposureTime * gain) }; + return { exposureTime, gain, quantGain, + exposure / (exposureTime * gain * quantGain) }; } /** diff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h index 8701fae9a26e..cc811e9fde18 100644 --- a/src/ipa/libipa/exposure_mode_helper.h +++ b/src/ipa/libipa/exposure_mode_helper.h @@ -30,7 +30,7 @@ public: void setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime, double minGain, double maxGain); - std::tuple<utils::Duration, double, double> + std::tuple<utils::Duration, double, double, double> splitExposure(utils::Duration exposure) const; utils::Duration minExposureTime() const { return minExposureTime_; } diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index 35440b67e999..0a29326841fb 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -567,15 +567,16 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, setExposureCompensation(pow(2.0, frameContext.agc.exposureValue)); utils::Duration newExposureTime; - double aGain, dGain; - std::tie(newExposureTime, aGain, dGain) = + double aGain, qGain, dGain; + std::tie(newExposureTime, aGain, qGain, dGain) = calculateNewEv(frameContext.agc.constraintMode, frameContext.agc.exposureMode, hist, effectiveExposureValue); LOG(RkISP1Agc, Debug) - << "Divided up exposure time, analogue gain and digital gain are " - << newExposureTime << ", " << aGain << " and " << dGain; + << "Divided up exposure time, analogue gain, quantization gain" + << " and digital gain are " << newExposureTime << ", " << aGain + << ", " << qGain << " and " << dGain; IPAActiveState &activeState = context.activeState; /* Update the estimated exposure and gain. */
Calculate the error introduced by quantization as "quantization gain" and return it separately from splitExposure(). It is not included in the digital gain, to not silently ignore the limits imposed by the AGC configuration. Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com> --- src/ipa/ipu3/algorithms/agc.cpp | 4 +-- src/ipa/libipa/agc_mean_luminance.cpp | 7 ++-- src/ipa/libipa/agc_mean_luminance.h | 2 +- src/ipa/libipa/exposure_mode_helper.cpp | 46 +++++++++++++++++-------- src/ipa/libipa/exposure_mode_helper.h | 2 +- src/ipa/rkisp1/algorithms/agc.cpp | 9 ++--- 6 files changed, 44 insertions(+), 26 deletions(-)