All of lore.kernel.org
 help / color / mirror / Atom feed
From: Balakrishnan Sambath <balakrishnan.s@microchip.com>
To: <linux-media@vger.kernel.org>
Cc: <mchehab@kernel.org>, <hverkuil@kernel.org>,
	<nicolas.ferre@microchip.com>, <linux-kernel@vger.kernel.org>
Subject: [PATCH v3 13/15] media: microchip-isc: use weighted averages for Grey World AWB
Date: Wed, 13 May 2026 12:47:40 +0530	[thread overview]
Message-ID: <20260513071742.97263-14-balakrishnan.s@microchip.com> (raw)
In-Reply-To: <20260513071742.97263-1-balakrishnan.s@microchip.com>

Replace pixel counts with intensity-weighted averages. Add 2% outlier
rejection at histogram tails.

Signed-off-by: Balakrishnan Sambath <balakrishnan.s@microchip.com>
---
 .../platform/microchip/microchip-isc-base.c   | 167 +++++++++++++-----
 .../media/platform/microchip/microchip-isc.h  |   2 +
 2 files changed, 125 insertions(+), 44 deletions(-)

diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c
index 28957c2169b7..ea71cf50eb58 100644
--- a/drivers/media/platform/microchip/microchip-isc-base.c
+++ b/drivers/media/platform/microchip/microchip-isc-base.c
@@ -40,6 +40,12 @@
 	(((mbus_code) == MEDIA_BUS_FMT_Y10_1X10) | \
 	(((mbus_code) == MEDIA_BUS_FMT_Y8_1X8)))
 
