From patchwork Mon Aug 24 09:06:26 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 28020 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 7085DC3339 for ; Mon, 24 Aug 2026 09:07:27 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2E785683B2; Mon, 24 Aug 2026 11:07:27 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="rLlRyXpV"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C22B867FEC for ; Mon, 24 Aug 2026 11:07:25 +0200 (CEST) Received: from neptunite.hamster-moth.ts.net (unknown [IPv6:2400:2411:160:2f00:e616:f172:fe90:f1b5]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 6C9BDC31; Mon, 24 Aug 2026 11:05:58 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1787562361; bh=H2qLjTk1QDZE1mPMgumstaTmrps3NMDIHuuUl/2jFVE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=rLlRyXpVdhTMq4w3UITm3DAJYSwPymZXWJJfI3OPEApZ2ypMAQfC6UmeRsOdEUppb bn1R6MTfBH/n7cbpH3GW9fdPbry9q8a2c8/maKfpXIWeegykzBZNtZ5RC0NP+S6R7B TAYQFt+3Pp1GXlgQDWsGOFZmPrb8PqB+dffFXNBk= From: Paul Elder To: laurent.pinchart@ideasonboard.com Cc: Paul Elder , michael.riesch@collabora.com, xuhf@rock-chips.com, stefan.klug@ideasonboard.com, kieran.bingham@ideasonboard.com, dan.scally@ideasonboard.com, jacopo.mondi@ideasonboard.com, nicolas.dufresne@collabora.com, mehdi.djait@linux.intel.com, libcamera-devel@lists.libcamera.org Subject: [PATCH v2 09/20] ipa: rkisp2: algo: awb: Implement automatic white balance control Date: Mon, 24 Aug 2026 18:06:26 +0900 Message-ID: <20260824090641.3705246-10-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20260824090641.3705246-1-paul.elder@ideasonboard.com> References: <20260824090641.3705246-1-paul.elder@ideasonboard.com> 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" Implement auto white balance control algorithm for the rkisp2 IPA. This leverages the libipa awb, so it supports baysian awb, grey world awb, as well as manual white balance. Signed-off-by: Paul Elder --- Changes in v2: - rebased on libipa rework - use extensible stats --- src/ipa/rkisp2/algorithms/awb.cpp | 211 ++++++++++++++++++++++++++ src/ipa/rkisp2/algorithms/awb.h | 56 +++++++ src/ipa/rkisp2/algorithms/meson.build | 1 + 3 files changed, 268 insertions(+) create mode 100644 src/ipa/rkisp2/algorithms/awb.cpp create mode 100644 src/ipa/rkisp2/algorithms/awb.h diff --git a/src/ipa/rkisp2/algorithms/awb.cpp b/src/ipa/rkisp2/algorithms/awb.cpp new file mode 100644 index 000000000000..e3b284a2d79f --- /dev/null +++ b/src/ipa/rkisp2/algorithms/awb.cpp @@ -0,0 +1,211 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * AWB control algorithm + */ + +#include "awb.h" + +#include + +#include + +#include + +#include "libcamera/internal/vector.h" + +/** + * \file awb.h + */ + +namespace libcamera { + +namespace ipa::rkisp2::algorithms { + +LOG_DEFINE_CATEGORY(RkISP2Awb) + +class RkISP2AwbStats final : public AwbStats +{ +public: + RkISP2AwbStats() = default; + RkISP2AwbStats(const RGB means) + { + rgbMeans_[0] = means.r(); + rgbMeans_[1] = means.g(); + rgbMeans_[2] = means.b(); + + rg_ = rgbMeans_.r(); + bg_ = rgbMeans_.b(); + } + + double computeColourError(const RGB &gains) const override + { + /* + * Compute the sum of the squared colour error (non-greyness) as + * it appears in the log likelihood equation. + */ + double deltaR = gains.r() * rg_ - 1.0; + double deltaB = gains.b() * bg_ - 1.0; + double delta2 = deltaR * deltaR + deltaB * deltaB; + + return delta2; + } + + RGB rgbMeans() const override + { + return rgbMeans_; + } + + bool valid() const override + { + /* Minimum mean value below which AWB can't operate. */ + constexpr double minValue = 0.2; + + return rgbMeans_.r() > minValue || rgbMeans_.g() > minValue || + rgbMeans_.b() > minValue; + } + +private: + RGB rgbMeans_; + double rg_; + double bg_; +}; + +namespace { + +} /* namespace */ + +/** + * \class Awb + * \brief Manage the white balance with automatic and manual controls + */ + +Awb::Awb() +{ +} + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Awb::init(IPAContext &context, const ValueNode &tuningData) +{ + return awbAlgo_.init(tuningData, context.ctrlMap); +} + +int Awb::configure(IPAContext &context, + const IPACameraSensorInfo &configInfo) +{ + awbAlgo_.configure(context.activeState.awb); + + context.configuration.awb.measureWindow.h_offs = 0; + context.configuration.awb.measureWindow.v_offs = 0; + /* + * Unlike ae lite, this seems to still work when height == full window + * height + */ + context.configuration.awb.measureWindow.h_size = configInfo.outputSize.width / 15; + context.configuration.awb.measureWindow.v_size = configInfo.outputSize.height / 15; + + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void Awb::queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) +{ + awbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb, + controls); +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void Awb::prepare([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, RkISP2Params *params) +{ + awbAlgo_.prepare(context.activeState.awb, frameContext.awb); + + auto gainConfig = params->block(); + gainConfig.setEnabled(true); + + RGB gains = frameContext.awb.gains; + + /* \todo Use quantized class */ + gainConfig->gains[0].gb = std::clamp(256 * 1.0, 0, 0x3fff); + gainConfig->gains[0].b = std::clamp(256 * gains.b(), 0, 0x3fff); + gainConfig->gains[0].r = std::clamp(256 * gains.r(), 0, 0x3fff); + gainConfig->gains[0].gr = std::clamp(256 * 1.0, 0, 0x3fff); + + auto measConfig = params->block(); + measConfig.setEnabled(true); + + measConfig->meas_window = context.configuration.awb.measureWindow; + struct rkisp2_isp_awb_color_quad minLimits = { 0, 0, 0, 0 }; + struct rkisp2_isp_awb_color_quad maxLimits = { 255, 255, 255, 255 }; + measConfig->limits[0] = minLimits; + measConfig->limits[1] = maxLimits; + + for (unsigned int i = 0; i < RKISP2_ISP_AWB_COUNTS_SIZE; i++) + measConfig->weights[i] = 0x20; +} + +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void Awb::process([[maybe_unused]] IPAContext &context, + const uint32_t frame, + IPAFrameContext &frameContext, + const RkISP2Stats *stats, + ControlList &metadata) +{ + /* The first frame has no stats so skip processing */ + if (frame < 1) + return; + + RkISP2AwbStats awbStats = calculateRgbMeans(frameContext, stats); + + awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats, + frameContext.lux.lux, metadata); +} + +RkISP2AwbStats Awb::calculateRgbMeans([[maybe_unused]] const IPAFrameContext &frameContext, + const RkISP2Stats *stats) const +{ + auto awb = stats->block(); + + if (!awb->done) { + LOG(RkISP2Awb, Error) << "No awb stats"; + return {}; + } + + std::array counts; + RGB means; + + for (size_t i = 0; i < RKISP2_ISP_AWB_COUNTS_SIZE; i++) + counts[i] = static_cast(awb->counts_r[i]); + means.r() = std::accumulate(counts.begin(), counts.end(), 0) / counts.size(); + + for (size_t i = 0; i < RKISP2_ISP_AWB_COUNTS_SIZE; i++) + counts[i] = static_cast(awb->counts_g[i]); + means.g() = std::accumulate(counts.begin(), counts.end(), 0) / counts.size(); + + for (size_t i = 0; i < RKISP2_ISP_AWB_COUNTS_SIZE; i++) + counts[i] = static_cast(awb->counts_b[i]); + means.b() = std::accumulate(counts.begin(), counts.end(), 0) / counts.size(); + + for (size_t i = 0; i < RKISP2_ISP_AWB_COUNTS_SIZE; i++) + counts[i] = static_cast(awb->counts_w[i]); + + return RkISP2AwbStats(means); +} + +REGISTER_IPA_ALGORITHM(Awb, "Awb") + +} /* namespace ipa::rkisp2::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rkisp2/algorithms/awb.h b/src/ipa/rkisp2/algorithms/awb.h new file mode 100644 index 000000000000..42c9f1193795 --- /dev/null +++ b/src/ipa/rkisp2/algorithms/awb.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * AWB control algorithm + */ + +#pragma once + +#include + +#include + +#include "libcamera/internal/value_node.h" + +#include "libipa/awb.h" +#include "libipa/fixedpoint.h" + +#include "algorithm.h" +#include "ipa_context.h" +#include "params.h" + +namespace libcamera { + +namespace ipa::rkisp2::algorithms { + +class RkISP2AwbStats; + +class Awb : public Algorithm +{ +public: + Awb(); + ~Awb() = default; + + int init(IPAContext &context, const ValueNode &tuningData) override; + int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; + void queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + RkISP2Params *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const RkISP2Stats *stats, + ControlList &metadata) override; + +private: + RkISP2AwbStats calculateRgbMeans(const IPAFrameContext &frameContext, + const RkISP2Stats *stats) const; + + AwbAlgorithm> awbAlgo_; +}; + +} /* namespace ipa::rkisp2::algorithms */ +} /* namespace libcamera */ diff --git a/src/ipa/rkisp2/algorithms/meson.build b/src/ipa/rkisp2/algorithms/meson.build index 36996918b39e..30133ac4fa68 100644 --- a/src/ipa/rkisp2/algorithms/meson.build +++ b/src/ipa/rkisp2/algorithms/meson.build @@ -1,6 +1,7 @@ # SPDX-License-Identifier: CC0-1.0 rkisp2_ipa_algorithms = files([ + 'awb.cpp', 'bls.cpp', ])