diff --git a/include/libcamera/internal/software_isp/swisp_stats.h b/include/libcamera/internal/software_isp/swisp_stats.h
index d9d0d9be8..f15c8aae5 100644
--- a/include/libcamera/internal/software_isp/swisp_stats.h
+++ b/include/libcamera/internal/software_isp/swisp_stats.h
@@ -28,9 +28,14 @@ struct SwIspStats {
 	 */
 	bool valid;
 	/**
-	 * \brief Sums of colour channels of all the sampled pixels
+	 * \brief Sums of colour channels of the sampled pixels that are not
+	 *        (near) saturated
 	 */
 	RGB<uint64_t> sum_;
+	/**
+	 * \brief Number of sampled pixels included in sum_
+	 */
+	uint64_t sumCount_;
 	/**
 	 * \brief Number of bins in the yHistogram
 	 */
diff --git a/src/ipa/softisp/algorithms/awb.cpp b/src/ipa/softisp/algorithms/awb.cpp
index 55fd326fc..e6b750d87 100644
--- a/src/ipa/softisp/algorithms/awb.cpp
+++ b/src/ipa/softisp/algorithms/awb.cpp
@@ -7,7 +7,6 @@
 
 #include "awb.h"
 
-#include <numeric>
 #include <stdint.h>
 
 #include <libcamera/base/log.h>
@@ -118,16 +117,18 @@ SoftIspAwbStats Awb::calculateRgbMeans(IPAContext &context,
 	if (!stats->valid)
 		return {};
 
-	const SwIspStats::Histogram &histogram = stats->yHistogram;
 	const uint8_t blackLevel = context.activeState.blc.level;
 
+	/* Saturated pixels are not included in the sums. */
+	const uint64_t nPixels = stats->sumCount_;
+	if (!nPixels)
+		return {};
+
 	/*
 	 * Black level must be subtracted to get the correct AWB ratios, they
 	 * would be off if they were computed from the whole brightness range
 	 * rather than from the sensor range.
 	 */
-	const uint64_t nPixels = std::accumulate(
-		histogram.begin(), histogram.end(), uint64_t(0));
 	const uint64_t offset = blackLevel * nPixels;
 	const uint64_t minValid = 1;
 
diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp
index 7fb77ce7d..c4391a261 100644
--- a/src/libcamera/software_isp/swstats_cpu.cpp
+++ b/src/libcamera/software_isp/swstats_cpu.cpp
@@ -11,6 +11,8 @@
 
 #include "libcamera/internal/software_isp/swstats_cpu.h"
 
+#include <algorithm>
+
 #include <libcamera/base/log.h>
 
 #include <libcamera/stream.h>
@@ -171,28 +173,40 @@ static constexpr unsigned int kRedYMul = 77; /* 0.299 * 256 */
 static constexpr unsigned int kGreenYMul = 150; /* 0.587 * 256 */
 static constexpr unsigned int kBlueYMul = 29; /* 0.114 * 256 */
 
+/*
+ * Pixels with any colour component at or above this 8-bit level are left out
+ * of the colour sums. Clipped pixels have lost their colour information and
+ * would skew the AWB ratios, typically towards the channel that clips last.
+ */
+static constexpr unsigned int kSaturationLevel = 250;
+
 #define SWSTATS_START_LINE_STATS(pixel_t) \
 	pixel_t r, g, g2, b;              \
 	uint64_t yVal;                    \
                                           \
 	uint64_t sumR = 0;                \
 	uint64_t sumG = 0;                \
-	uint64_t sumB = 0;
-
-#define SWSTATS_ACCUMULATE_LINE_STATS(div) \
-	sumR += r;                         \
-	sumG += g;                         \
-	sumB += b;                         \
-                                           \
-	yVal = r * kRedYMul;               \
-	yVal += g * kGreenYMul;            \
-	yVal += b * kBlueYMul;             \
+	uint64_t sumB = 0;                \
+	uint64_t sumCount = 0;
+
+#define SWSTATS_ACCUMULATE_LINE_STATS(div)                      \
+	if (std::max({ r, g, b }) < kSaturationLevel * (div)) { \
+		sumR += r;                                      \
+		sumG += g;                                      \
+		sumB += b;                                      \
+		sumCount++;                                     \
+	}                                                       \
+                                                                \
+	yVal = r * kRedYMul;                                    \
+	yVal += g * kGreenYMul;                                 \
+	yVal += b * kBlueYMul;                                  \
 	stats.yHistogram[yVal * SwIspStats::kYHistogramSize / (256 * 256 * (div))]++;
 
 #define SWSTATS_FINISH_LINE_STATS() \
 	stats.sum_.r() += sumR;     \
 	stats.sum_.g() += sumG;     \
-	stats.sum_.b() += sumB;
+	stats.sum_.b() += sumB;     \
+	stats.sumCount_ += sumCount;
 
 void SwStatsCpu::statsBGGR8Line0(const uint8_t *src[], SwIspStats &stats)
 {
@@ -391,6 +405,7 @@ void SwStatsCpu::startFrame(uint32_t frame)
 
 	for (auto &s : stats_) {
 		s.sum_ = RGB<uint64_t>({ 0, 0, 0 });
+		s.sumCount_ = 0;
 		s.yHistogram.fill(0);
 	}
 }
@@ -408,9 +423,11 @@ void SwStatsCpu::finishFrame(uint32_t frame, uint32_t bufferId)
 
 	if (valid) {
 		sharedStats_->sum_ = RGB<uint64_t>({ 0, 0, 0 });
+		sharedStats_->sumCount_ = 0;
 		sharedStats_->yHistogram.fill(0);
 		for (const auto &s : stats_) {
 			sharedStats_->sum_ += s.sum_;
+			sharedStats_->sumCount_ += s.sumCount_;
 			for (unsigned int j = 0; j < SwIspStats::kYHistogramSize; j++)
 				sharedStats_->yHistogram[j] += s.yHistogram[j];
 		}