+/* 4.0 in Q9 fixed-point: cap grey-world correction at 4x. */
+#define ISC_AWB_GW_GAIN_MAX	(4u << 9)
+
+/* Outlier rejection: skip darkest/brightest 2% of histogram. */
+#define ISC_AWB_OUTLIER_DIV	50
+
 static inline void isc_update_v4l2_ctrls(struct isc_device *isc)
 {
 	struct isc_ctrls *ctrls = &isc->ctrls;
@@ -1402,6 +1408,11 @@ static void isc_hist_count(struct isc_device *isc, u32 *min, u32 *max)
 	u32 *hist_count = &ctrls->hist_count[ctrls->hist_id];
 	u32 *hist_entry = &ctrls->hist_entry[0];
 	u32 i;
+	u32 total_pixels;
+	u32 dark_threshold, bright_threshold;
+	u32 cumulative;
+	u64 weighted_sum;
+	u32 pixel_count;
 
 	*min = 0;
 	*max = HIST_ENTRIES;
@@ -1409,44 +1420,103 @@ static void isc_hist_count(struct isc_device *isc, u32 *min, u32 *max)
 	regmap_bulk_read(regmap, ISC_HIS_ENTRY + isc->offsets.his_entry,
 			 hist_entry, HIST_ENTRIES);
 
-	*hist_count = 0;
-	/*
-	 * we deliberately ignore the end of the histogram,
-	 * the most white pixels
-	 */
+	/* Calculate total pixels */
+	total_pixels = 0;
+	for (i = 0; i < HIST_ENTRIES; i++)
+		total_pixels += hist_entry[i];
+
+	/* Handle empty histogram case */
+	if (total_pixels == 0) {
+		*hist_count = 0;
+		ctrls->channel_avg[ctrls->hist_id] = 256; /* Default middle value */
+		ctrls->total_pixels[ctrls->hist_id] = 0;
+		*min = 1;
+		*max = HIST_ENTRIES - 1;
+		dev_dbg(isc->dev,
+			"isc wb: no pixels in histogram for channel %u\n",
+			ctrls->hist_id);
+		return;
+	}
+
+	/* Outlier rejection: skip darkest/brightest 2% of histogram */
+	dark_threshold = total_pixels / ISC_AWB_OUTLIER_DIV;
+	bright_threshold = total_pixels / ISC_AWB_OUTLIER_DIV;
+	cumulative = 0;
+
+	/* Find effective minimum (skip dark noise) */
+	*min = 1;
 	for (i = 1; i < HIST_ENTRIES; i++) {
-		if (*hist_entry && !*min)
+		cumulative += hist_entry[i];
+		if (cumulative > dark_threshold) {
 			*min = i;
-		if (*hist_entry)
+			break;
+		}
+	}
+
+	/* Find effective maximum (skip bright saturation) */
+	cumulative = 0;
+	*max = HIST_ENTRIES - 1;
+	for (i = HIST_ENTRIES - 1; i > *min; i--) {
+		cumulative += hist_entry[i];
+		if (cumulative > bright_threshold) {
 			*max = i;
-		*hist_count += i * (*hist_entry++);
+			break;
+		}
 	}
 
+	/* Ensure reasonable range */
+	if (*max <= *min) {
+		*min = HIST_ENTRIES / 4;
+		*max = (HIST_ENTRIES * 3) / 4;
+	}
+
+	/* Calculate both pixel count and weighted average for useful range */
+	*hist_count = 0;
+	weighted_sum = 0;
+
+	for (i = *min; i <= *max; i++) {
+		pixel_count = hist_entry[i];
+		*hist_count += pixel_count;
+		weighted_sum += (u64)i * pixel_count;
+	}
+
+	/* Store total useful pixels for this channel */
+	ctrls->total_pixels[ctrls->hist_id] = *hist_count;
+
+	/* Calculate channel average */
+	if (*hist_count > 0)
+		ctrls->channel_avg[ctrls->hist_id] =
+			div64_u64(weighted_sum, *hist_count);
+	else
+		/* Default middle value */
+		ctrls->channel_avg[ctrls->hist_id] = 256;
+
 	if (!*min)
 		*min = 1;
 
-	dev_dbg(isc->dev, "isc wb: hist_id %u, hist_count %u",
-		ctrls->hist_id, *hist_count);
+	dev_dbg(isc->dev,
+		"isc wb: hist_id %u, avg %u, count %u, range [%u,%u], total %u\n",
+		ctrls->hist_id, ctrls->channel_avg[ctrls->hist_id],
+		*hist_count, *min, *max, total_pixels);
 }
 
 static void isc_wb_update(struct isc_ctrls *ctrls)
 {
 	struct isc_device *isc = container_of(ctrls, struct isc_device, ctrls);
-	u32 *hist_count = &ctrls->hist_count[0];
 	u32 c, offset[4];
 	u64 avg = 0;
-	/* We compute two gains, stretch gain and grey world gain */
-	u32 s_gain[4], gw_gain[4];
+	u32 gain, gw_gain, s_gain;
+	u32 min_pixels;
+	u32 frame_pixels;
 
 	/*
 	 * According to Grey World, we need to set gains for R/B to normalize
 	 * them towards the green channel.
-	 * Thus we want to keep Green as fixed and adjust only Red/Blue
-	 * Compute the average of the both green channels first
+	 * Thus we want to keep Green as fixed and adjust only Red/Blue.
+	 * Compute the average of the both green channels first.
 	 */
-	avg = (u64)hist_count[ISC_HIS_CFG_MODE_GR] +
-		(u64)hist_count[ISC_HIS_CFG_MODE_GB];
-	avg >>= 1;
+	avg = (ctrls->channel_avg[ISC_HIS_CFG_MODE_GR] +
+		ctrls->channel_avg[ISC_HIS_CFG_MODE_GB]) >> 1;
 
 	dev_dbg(isc->dev, "isc wb: green components average %llu\n", avg);
 
@@ -1454,7 +1524,23 @@ static void isc_wb_update(struct isc_ctrls *ctrls)
 	if (!avg)
 		return;
 
+	/*
+	 * Require a minimum pixel count for both black-level offset and
+	 * grey-world gain: 1/64 of the frame area, which equals ~6.25% of
+	 * one Bayer channel's expected pixel count.  This scales with sensor
+	 * resolution and prevents noise-dominated histograms (from very small
+	 * crops or a nearly-empty frame) from producing wild corrections.
+	 * A floor of 64 ensures the guard is non-zero for tiny crops.
+	 */
+	frame_pixels = isc->fmt.fmt.pix.width * isc->fmt.fmt.pix.height;
+	min_pixels = frame_pixels ? max(frame_pixels >> 6, 64u) : 64u;
+
 	for (c = ISC_HIS_CFG_MODE_GR; c <= ISC_HIS_CFG_MODE_B; c++) {
+		u32 hist_min = ctrls->hist_minmax[c][HIST_MIN_INDEX];
+		u32 hist_max = ctrls->hist_minmax[c][HIST_MAX_INDEX];
+		u32 channel_avg = ctrls->channel_avg[c];
+		u32 total_pixels = ctrls->total_pixels[c];
+
 		/*
 		 * the color offset is the minimum value of the histogram.
 		 * we stretch this color to the full range by substracting
@@ -1480,40 +1566,33 @@ static void isc_wb_update(struct isc_ctrls *ctrls)
 		ctrls->offset[c] = -ctrls->offset[c];
 
 		/*
-		 * the stretch gain is the total number of histogram bins
-		 * divided by the actual range of color component (Max - Min)
-		 * If we compute gain like this, the actual color component
-		 * will be stretched to the full histogram.
-		 * We need to shift 9 bits for precision, we have 9 bits for
-		 * decimals
+		 * Stretch gain: scale the histogram range [hist_min, hist_max]
+		 * to the full 512-bin span.  Result is in Q9 fixed-point
+		 * (1.0 = 512).
 		 */
-		s_gain[c] = (HIST_ENTRIES << 9) /
-			(ctrls->hist_minmax[c][HIST_MAX_INDEX] -
-			ctrls->hist_minmax[c][HIST_MIN_INDEX] + 1);
+		s_gain = (HIST_ENTRIES << 9) / (hist_max - hist_min + 1);
 
 		/*
-		 * Now we have to compute the gain w.r.t. the average.
-		 * Add/lose gain to the component towards the average.
-		 * If it happens that the component is zero, use the
-		 * fixed point value : 1.0 gain.
+		 * Grey-world gain: scale each channel towards the green
+		 * average.  Require a minimum pixel count so noise-dominated
+		 * channels do not produce wild corrections.
 		 */
-		if (hist_count[c])
-			gw_gain[c] = div_u64(avg << 9, hist_count[c]);
+		if (channel_avg > 0 && total_pixels >= min_pixels)
+			gw_gain = div64_u64((avg << 9), channel_avg);
 		else
-			gw_gain[c] = 1 << 9;
+			gw_gain = 1 << 9;
 
-		dev_dbg(isc->dev,
-			"isc wb: component %d, s_gain %u, gw_gain %u\n",
-			c, s_gain[c], gw_gain[c]);
-		/* multiply both gains and adjust for decimals */
-		ctrls->gain[c] = s_gain[c] * gw_gain[c];
-		ctrls->gain[c] >>= 9;
+		/* Cap grey-world correction at 4x to avoid over-amplification. */
+		gw_gain = min_t(u32, gw_gain, ISC_AWB_GW_GAIN_MAX);
 
-		/* make sure we are not out of range */
-		ctrls->gain[c] = clamp_val(ctrls->gain[c], 0, GENMASK(12, 0));
+		/* Combine stretch and grey-world gains; result stays in Q9. */
+		gain = (s_gain * gw_gain) >> 9;
 
-		dev_dbg(isc->dev, "isc wb: component %d, final gain %u\n",
-			c, ctrls->gain[c]);
+		ctrls->gain[c] = clamp_val(gain, 0, GENMASK(12, 0));
+
+		dev_dbg(isc->dev,
+			"isc wb: c=%u black=%u avg=%u s_gain=%u gw_gain=%u gain=%u",
+			c, hist_min, channel_avg, s_gain, gw_gain, gain);
 	}
 }
 
