| Message ID | 20260506221944.066FD1EA006B@mailuser.phl.internal |
|---|---|
| State | Accepted |
| Headers | show |
| Series |
|
| Related | show |
Hi, On 6-May-26 23:46, Javier Tia wrote: > The SWSTATS_ACCUMULATE_LINE_STATS() macro divides the luminance value > for the histogram to normalize it to 8-bit range, but does not apply > the same normalization to the RGB sums. For 10-bit and 12-bit unpacked > Bayer formats this means the sums are accumulated at native bit depth > (0-1023 or 0-4095 per pixel) while the AWB algorithm subtracts an > 8-bit black level from them, under-correcting by 4x or 16x > respectively. > > This mismatch between the AWB's gain calculation (using incorrectly > BLC-subtracted sums) and the debayer's correct normalized BLC > subtraction produces a visible color cast. For example, with the > OV2740 sensor (10-bit, BLC=16), the under-subtraction skews R/G gain > by ~9%. > > Fix this by right-shifting the RGB sums in finishFrame() to normalize > them to 8-bit scale, matching the histogram and the 8-bit black level > used by AWB. A per-format sumShift_ value is set in configure(): > 0 for 8-bit and CSI-2 packed formats (already 8-bit), 2 for 10-bit, > and 4 for 12-bit unpacked formats. > > Signed-off-by: Javier Tia <floss@jetm.me> > Reviewed-by: Milan Zamazal <mzamazal@redhat.com> > Tested-by: Milan Zamazal <mzamazal@redhat.com> > Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> > Tested-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> Thanks, patch looks good to me: Reviewed-by: Hans de Goede <johannes.goede@oss.qualcomm.com> Regards, Hans > --- > include/libcamera/internal/software_isp/swstats_cpu.h | 1 + > src/libcamera/software_isp/swstats_cpu.cpp | 9 +++++++++ > 2 files changed, 10 insertions(+) > > diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h > index 802370bd..2dac6945 100644 > --- a/include/libcamera/internal/software_isp/swstats_cpu.h > +++ b/include/libcamera/internal/software_isp/swstats_cpu.h > @@ -116,6 +116,7 @@ private: > > unsigned int xShift_; > unsigned int stride_; > + unsigned int sumShift_; > > std::vector<SwIspStats> stats_; > SharedMemObject<SwIspStats> sharedStats_; > diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp > index 5366e019..b40d3334 100644 > --- a/src/libcamera/software_isp/swstats_cpu.cpp > +++ b/src/libcamera/software_isp/swstats_cpu.cpp > @@ -362,6 +362,11 @@ void SwStatsCpu::finishFrame(uint32_t frame, uint32_t bufferId) > for (unsigned int j = 0; j < SwIspStats::kYHistogramSize; j++) > sharedStats_->yHistogram[j] += s.yHistogram[j]; > } > + if (sumShift_) { > + sharedStats_->sum_.r() >>= sumShift_; > + sharedStats_->sum_.g() >>= sumShift_; > + sharedStats_->sum_.b() >>= sumShift_; > + } > } > > sharedStats_->valid = valid; > @@ -425,12 +430,15 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg, unsigned int stat > switch (bayerFormat.bitDepth) { > case 8: > stats0_ = &SwStatsCpu::statsBGGR8Line0; > + sumShift_ = 0; > return 0; > case 10: > stats0_ = &SwStatsCpu::statsBGGR10Line0; > + sumShift_ = 2; > return 0; > case 12: > stats0_ = &SwStatsCpu::statsBGGR12Line0; > + sumShift_ = 4; > return 0; > } > } > @@ -442,6 +450,7 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg, unsigned int stat > /* Skip every 3th and 4th line, sample every other 2x2 block */ > ySkipMask_ = 0x02; > xShift_ = 0; > + sumShift_ = 0; > processFrame_ = &SwStatsCpu::processBayerFrame2; > > switch (bayerFormat.order) {
On Wed, May 06, 2026 at 03:46:28PM -0600, Javier Tia wrote: > The SWSTATS_ACCUMULATE_LINE_STATS() macro divides the luminance value > for the histogram to normalize it to 8-bit range, but does not apply > the same normalization to the RGB sums. For 10-bit and 12-bit unpacked > Bayer formats this means the sums are accumulated at native bit depth > (0-1023 or 0-4095 per pixel) while the AWB algorithm subtracts an > 8-bit black level from them, under-correcting by 4x or 16x > respectively. > > This mismatch between the AWB's gain calculation (using incorrectly > BLC-subtracted sums) and the debayer's correct normalized BLC > subtraction produces a visible color cast. For example, with the > OV2740 sensor (10-bit, BLC=16), the under-subtraction skews R/G gain > by ~9%. > > Fix this by right-shifting the RGB sums in finishFrame() to normalize > them to 8-bit scale, matching the histogram and the 8-bit black level > used by AWB. A per-format sumShift_ value is set in configure(): > 0 for 8-bit and CSI-2 packed formats (already 8-bit), 2 for 10-bit, > and 4 for 12-bit unpacked formats. > > Signed-off-by: Javier Tia <floss@jetm.me> > Reviewed-by: Milan Zamazal <mzamazal@redhat.com> > Tested-by: Milan Zamazal <mzamazal@redhat.com> > Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> > Tested-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> > --- > include/libcamera/internal/software_isp/swstats_cpu.h | 1 + > src/libcamera/software_isp/swstats_cpu.cpp | 9 +++++++++ > 2 files changed, 10 insertions(+) > > diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h > index 802370bd..2dac6945 100644 > --- a/include/libcamera/internal/software_isp/swstats_cpu.h > +++ b/include/libcamera/internal/software_isp/swstats_cpu.h > @@ -116,6 +116,7 @@ private: > > unsigned int xShift_; > unsigned int stride_; > + unsigned int sumShift_; > > std::vector<SwIspStats> stats_; > SharedMemObject<SwIspStats> sharedStats_; > diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp > index 5366e019..b40d3334 100644 > --- a/src/libcamera/software_isp/swstats_cpu.cpp > +++ b/src/libcamera/software_isp/swstats_cpu.cpp > @@ -362,6 +362,11 @@ void SwStatsCpu::finishFrame(uint32_t frame, uint32_t bufferId) > for (unsigned int j = 0; j < SwIspStats::kYHistogramSize; j++) > sharedStats_->yHistogram[j] += s.yHistogram[j]; > } > + if (sumShift_) { > + sharedStats_->sum_.r() >>= sumShift_; > + sharedStats_->sum_.g() >>= sumShift_; > + sharedStats_->sum_.b() >>= sumShift_; > + } Should we do this unconditionally ? Shifting right by 0 is a no-op. Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> > } > > sharedStats_->valid = valid; > @@ -425,12 +430,15 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg, unsigned int stat > switch (bayerFormat.bitDepth) { > case 8: > stats0_ = &SwStatsCpu::statsBGGR8Line0; > + sumShift_ = 0; > return 0; > case 10: > stats0_ = &SwStatsCpu::statsBGGR10Line0; > + sumShift_ = 2; > return 0; > case 12: > stats0_ = &SwStatsCpu::statsBGGR12Line0; > + sumShift_ = 4; > return 0; > } > } > @@ -442,6 +450,7 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg, unsigned int stat > /* Skip every 3th and 4th line, sample every other 2x2 block */ > ySkipMask_ = 0x02; > xShift_ = 0; > + sumShift_ = 0; > processFrame_ = &SwStatsCpu::processBayerFrame2; > > switch (bayerFormat.order) {
2026. 05. 07. 16:21 keltezéssel, Laurent Pinchart írta: > On Wed, May 06, 2026 at 03:46:28PM -0600, Javier Tia wrote: >> The SWSTATS_ACCUMULATE_LINE_STATS() macro divides the luminance value >> for the histogram to normalize it to 8-bit range, but does not apply >> the same normalization to the RGB sums. For 10-bit and 12-bit unpacked >> Bayer formats this means the sums are accumulated at native bit depth >> (0-1023 or 0-4095 per pixel) while the AWB algorithm subtracts an >> 8-bit black level from them, under-correcting by 4x or 16x >> respectively. >> >> This mismatch between the AWB's gain calculation (using incorrectly >> BLC-subtracted sums) and the debayer's correct normalized BLC >> subtraction produces a visible color cast. For example, with the >> OV2740 sensor (10-bit, BLC=16), the under-subtraction skews R/G gain >> by ~9%. >> >> Fix this by right-shifting the RGB sums in finishFrame() to normalize >> them to 8-bit scale, matching the histogram and the 8-bit black level >> used by AWB. A per-format sumShift_ value is set in configure(): >> 0 for 8-bit and CSI-2 packed formats (already 8-bit), 2 for 10-bit, >> and 4 for 12-bit unpacked formats. >> >> Signed-off-by: Javier Tia <floss@jetm.me> >> Reviewed-by: Milan Zamazal <mzamazal@redhat.com> >> Tested-by: Milan Zamazal <mzamazal@redhat.com> >> Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> >> Tested-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> >> --- >> include/libcamera/internal/software_isp/swstats_cpu.h | 1 + >> src/libcamera/software_isp/swstats_cpu.cpp | 9 +++++++++ >> 2 files changed, 10 insertions(+) >> >> diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h >> index 802370bd..2dac6945 100644 >> --- a/include/libcamera/internal/software_isp/swstats_cpu.h >> +++ b/include/libcamera/internal/software_isp/swstats_cpu.h >> @@ -116,6 +116,7 @@ private: >> >> unsigned int xShift_; >> unsigned int stride_; >> + unsigned int sumShift_; >> >> std::vector<SwIspStats> stats_; >> SharedMemObject<SwIspStats> sharedStats_; >> diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp >> index 5366e019..b40d3334 100644 >> --- a/src/libcamera/software_isp/swstats_cpu.cpp >> +++ b/src/libcamera/software_isp/swstats_cpu.cpp >> @@ -362,6 +362,11 @@ void SwStatsCpu::finishFrame(uint32_t frame, uint32_t bufferId) >> for (unsigned int j = 0; j < SwIspStats::kYHistogramSize; j++) >> sharedStats_->yHistogram[j] += s.yHistogram[j]; >> } >> + if (sumShift_) { >> + sharedStats_->sum_.r() >>= sumShift_; >> + sharedStats_->sum_.g() >>= sumShift_; >> + sharedStats_->sum_.b() >>= sumShift_; >> + } > > Should we do this unconditionally ? Shifting right by 0 is a no-op. I second that! > > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> > >> } >> >> sharedStats_->valid = valid; >> @@ -425,12 +430,15 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg, unsigned int stat >> switch (bayerFormat.bitDepth) { >> case 8: >> stats0_ = &SwStatsCpu::statsBGGR8Line0; >> + sumShift_ = 0; >> return 0; >> case 10: >> stats0_ = &SwStatsCpu::statsBGGR10Line0; >> + sumShift_ = 2; >> return 0; >> case 12: >> stats0_ = &SwStatsCpu::statsBGGR12Line0; >> + sumShift_ = 4; >> return 0; >> } >> } >> @@ -442,6 +450,7 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg, unsigned int stat >> /* Skip every 3th and 4th line, sample every other 2x2 block */ >> ySkipMask_ = 0x02; >> xShift_ = 0; >> + sumShift_ = 0; >> processFrame_ = &SwStatsCpu::processBayerFrame2; >> >> switch (bayerFormat.order) { >
diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h index 802370bd..2dac6945 100644 --- a/include/libcamera/internal/software_isp/swstats_cpu.h +++ b/include/libcamera/internal/software_isp/swstats_cpu.h @@ -116,6 +116,7 @@ private: unsigned int xShift_; unsigned int stride_; + unsigned int sumShift_; std::vector<SwIspStats> stats_; SharedMemObject<SwIspStats> sharedStats_; diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp index 5366e019..b40d3334 100644 --- a/src/libcamera/software_isp/swstats_cpu.cpp +++ b/src/libcamera/software_isp/swstats_cpu.cpp @@ -362,6 +362,11 @@ void SwStatsCpu::finishFrame(uint32_t frame, uint32_t bufferId) for (unsigned int j = 0; j < SwIspStats::kYHistogramSize; j++) sharedStats_->yHistogram[j] += s.yHistogram[j]; } + if (sumShift_) { + sharedStats_->sum_.r() >>= sumShift_; + sharedStats_->sum_.g() >>= sumShift_; + sharedStats_->sum_.b() >>= sumShift_; + } } sharedStats_->valid = valid; @@ -425,12 +430,15 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg, unsigned int stat switch (bayerFormat.bitDepth) { case 8: stats0_ = &SwStatsCpu::statsBGGR8Line0; + sumShift_ = 0; return 0; case 10: stats0_ = &SwStatsCpu::statsBGGR10Line0; + sumShift_ = 2; return 0; case 12: stats0_ = &SwStatsCpu::statsBGGR12Line0; + sumShift_ = 4; return 0; } } @@ -442,6 +450,7 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg, unsigned int stat /* Skip every 3th and 4th line, sample every other 2x2 block */ ySkipMask_ = 0x02; xShift_ = 0; + sumShift_ = 0; processFrame_ = &SwStatsCpu::processBayerFrame2; switch (bayerFormat.order) {