From patchwork Tue Oct 20 09:32:29 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Geir X-Patchwork-Id: 28532 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id C450FC324C for ; Tue, 20 Oct 2026 09:32:43 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0D56F68E17; Tue, 20 Oct 2026 11:32:43 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=isene-com.20251104.gappssmtp.com header.i=@isene-com.20251104.gappssmtp.com header.b="hU1GElUW"; dkim-atps=neutral Received: from mail-wr1-x429.google.com (mail-wr1-x429.google.com [IPv6:2a00:1450:4864:20::429]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3588B68E17 for ; Tue, 20 Oct 2026 11:32:41 +0200 (CEST) Received: by mail-wr1-x429.google.com with SMTP id ffacd0b85a97d-48afcd7ba9bso1819827f8f.3 for ; Tue, 20 Oct 2026 02:32:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=isene-com.20251104.gappssmtp.com; s=20251104; t=1792488760; x=1793093560; darn=lists.libcamera.org; h=content-type:mime-version:subject:cc:to:message-id:date:from:from :to:cc:subject:date:message-id:reply-to:content-type; bh=l8kgFeZFfYl7sSWHCbktDBncqE37rnNmrecK6nSuK5I=; b=hU1GElUWPtGrZY3kpL9CG+SsTp+WVvoDlB6VLpghnvjlVSdkJTMvmWt1QhKid3MnG4 ralj6XJ8cDqmTYnlOQdDknZ2VuUcScR6imDMyK1m6yZucYssi3O24OZexNxyCG0fhtie hgD0pNmRQthV4ZMzSxfLYDNrrDXV0si0STYlaXPz71zve6FYAT6ElZeTAeAQP4ad6keq Wsuib1qlYcjtN0U9kxuiB28J9CE0iae4My8n+jRYDoG3DCCCpiw5QJLN9bdLYd0+h4aF AkRN5C9VAH9U3/nnO0rWdSwfyZqO27JZ4+wiW6iACrP1L7vw4F6k0Vcpj41gILfAiSP1 o/hw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1792488760; x=1793093560; h=content-type:mime-version:subject:cc:to:message-id:date:from :x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to:content-type; bh=l8kgFeZFfYl7sSWHCbktDBncqE37rnNmrecK6nSuK5I=; b=0BMrWilyJ9aeeOjSqyQHjInFub9/x/YGJqddnS2RL4QSOu95QF0sjXffGJKNZgpIZC WVRzoBvIz1pX8g+xaJoo9eKLzU/dwRsc/+/4J9elB2HMua5RPvCbotn+6E+ZOKT6ly/6 axYkabB5EGzryw+2duUVAMFUd2D2tGzxGQOwQhWQdur8719T741m/nW4hT4AeyKK9gjs o6sklvlTKA2Fc0NBI4+LsmkefXD5Yw4jv01RKMq2RKYs9I76NQtmxlFtSIl8e7QCAiCl vW5jtyt4FAljZA9OiYjatJAE3zADn14fD82Qg3Q4jfLJDGy7geZ2apBRuN14NKV4OLKU ikZg== X-Gm-Message-State: AFq9FYJOHzaqKbLfkgZU1l5uGqrWjAmdp/Y5turQUiJm732K4AfiPEKf DzePZG7ohXO/MT1eBTD8F8MxVeahWEqI3wFp9zqYF64ae5+dh3RjaoljSLC1VX3/Ps0f7uqvPPx /3g9rlxKbOA== X-Gm-Gg: AYBFou0jWmUdBF2791fqpTseCywPRqu5UzpnkiT2JTjF/ELE9r8ZCcLqvZCCdLB7rdp kjvj/xjdiqYxZQvgXWA1gtN/03QOKYW/mBIriiP3vl9mZSfEKtAr84BgS6GejIzZgNTcKHoFQHu xEIy5/kgJXr8PQKEy20Sn6C0wwQ/ELKyWiEqHjE0FLKYaHsDVqR3C987bIRF6HDbDsrLdWQDjl6 r9tUd203B3ie9mjXnEid1/QOq+JfhmY3iwmzjEX7jH8noEEd5MCF+BaS0SdOA/enUIWfugn6occ I9675+beyM8axdOPDRsvMdy5zhz9u4hgjoZa1C/jbvN0GyKN8xFFzgSFSPpHDG29RUuv6TMOUKt ver59phrzt8KFy3J9rIxBwpE4Rx9s8Iuir6NRVDxwRxw2Sz1gWRVJOeSmfeJhkzAMifLr7MaTsV KFL1qb3+IaiNEUHGb4VGTU/77iJIZBUizl5o6xO8pMC5W2L0uiSmYuGNBKm/HxFbW4pYzki7qRt 6ZginJMFQPRX853DHQuPoNtKpyCN1Q70sYK X-Received: by 2002:a05:6000:4382:b0:48e:22ff:3f9 with SMTP id ffacd0b85a97d-48e22ff0477mr487354f8f.10.1792488759706; Tue, 20 Oct 2026 02:32:39 -0700 (PDT) Received: from kastrup.localhost (c32FF45C1.static.as2116.net. [193.69.255.50]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-48e1d817d80sm5275400f8f.26.2026.10.20.02.32.30 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 20 Oct 2026 02:32:34 -0700 (PDT) From: Geir Isene Date: Tue, 20 Oct 2026 11:32:29 +0200 Message-ID: <1792488749.754103.325414999@isene.com> To: libcamera-devel@lists.libcamera.org Cc: barnabas.pocze@ideasonboard.com Subject: [PATCH] libcamera: software_isp: Leave clipped pixels out of the colour sums MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" The software ISP sets white balance by making the mean colour of the sampled pixels grey. A clipped pixel reads the same in red, green and blue, whatever its real colour. A bright window in the picture then pulls the red and blue gains towards 1, and the rest of the picture turns green. Add a pixel to the colour sums only when all its channels are below 250 of 255 (scaled to the bit depth), and count those pixels in a new sumPixels field. White balance takes the black level off and divides by that count instead of the histogram total. When every pixel is clipped, it returns no means and the last gains stay. On a Dell XPS 14 (OV02C10 on IPU6), with daylight windows behind the person in a video call, 22% of the frame was clipped and a white wall came out with red/green 0.73. With this change the same scene gives 0.91, and 0.96 for the whole frame. Signed-off-by: Geir Isene --- Tested on libcamera 0.7.2 on the laptop above, where it fixed the picture. On master it builds without warnings and passes checkstyle; I have not run master on the camera. Written with help from an AI model (Claude Opus 5.5). /Geir 🤗 .../internal/software_isp/swisp_stats.h | 11 ++++++++- src/ipa/softisp/algorithms/awb.cpp | 8 +++---- src/libcamera/software_isp/swstats_cpu.cpp | 24 +++++++++++++++---- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/include/libcamera/internal/software_isp/swisp_stats.h b/include/libcamera/internal/software_isp/swisp_stats.h index d9d0d9b..7f4e457 100644 --- a/include/libcamera/internal/software_isp/swisp_stats.h +++ b/include/libcamera/internal/software_isp/swisp_stats.h @@ -28,9 +28,18 @@ 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 + * clipped */ RGB sum_; + /** + * \brief Number of pixels added up in sum_ + * + * A pixel with any colour channel at or near the top of the range is + * left out of sum_: it reads the same in all channels whatever its + * real colour, and would pull white balance towards grey. + */ + uint64_t sumPixels; /** * \brief Number of bins in the yHistogram */ diff --git a/src/ipa/softisp/algorithms/awb.cpp b/src/ipa/softisp/algorithms/awb.cpp index 55fd326..71f6997 100644 --- a/src/ipa/softisp/algorithms/awb.cpp +++ b/src/ipa/softisp/algorithms/awb.cpp @@ -7,7 +7,6 @@ #include "awb.h" -#include #include #include @@ -115,10 +114,10 @@ void Awb::prepare(IPAContext &context, SoftIspAwbStats Awb::calculateRgbMeans(IPAContext &context, const SwIspStats *stats) const { - if (!stats->valid) + /* With every pixel clipped, return no means and keep the last gains. */ + if (!stats->valid || !stats->sumPixels) return {}; - const SwIspStats::Histogram &histogram = stats->yHistogram; const uint8_t blackLevel = context.activeState.blc.level; /* @@ -126,8 +125,7 @@ SoftIspAwbStats Awb::calculateRgbMeans(IPAContext &context, * 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 nPixels = stats->sumPixels; 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 7fb77ce..996223c 100644 --- a/src/libcamera/software_isp/swstats_cpu.cpp +++ b/src/libcamera/software_isp/swstats_cpu.cpp @@ -177,12 +177,22 @@ static constexpr unsigned int kBlueYMul = 29; /* 0.114 * 256 */ \ uint64_t sumR = 0; \ uint64_t sumG = 0; \ - uint64_t sumB = 0; + uint64_t sumB = 0; \ + uint64_t sumN = 0; +/* + * Leave clipped pixels, with any channel at 250 of 255 or above, out of the + * colour sums. + */ #define SWSTATS_ACCUMULATE_LINE_STATS(div) \ - sumR += r; \ - sumG += g; \ - sumB += b; \ + if (r < 250 * (div) && \ + g < 250 * (div) && \ + b < 250 * (div)) { \ + sumR += r; \ + sumG += g; \ + sumB += b; \ + sumN++; \ + } \ \ yVal = r * kRedYMul; \ yVal += g * kGreenYMul; \ @@ -192,7 +202,8 @@ static constexpr unsigned int kBlueYMul = 29; /* 0.114 * 256 */ #define SWSTATS_FINISH_LINE_STATS() \ stats.sum_.r() += sumR; \ stats.sum_.g() += sumG; \ - stats.sum_.b() += sumB; + stats.sum_.b() += sumB; \ + stats.sumPixels += sumN; void SwStatsCpu::statsBGGR8Line0(const uint8_t *src[], SwIspStats &stats) { @@ -391,6 +402,7 @@ void SwStatsCpu::startFrame(uint32_t frame) for (auto &s : stats_) { s.sum_ = RGB({ 0, 0, 0 }); + s.sumPixels = 0; s.yHistogram.fill(0); } } @@ -408,9 +420,11 @@ void SwStatsCpu::finishFrame(uint32_t frame, uint32_t bufferId) if (valid) { sharedStats_->sum_ = RGB({ 0, 0, 0 }); + sharedStats_->sumPixels = 0; sharedStats_->yHistogram.fill(0); for (const auto &s : stats_) { sharedStats_->sum_ += s.sum_; + sharedStats_->sumPixels += s.sumPixels; for (unsigned int j = 0; j < SwIspStats::kYHistogramSize; j++) sharedStats_->yHistogram[j] += s.yHistogram[j]; }