diff --git a/drivers/media/platform/microchip/microchip-isc.h b/drivers/media/platform/microchip/microchip-isc.h
index a4f1e6c22e44..44d54404250d 100644
--- a/drivers/media/platform/microchip/microchip-isc.h
+++ b/drivers/media/platform/microchip/microchip-isc.h
@@ -164,6 +164,8 @@ struct isc_ctrls {
 #define HIST_MIN_INDEX		0
 #define HIST_MAX_INDEX		1
 	u32 hist_minmax[HIST_BAYER][2];
+	u32 channel_avg[HIST_BAYER];      /* Average pixel intensity per channel */
+	u32 total_pixels[HIST_BAYER];     /* Total pixels per channel */
 
 	/*
 	 * Custom per-channel gamma LUT (10-bit output values, 64 entries).
-- 
2.34.1


  parent reply	other threads:[~2026-05-13  7:18 UTC|newest]

Thread overview: 65+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-10-09 15:52 [PATCH 00/18] media: microchip-isc: Color correction and histogram stats Balamanikandan Gunasundar
2025-10-09 15:52 ` [PATCH 01/18] media: platform: microchip: set maximum resolution for sam9x7 Balamanikandan Gunasundar
2025-10-09 15:52 ` [PATCH 02/18] media: platform: microchip: Include DPC modules flags in pipeline Balamanikandan Gunasundar
2025-10-09 15:52 ` [PATCH 03/18] media: microchip-isc: Enable GDC and CBC module flags for RGB formats Balamanikandan Gunasundar
2025-10-09 15:52 ` [PATCH 04/18] media: microchip-isc: Improve histogram calculation with outlier rejection Balamanikandan Gunasundar
2025-10-09 15:52 ` [PATCH 05/18] media: microchip-isc: Use channel averages for Grey World AWB Balamanikandan Gunasundar
2025-10-09 15:52 ` [PATCH 06/18] media: microchip-isc: Add range based black level correction Balamanikandan Gunasundar
2025-10-09 15:52 ` [PATCH 07/18] media: platform: microchip: Extend gamma table and control range Balamanikandan Gunasundar
2025-10-09 15:52 ` [PATCH 08/18] media: platform: microchip: Add new histogram submodule Balamanikandan Gunasundar
2025-11-10  8:41   ` Hans Verkuil
2025-11-10  8:55   ` Hans Verkuil
2025-10-09 15:52 ` [PATCH 09/18] media: microchip-isc: Register and unregister statistics device Balamanikandan Gunasundar
2025-10-09 15:52 ` [PATCH 10/18] media: microchip-isc: Always enable histogram for all RAW formats Balamanikandan Gunasundar
2025-10-09 15:52 ` [PATCH 11/18] media: microchip-isc: expose hue and saturation as v4l2 controls Balamanikandan Gunasundar
2025-10-09 15:52 ` [PATCH 12/18] media: microchip-isc: Rename CBC to CBHS Balamanikandan Gunasundar
2025-10-09 15:52 ` [PATCH 13/18] media: microchip-isc: Store histogram data of all channels Balamanikandan Gunasundar
2025-10-09 15:52 ` [PATCH 14/18] media: microchip-isc: fix histogram state initialization order Balamanikandan Gunasundar
2025-10-09 15:52 ` [PATCH 15/18] media: microchip-isc: decouple histogram cycling from AWB mode Balamanikandan Gunasundar
2025-10-09 15:52 ` [PATCH 16/18] media: microchip-isc: enable userspace histogram statistics export Balamanikandan Gunasundar
2025-10-09 15:52 ` [PATCH 17/18] media: videodev2.h, v4l2-ioctl: Add microchip statistics format Balamanikandan Gunasundar
2025-11-10  8:49   ` Hans Verkuil
2025-10-09 15:52 ` [PATCH 18/18] media: microchip-isc: expose color correction registers as v4l2 controls Balamanikandan Gunasundar
2025-10-09 20:11 ` [PATCH 00/18] media: microchip-isc: Color correction and histogram stats Eugen Hristev
2025-10-10  8:21   ` Kieran Bingham
2025-10-10  8:27     ` Laurent Pinchart
2025-10-15  6:05     ` Balamanikandan.Gunasundar
2025-10-15  6:45       ` Balamanikandan.Gunasundar
2025-10-15  6:33     ` Balamanikandan.Gunasundar
2025-10-15  5:26   ` Balamanikandan.Gunasundar
2025-11-10  9:01 ` Hans Verkuil
2025-11-10  9:26 ` Hans Verkuil
2026-05-12 15:43 ` [PATCH v2 00/15] media: microchip-isc: fixes and enhancements Balakrishnan Sambath
2026-05-12 15:43   ` [PATCH v2 01/15] media: microchip-isc: fix SBGGR10 Bayer pattern Balakrishnan Sambath
2026-05-12 15:43   ` [PATCH v2 02/15] media: microchip-isc: mask WB offset and gain register fields Balakrishnan Sambath
2026-05-12 15:43   ` [PATCH v2 03/15] media: microchip-isc: fix race condition on stream stop Balakrishnan Sambath
2026-05-12 15:43   ` [PATCH v2 04/15] media: microchip-isc: fix PM runtime leak in AWB work handler Balakrishnan Sambath
2026-05-12 15:43   ` [PATCH v2 05/15] media: microchip-isc: add driver documentation Balakrishnan Sambath
2026-05-12 15:43   ` [PATCH v2 06/15] media: microchip-isc: set SAM9X7 maximum resolution to 2560x1920 Balakrishnan Sambath
2026-05-12 15:43   ` [PATCH v2 07/15] media: microchip-isc: configure DPC and pipeline for SAMA7G5 Balakrishnan Sambath
2026-05-12 15:43   ` [PATCH v2 08/15] media: microchip-isc: add gamma 1.8 and 2.4 correction curves Balakrishnan Sambath
2026-05-12 15:43   ` [PATCH v2 09/15] media: microchip-isc: add SAMA7G5 hue and saturation controls Balakrishnan Sambath
2026-05-12 15:43   ` [PATCH v2 10/15] media: microchip-isc: expose color correction matrix as V4L2 controls Balakrishnan Sambath
2026-05-12 15:43   ` [PATCH v2 11/15] media: microchip-isc: add per-channel gamma LUT controls Balakrishnan Sambath
2026-05-15 10:26     ` Sakari Ailus
2026-05-12 15:43   ` [PATCH v2 12/15] media: microchip-isc: reset pipeline state on kernel AWB enable Balakrishnan Sambath
2026-05-12 15:43   ` [PATCH v2 13/15] media: microchip-isc: use weighted averages for Grey World AWB Balakrishnan Sambath
2026-05-12 15:43   ` [PATCH v2 14/15] media: microchip-isc: smooth AWB gains with EMA filter Balakrishnan Sambath
2026-05-12 15:43   ` [PATCH v2 15/15] media: microchip-isc: scale DPC black level to sensor bit depth Balakrishnan Sambath
2026-05-13  7:17   ` [PATCH v3 00/15] media: microchip-isc: fixes and enhancements Balakrishnan Sambath
2026-05-13  7:17     ` [PATCH v3 01/15] media: microchip-isc: fix SBGGR10 Bayer pattern Balakrishnan Sambath
2026-05-13  7:17     ` [PATCH v3 02/15] media: microchip-isc: mask WB offset and gain register fields Balakrishnan Sambath
2026-05-13  7:17     ` [PATCH v3 03/15] media: microchip-isc: fix race condition on stream stop Balakrishnan Sambath
2026-05-13  7:17     ` [PATCH v3 04/15] media: microchip-isc: fix PM runtime leak in AWB work handler Balakrishnan Sambath
2026-05-13  7:17     ` [PATCH v3 05/15] media: microchip-isc: add driver documentation Balakrishnan Sambath
2026-05-13  7:17     ` [PATCH v3 06/15] media: microchip-isc: set SAM9X7 maximum resolution to 2560x1920 Balakrishnan Sambath
2026-05-13  7:17     ` [PATCH v3 07/15] media: microchip-isc: configure DPC and pipeline for SAMA7G5 Balakrishnan Sambath
2026-05-13  7:17     ` [PATCH v3 08/15] media: microchip-isc: add gamma 1.8 and 2.4 correction curves Balakrishnan Sambath
2026-05-13  7:17     ` [PATCH v3 09/15] media: microchip-isc: add SAMA7G5 hue and saturation controls Balakrishnan Sambath
2026-05-13  7:17     ` [PATCH v3 10/15] media: microchip-isc: expose color correction matrix as V4L2 controls Balakrishnan Sambath
2026-05-13  7:17     ` [PATCH v3 11/15] media: microchip-isc: add per-channel gamma LUT controls Balakrishnan Sambath
2026-05-13  7:17     ` [PATCH v3 12/15] media: microchip-isc: reset pipeline state on kernel AWB enable Balakrishnan Sambath
2026-05-13  7:17     ` Balakrishnan Sambath [this message]
2026-05-13  7:17     ` [PATCH v3 14/15] media: microchip-isc: smooth AWB gains with EMA filter Balakrishnan Sambath
2026-05-13  7:17     ` [PATCH v3 15/15] media: microchip-isc: scale DPC black level to sensor bit depth Balakrishnan Sambath
2026-05-15 10:27     ` [PATCH v3 00/15] media: microchip-isc: fixes and enhancements Sakari Ailus

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260513071742.97263-14-balakrishnan.s@microchip.com \
    --to=balakrishnan.s@microchip.com \
    --cc=hverkuil@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-media@vger.kernel.org \
    --cc=mchehab@kernel.org \
    --cc=nicolas.ferre@microchip.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.