From patchwork Wed Feb 25 22:18:58 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Javier Tia X-Patchwork-Id: 26238 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 DA724C32C8 for ; Wed, 25 Feb 2026 22:20:18 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D8CE9622B2; Wed, 25 Feb 2026 23:20:16 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=jetm.me header.i=@jetm.me header.b="N5awxEPK"; dkim=pass (2048-bit key; unprotected) header.d=messagingengine.com header.i=@messagingengine.com header.b="pyyjpXbq"; dkim-atps=neutral Received: from fhigh-a2-smtp.messagingengine.com (fhigh-a2-smtp.messagingengine.com [103.168.172.153]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 65FCD61FA0 for ; Wed, 25 Feb 2026 23:20:15 +0100 (CET) Received: from phl-compute-05.internal (phl-compute-05.internal [10.202.2.45]) by mailfhigh.phl.internal (Postfix) with ESMTP id B2B981400215; Wed, 25 Feb 2026 17:20:14 -0500 (EST) Received: from phl-frontend-04 ([10.202.2.163]) by phl-compute-05.internal (MEProxy); Wed, 25 Feb 2026 17:20:14 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=jetm.me; h=cc:cc :content-transfer-encoding:content-type:date:date:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to; s=fm2; t=1772058014; x= 1772144414; bh=OaeDzIOakd+ONgmodunrnm7nSLAmRXKVvpXL1/Le0Aw=; b=N 5awxEPKoLqklOzqg/odFBLmaPhPh1Jf927sH/oNdpdko+HHlelOPmRzVx62vDFP+ qZsM+wV+32FxIdM/UD6Ftbrw06Fwfk1E1wR7MRS7E/95qVt/rP3Nhty5ONG6RVtm Pa0EkQ7DB9wnq2FWEX3AXV+yUlHi2mLDwLh62JpELURBAouvVXbn6In9d9k3t9pe DweEX3njnCtGF899P64LtbS2+6kC8YYow4rCxNXFrztuAtLxIWiVZ/vxBMUpGGBB zQ6SxD7sGymtgD/dgwQMclBmJeHpBFAzS6HlHM9M88BAf0wkIlzngvQUaVQbts6c P/A6J3pG4O00TkGnzshBw== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-transfer-encoding :content-type:date:date:feedback-id:feedback-id:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to:x-me-proxy:x-me-sender :x-me-sender:x-sasl-enc; s=fm3; t=1772058014; x=1772144414; bh=O aeDzIOakd+ONgmodunrnm7nSLAmRXKVvpXL1/Le0Aw=; b=pyyjpXbq8lLt77nIR rjgvRi2MRwNW1S6hMlORxKmljR9gM0jgE9S4Fg7OKy+KhrsSdw+cle1MlN7NAknC r8Ha1vuTlrkV9BmuvgYhbvNGWATws45vE9XjQ7LTw+7fX29x8hs+jZGY04QxyYRK zaCtdJ1gbkqXqP/cMCTzZjzaWWU4gP810iZz91yCJruLoq59VvPzfkDiRRorx1EI LA6Za8SXSLlJohGaT1q6ubl19KsL1Nek/hirxYtAuUIti4SavPbRwhAGPv2MwQVJ 6xx2g352AgJZsecZTkcEHUAZn6Yy7IxUFLlAMSnqtTjHAwIN/g50VV8EDXxMHEkE DBrpw== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefgedrtddtgddvgeegfedtucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfurfetoffkrfgpnffqhgenuceu rghilhhouhhtmecufedttdenucenucfjughrpefhvfevufffkffojghfggfgsedtkeertd ertddtnecuhfhrohhmpeflrghvihgvrhcuvfhirgcuoehflhhoshhssehjvghtmhdrmhgv qeenucggtffrrghtthgvrhhnpeeiueehgeevudeiheeuheevgffhtdevheeuffeiieduff ffvdeftdejfefftdekheenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgr ihhlfhhrohhmpehflhhoshhssehjvghtmhdrmhgvpdhnsggprhgtphhtthhopedvpdhmoh guvgepshhmthhpohhuthdprhgtphhtthhopehlihgstggrmhgvrhgrqdguvghvvghlsehl ihhsthhsrdhlihgstggrmhgvrhgrrdhorhhgpdhrtghpthhtohepfhhlohhsshesjhgvth hmrdhmvg X-ME-Proxy: Feedback-ID: i9dde48b3:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Wed, 25 Feb 2026 17:20:14 -0500 (EST) From: Javier Tia To: libcamera-devel@lists.libcamera.org Cc: Javier Tia Subject: [PATCH 1/2] ipa: simple: agc: Replace bang-bang controller with proportional Date: Wed, 25 Feb 2026 16:18:58 -0600 Message-ID: <20260225221859.600869-2-floss@jetm.me> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260225221859.600869-1-floss@jetm.me> References: <20260225221859.600869-1-floss@jetm.me> 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 AGC's updateExposure() uses a fixed ~10% step per frame regardless of how far the current exposure is from optimal. With a hysteresis dead band of only +/-4%, the controller overshoots when the correct value falls within one step, causing visible brightness oscillation (flicker). Replace the fixed-step bang-bang controller with a proportional one where the correction factor scales linearly with the MSV error: factor = 1.0 + clamp(error * 0.04, -0.15, +0.15) At maximum error (~2.5), this gives the same ~10% step as before. Near the target, steps shrink to <1%, eliminating overshoot. The existing hysteresis (kExposureSatisfactory) still prevents hunting on noise. Tested on OV2740 behind Intel IPU6 ISYS (ThinkPad X1 Carbon Gen 10) where the old controller produced continuous brightness flicker. The proportional controller converges in ~3 seconds from cold start with no visible oscillation. Signed-off-by: Javier Tia --- src/ipa/simple/algorithms/agc.cpp | 73 +++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 24 deletions(-) diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp index 2f7e040c..a13a7552 100644 --- a/src/ipa/simple/algorithms/agc.cpp +++ b/src/ipa/simple/algorithms/agc.cpp @@ -7,6 +7,8 @@ #include "agc.h" +#include +#include #include #include @@ -37,52 +39,74 @@ static constexpr float kExposureOptimal = kExposureBinsCount / 2.0; */ static constexpr float kExposureSatisfactory = 0.2; +/* + * Proportional gain for exposure/gain adjustment. Maps the MSV error to a + * multiplicative correction factor: + * + * factor = 1.0 + kExpProportionalGain * error + * + * With kExpProportionalGain = 0.04: + * - max error ~2.5 -> factor 1.10 (~10% step, same as before) + * - error 1.0 -> factor 1.04 (~4% step) + * - error 0.3 -> factor 1.012 (~1.2% step) + * + * This replaces the fixed 10% bang-bang step with a proportional correction + * that converges smoothly and avoids overshooting near the target. + */ +static constexpr float kExpProportionalGain = 0.04; + +/* + * Maximum multiplicative step per frame, to bound the correction when the + * scene changes dramatically. + */ +static constexpr float kExpMaxStep = 0.15; + Agc::Agc() { } void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV) { - /* - * kExpDenominator of 10 gives ~10% increment/decrement; - * kExpDenominator of 5 - about ~20% - */ - static constexpr uint8_t kExpDenominator = 10; - static constexpr uint8_t kExpNumeratorUp = kExpDenominator + 1; - static constexpr uint8_t kExpNumeratorDown = kExpDenominator - 1; - int32_t &exposure = frameContext.sensor.exposure; double &again = frameContext.sensor.gain; - if (exposureMSV < kExposureOptimal - kExposureSatisfactory) { + double error = kExposureOptimal - exposureMSV; + + if (std::abs(error) <= kExposureSatisfactory) + return; + + /* + * Compute a proportional correction factor. The sign of the error + * determines the direction: positive error means too dark (increase), + * negative means too bright (decrease). + */ + float step = std::clamp(static_cast(error) * kExpProportionalGain, + -kExpMaxStep, kExpMaxStep); + float factor = 1.0f + step; + + if (factor > 1.0f) { + /* Scene too dark: increase exposure first, then gain. */ if (exposure < context.configuration.agc.exposureMax) { - int32_t next = exposure * kExpNumeratorUp / kExpDenominator; - if (next - exposure < 1) - exposure += 1; - else - exposure = next; + int32_t next = static_cast(exposure * factor); + exposure = std::max(next, exposure + 1); } else { - double next = again * kExpNumeratorUp / kExpDenominator; + double next = again * factor; if (next - again < context.configuration.agc.againMinStep) again += context.configuration.agc.againMinStep; else again = next; } - } - - if (exposureMSV > kExposureOptimal + kExposureSatisfactory) { + } else { + /* Scene too bright: decrease gain first, then exposure. */ if (again > context.configuration.agc.again10) { - double next = again * kExpNumeratorDown / kExpDenominator; + double next = again * factor; if (again - next < context.configuration.agc.againMinStep) again -= context.configuration.agc.againMinStep; else again = next; } else { - int32_t next = exposure * kExpNumeratorDown / kExpDenominator; - if (exposure - next < 1) - exposure -= 1; - else - exposure = next; + int32_t next = static_cast(exposure * factor); + exposure = std::min(next, exposure - 1); } } @@ -96,6 +120,7 @@ void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou LOG(IPASoftExposure, Debug) << "exposureMSV " << exposureMSV + << " error " << error << " factor " << factor << " exp " << exposure << " again " << again; } From patchwork Wed Feb 25 22:18:59 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Javier Tia X-Patchwork-Id: 26239 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 1F204C0DA4 for ; Wed, 25 Feb 2026 22:20:20 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6CA8D622F2; Wed, 25 Feb 2026 23:20:19 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=jetm.me header.i=@jetm.me header.b="aSldUWDR"; dkim=pass (2048-bit key; unprotected) header.d=messagingengine.com header.i=@messagingengine.com header.b="MOv8txfG"; dkim-atps=neutral Received: from fhigh-a2-smtp.messagingengine.com (fhigh-a2-smtp.messagingengine.com [103.168.172.153]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1432C622E7 for ; Wed, 25 Feb 2026 23:20:16 +0100 (CET) Received: from phl-compute-03.internal (phl-compute-03.internal [10.202.2.43]) by mailfhigh.phl.internal (Postfix) with ESMTP id 63E77140015D; Wed, 25 Feb 2026 17:20:15 -0500 (EST) Received: from phl-frontend-04 ([10.202.2.163]) by phl-compute-03.internal (MEProxy); Wed, 25 Feb 2026 17:20:15 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=jetm.me; h=cc:cc :content-transfer-encoding:content-type:date:date:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to; s=fm2; t=1772058015; x= 1772144415; bh=etawwz8wYX5zyMveCrQVW5v7g9gVbr4YzsYl5Vs+M6A=; b=a SldUWDRyXGYys4B0uEM3MalS3j7asazw5xZ7P5j56ek1zyp8cgu7kjUTxw4F5j7W 78Vks5R7+x86OIdfYvPS44lmLPbi64wtrE5KfecZPETuMY34elwJzetwPCierLCA kAmE1cjlwP+WgNGmUx4RXZ9VC22LBTNf5TXid0KXjXPmlDBEbOdYbwjb+ScSQry0 Wp6BfIuE+vW92CG/RQ64zqcjbPveTmfIe1VfEww9F0JJQXEbTOKm0riKcr2eq2gA 36pO8+0HZQBdLBg90wwRfD9FIwvY7eEyEZU6czv+jhjlkJutjCAIii6RLdINw2i4 P5sQjAQAD5F73ABqtg/mg== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-transfer-encoding :content-type:date:date:feedback-id:feedback-id:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to:x-me-proxy:x-me-sender :x-me-sender:x-sasl-enc; s=fm3; t=1772058015; x=1772144415; bh=e tawwz8wYX5zyMveCrQVW5v7g9gVbr4YzsYl5Vs+M6A=; b=MOv8txfGvVnR65xsR /OGM6MmpylcbR/OB+TnqvhK5cKLZUv65105n/tLzjg+zGX7H+/DnRt+Kpi42MJz+ cCfmNTJmUeOBRfxB6KSss9S5g1oijafpXow5p7ruX0F5k1AyprZ2TeXIESlxFn8S MVMZXKxjWrG+bAsoj7QUmtPp1l5iixedNP7ZyXF0Iq7oKf9dgEeD5oydcKCHs3nN Gzh5HHFoTHH7H1C4xfbipnJmcDtmwGdqjWlXaof2dEGG/HXCzfyIdmsO9nhySPso f7BcM0TXk5LaR/28LDTE4DMJMw6B4+/5E5aFSropWOW9QfCBBemF6nErRJV3VYs9 ywXiQ== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefgedrtddtgddvgeegvdelucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfurfetoffkrfgpnffqhgenuceu rghilhhouhhtmecufedttdenucenucfjughrpefhvfevufffkffojghfggfgsedtkeertd ertddtnecuhfhrohhmpeflrghvihgvrhcuvfhirgcuoehflhhoshhssehjvghtmhdrmhgv qeenucggtffrrghtthgvrhhnpeeiueehgeevudeiheeuheevgffhtdevheeuffeiieduff ffvdeftdejfefftdekheenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgr ihhlfhhrohhmpehflhhoshhssehjvghtmhdrmhgvpdhnsggprhgtphhtthhopedvpdhmoh guvgepshhmthhpohhuthdprhgtphhtthhopehlihgstggrmhgvrhgrqdguvghvvghlsehl ihhsthhsrdhlihgstggrmhgvrhgrrdhorhhgpdhrtghpthhtohepfhhlohhsshesjhgvth hmrdhmvg X-ME-Proxy: Feedback-ID: i9dde48b3:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Wed, 25 Feb 2026 17:20:14 -0500 (EST) From: Javier Tia To: libcamera-devel@lists.libcamera.org Cc: Javier Tia Subject: [PATCH 2/2] ipa: simple: data: Add OV2740 tuning file Date: Wed, 25 Feb 2026 16:18:59 -0600 Message-ID: <20260225221859.600869-3-floss@jetm.me> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260225221859.600869-1-floss@jetm.me> References: <20260225221859.600869-1-floss@jetm.me> 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" Add a sensor-specific tuning file for the OmniVision OV2740, commonly found in Intel IPU6 laptops (ThinkPad X1 Carbon, XPS, Surface). The tuning enables all Simple IPA algorithms including AGC, AWB, black level correction, and a color correction matrix calibrated for 6500K. The CCM compensates for the green bias inherent in Bayer demosaicing (the GRBG pattern has 2x green pixels vs red/blue). Without this file, libcamera falls back to uncalibrated.yaml which produces a heavy green tint on the OV2740. Signed-off-by: Javier Tia --- src/ipa/simple/data/meson.build | 1 + src/ipa/simple/data/ov2740.yaml | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 src/ipa/simple/data/ov2740.yaml diff --git a/src/ipa/simple/data/meson.build b/src/ipa/simple/data/meson.build index 92795ee4..e3e4de74 100644 --- a/src/ipa/simple/data/meson.build +++ b/src/ipa/simple/data/meson.build @@ -1,6 +1,7 @@ # SPDX-License-Identifier: CC0-1.0 conf_files = files([ + 'ov2740.yaml', 'uncalibrated.yaml', ]) diff --git a/src/ipa/simple/data/ov2740.yaml b/src/ipa/simple/data/ov2740.yaml new file mode 100644 index 00000000..b48d50ce --- /dev/null +++ b/src/ipa/simple/data/ov2740.yaml @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: CC0-1.0 +# +# OmniVision OV2740 tuning for libcamera Simple/SoftISP pipeline. +# +# Tested on ThinkPad X1 Carbon Gen 10 (Alder Lake) with Intel IPU6 ISYS. +# The CCM corrects the green bias inherent in Bayer demosaicing (2:1 green +# to red/blue pixel ratio in the CFA). Values were iteratively calibrated +# from captured frame measurements under 6500K lighting. +%YAML 1.1 +--- +version: 1 +algorithms: + - BlackLevel: + - Awb: + - Ccm: + ccms: + - ct: 6500 + ccm: [ 2.49, -0.91, -0.26, + -0.30, 1.20, 0.10, + 0.07, -0.80, 2.19 ] + - Adjust: + - Agc: