{"id":19887,"url":"https://patchwork.libcamera.org/api/1.1/patches/19887/?format=json","web_url":"https://patchwork.libcamera.org/patch/19887/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/1.1/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20240416091357.211951-19-mzamazal@redhat.com>","date":"2024-04-16T09:13:54","name":"[v8,18/18] libcamera: software_isp: Apply black level compensation","commit_ref":null,"pull_url":null,"state":"accepted","archived":false,"hash":"356a39db21123b1fd9bc4bba42677d1398cc8078","submitter":{"id":177,"url":"https://patchwork.libcamera.org/api/1.1/people/177/?format=json","name":"Milan Zamazal","email":"mzamazal@redhat.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/19887/mbox/","series":[{"id":4257,"url":"https://patchwork.libcamera.org/api/1.1/series/4257/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=4257","date":"2024-04-16T09:13:36","name":"libcamera: introduce Software ISP and Software IPA","version":8,"mbox":"https://patchwork.libcamera.org/series/4257/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/19887/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/19887/checks/","tags":{},"headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id C7440BE08B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 16 Apr 2024 09:15:21 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 6FBC263374;\n\tTue, 16 Apr 2024 11:15:21 +0200 (CEST)","from us-smtp-delivery-124.mimecast.com\n\t(us-smtp-delivery-124.mimecast.com [170.10.129.124])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 1DD886337D\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 16 Apr 2024 11:15:13 +0200 (CEST)","from mimecast-mx02.redhat.com (mimecast-mx02.redhat.com\n\t[66.187.233.88]) by relay.mimecast.com with ESMTP with STARTTLS\n\t(version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id\n\tus-mta-31-VNGbn4dXM76SN_7BkKC6fw-1; Tue, 16 Apr 2024 05:15:08 -0400","from smtp.corp.redhat.com\n\t(int-mx04.intmail.prod.int.rdu2.redhat.com [10.11.54.4])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\tkey-exchange X25519 server-signature RSA-PSS (2048 bits)\n\tserver-digest SHA256) (No client certificate requested)\n\tby mimecast-mx02.redhat.com (Postfix) with ESMTPS id 102051887315;\n\tTue, 16 Apr 2024 09:15:08 +0000 (UTC)","from nuthatch.redhat.com (unknown [10.45.225.245])\n\tby smtp.corp.redhat.com (Postfix) with ESMTP id 1648D2026962;\n\tTue, 16 Apr 2024 09:15:05 +0000 (UTC)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=redhat.com header.i=@redhat.com\n\theader.b=\"hMCIxMuJ\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1713258912;\n\th=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n\tto:to:cc:cc:mime-version:mime-version:content-type:content-type:\n\tcontent-transfer-encoding:content-transfer-encoding:\n\tin-reply-to:in-reply-to:references:references;\n\tbh=Vdz+bKWhirCnqZVWAHICZCW+R9+K9NC5I5u8tdm20/U=;\n\tb=hMCIxMuJsBQUmGRSSHBVDRxNPfm63AThY8BWiPzjCGgFxXJ5nKzyOFBGzeYVpbq4DnMX8H\n\tm9Eu2SZD+tpf7luZGBkSxdxO41UOvtD4zn/z5tVPC8SeuWodmkyoHRwDnhCUzKgnuB83+n\n\t827oR4EwzzdM8ZxwKwRxvWXtBHop0Cw=","X-MC-Unique":"VNGbn4dXM76SN_7BkKC6fw-1","From":"Milan Zamazal <mzamazal@redhat.com>","To":"libcamera-devel@lists.libcamera.org","Cc":"Milan Zamazal <mzamazal@redhat.com>,\n\tAndrei Konovalov <andrey.konovalov.ynk@gmail.com>,\n\tBryan O'Donoghue <bryan.odonoghue@linaro.org>,\n\tMaxime Ripard <mripard@redhat.com>, Pavel Machek <pavel@ucw.cz>,\n\tHans de Goede <hdegoede@redhat.com>,\n\tKieran Bingham <kieran.bingham@ideasonboard.com>,\n\tLaurent Pinchart <laurent.pinchart@ideasonboard.com>","Subject":"[PATCH v8 18/18] libcamera: software_isp: Apply black level\n\tcompensation","Date":"Tue, 16 Apr 2024 11:13:54 +0200","Message-ID":"<20240416091357.211951-19-mzamazal@redhat.com>","In-Reply-To":"<20240416091357.211951-1-mzamazal@redhat.com>","References":"<20240416091357.211951-1-mzamazal@redhat.com>","MIME-Version":"1.0","X-Scanned-By":"MIMEDefang 3.4.1 on 10.11.54.4","X-Mimecast-Spam-Score":"0","X-Mimecast-Originator":"redhat.com","Content-Transfer-Encoding":"8bit","Content-Type":"text/plain; charset=\"US-ASCII\"; x-default=true","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"},"content":"Black may not be represented as 0 pixel value for given hardware, it may\nbe higher.  If this is not compensated then various problems may occur\nsuch as low contrast or suboptimal exposure.\n\nThe black pixel value can be either retrieved from a tuning file for the\ngiven hardware, or automatically on the fly.  The former is the right\nand correct method, while the latter can be used when a tuning file is\nnot available for the given hardware.  Since there is currently no\nsupport for tuning files in software ISP, the automatic, hardware\nindependent way, is always used.  Support for tuning files should be\nadded in future but it will require more work than this patch.\n\nThe patch looks at the image histogram and assumes that black starts\nwhen pixel values start occurring on the left.  A certain amount of the\ndarkest pixels is ignored; it doesn't matter whether they represent\nvarious kinds of noise or are real, they are better to omit in any case\nto make the image looking better.  It also doesn't matter whether the\ndarkest pixels occur around the supposed black level or are spread\nbetween 0 and the black level, the difference is not important.\n\nAn arbitrary threshold of 2% darkest pixels is applied; there is no\nmagic about that value.\n\nThe patch assumes that the black values for different colors are the\nsame and doesn't attempt any other non-primitive enhancements.  It\ncannot completely replace tuning files and simplicity, while providing\nvisible benefit, is its goal.  Anything more sophisticated is left for\nfuture patches.\n\nA possible cheap enhancement, if needed, could be setting exposure +\ngain to minimum values temporarily, before setting the black level.  In\ntheory, the black level should be fixed but it may not be reached in all\nimages.  For this reason, the patch updates black level only if the\nobserved value is lower than the current one; it should be never\nincreased.\n\nThe purpose of the patch is to compensate for hardware properties.\nGeneral image contrast enhancements are out of scope of this patch.\n\nStats are still gathered as an uncorrected histogram, to avoid any\nconfusion and to represent the raw image data.  Exposure must be\ndetermined after the black level correction -- it has no influence on\nthe sub-black area and must be correct after applying the black level\ncorrection.  The granularity of the histogram is increased from 16 to 64\nto provide a better precision (there is no theory behind either of those\nnumbers).\n\nReviewed-by: Hans de Goede <hdegoede@redhat.com>\nSigned-off-by: Milan Zamazal <mzamazal@redhat.com>\nSigned-off-by: Hans de Goede <hdegoede@redhat.com>\nSigned-off-by: Milan Zamazal <mzamazal@redhat.com>\n---\n .../internal/software_isp/debayer_params.h    |  4 +\n .../internal/software_isp/swisp_stats.h       |  8 +-\n src/ipa/simple/black_level.cpp                | 88 +++++++++++++++++++\n src/ipa/simple/black_level.h                  | 28 ++++++\n src/ipa/simple/meson.build                    |  7 +-\n src/ipa/simple/soft_simple.cpp                | 30 ++++---\n src/libcamera/software_isp/TODO               | 10 +++\n src/libcamera/software_isp/debayer_cpu.cpp    | 13 ++-\n src/libcamera/software_isp/debayer_cpu.h      |  1 +\n src/libcamera/software_isp/software_isp.cpp   |  2 +-\n 10 files changed, 173 insertions(+), 18 deletions(-)\n create mode 100644 src/ipa/simple/black_level.cpp\n create mode 100644 src/ipa/simple/black_level.h","diff":"diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h\nindex c818ca3a..32cd448a 100644\n--- a/include/libcamera/internal/software_isp/debayer_params.h\n+++ b/include/libcamera/internal/software_isp/debayer_params.h\n@@ -20,6 +20,10 @@ struct DebayerParams {\n \tunsigned int gainB;\n \n \tfloat gamma;\n+\t/**\n+\t * \\brief Level of the black point, 0..255, 0 is no correction.\n+\t */\n+\tunsigned int blackLevel;\n };\n \n } /* namespace libcamera */\ndiff --git a/include/libcamera/internal/software_isp/swisp_stats.h b/include/libcamera/internal/software_isp/swisp_stats.h\nindex dd5e207d..4ca8d647 100644\n--- a/include/libcamera/internal/software_isp/swisp_stats.h\n+++ b/include/libcamera/internal/software_isp/swisp_stats.h\n@@ -35,11 +35,15 @@ struct SwIspStats {\n \t/**\n \t * \\brief Number of bins in the yHistogram\n \t */\n-\tstatic constexpr unsigned int kYHistogramSize = 16;\n+\tstatic constexpr unsigned int kYHistogramSize = 64;\n+\t/**\n+\t * \\brief Type of the histogram.\n+\t */\n+\tusing Histogram = std::array<uint32_t, kYHistogramSize>;\n \t/**\n \t * \\brief A histogram of luminance values\n \t */\n-\tstd::array<uint32_t, kYHistogramSize> yHistogram;\n+\tHistogram yHistogram;\n };\n \n } /* namespace libcamera */\ndiff --git a/src/ipa/simple/black_level.cpp b/src/ipa/simple/black_level.cpp\nnew file mode 100644\nindex 00000000..c7e8d8b7\n--- /dev/null\n+++ b/src/ipa/simple/black_level.cpp\n@@ -0,0 +1,88 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2024, Red Hat Inc.\n+ *\n+ * black_level.cpp - black level handling\n+ */\n+\n+#include \"black_level.h\"\n+\n+#include <numeric>\n+\n+#include <libcamera/base/log.h>\n+\n+namespace libcamera {\n+\n+LOG_DEFINE_CATEGORY(IPASoftBL)\n+\n+/**\n+ * \\class BlackLevel\n+ * \\brief Object providing black point level for software ISP\n+ *\n+ * Black level can be provided in hardware tuning files or, if no tuning file is\n+ * available for the given hardware, guessed automatically, with less accuracy.\n+ * As tuning files are not yet implemented for software ISP, BlackLevel\n+ * currently provides only guessed black levels.\n+ *\n+ * This class serves for tracking black level as a property of the underlying\n+ * hardware, not as means of enhancing a particular scene or image.\n+ *\n+ * The class is supposed to be instantiated for the given camera stream.\n+ * The black level can be retrieved using BlackLevel::get() method. It is\n+ * initially 0 and may change when updated using BlackLevel::update() method.\n+ */\n+\n+BlackLevel::BlackLevel()\n+\t: blackLevel_(255), blackLevelSet_(false)\n+{\n+}\n+\n+/**\n+ * \\brief Return the current black level\n+ *\n+ * \\return The black level, in the range from 0 (minimum) to 255 (maximum).\n+ * If the black level couldn't be determined yet, return 0.\n+ */\n+unsigned int BlackLevel::get() const\n+{\n+\treturn blackLevelSet_ ? blackLevel_ : 0;\n+}\n+\n+/**\n+ * \\brief Update black level from the provided histogram\n+ * \\param[in] yHistogram The histogram to be used for updating black level\n+ *\n+ * The black level is property of the given hardware, not image. It is updated\n+ * only if it has not been yet set or if it is lower than the lowest value seen\n+ * so far.\n+ */\n+void BlackLevel::update(SwIspStats::Histogram &yHistogram)\n+{\n+\t/*\n+\t * The constant is selected to be \"good enough\", not overly conservative or\n+\t * aggressive. There is no magic about the given value.\n+\t */\n+\tconstexpr float ignoredPercentage_ = 0.02;\n+\tconst unsigned int total =\n+\t\tstd::accumulate(begin(yHistogram), end(yHistogram), 0);\n+\tconst unsigned int pixelThreshold = ignoredPercentage_ * total;\n+\tconst unsigned int histogramRatio = 256 / SwIspStats::kYHistogramSize;\n+\tconst unsigned int currentBlackIdx = blackLevel_ / histogramRatio;\n+\n+\tfor (unsigned int i = 0, seen = 0;\n+\t     i < currentBlackIdx && i < SwIspStats::kYHistogramSize;\n+\t     i++) {\n+\t\tseen += yHistogram[i];\n+\t\tif (seen >= pixelThreshold) {\n+\t\t\tblackLevel_ = i * histogramRatio;\n+\t\t\tblackLevelSet_ = true;\n+\t\t\tLOG(IPASoftBL, Debug)\n+\t\t\t\t<< \"Auto-set black level: \"\n+\t\t\t\t<< i << \"/\" << SwIspStats::kYHistogramSize\n+\t\t\t\t<< \" (\" << 100 * (seen - yHistogram[i]) / total << \"% below, \"\n+\t\t\t\t<< 100 * seen / total << \"% at or below)\";\n+\t\t\tbreak;\n+\t\t}\n+\t};\n+}\n+} /* namespace libcamera */\ndiff --git a/src/ipa/simple/black_level.h b/src/ipa/simple/black_level.h\nnew file mode 100644\nindex 00000000..7e37757e\n--- /dev/null\n+++ b/src/ipa/simple/black_level.h\n@@ -0,0 +1,28 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2024, Red Hat Inc.\n+ *\n+ * black_level.h - black level handling\n+ */\n+\n+#pragma once\n+\n+#include <array>\n+\n+#include \"libcamera/internal/software_isp/swisp_stats.h\"\n+\n+namespace libcamera {\n+\n+class BlackLevel\n+{\n+public:\n+\tBlackLevel();\n+\tunsigned int get() const;\n+\tvoid update(SwIspStats::Histogram &yHistogram);\n+\n+private:\n+\tunsigned int blackLevel_;\n+\tbool blackLevelSet_;\n+};\n+\n+} /* namespace libcamera */\ndiff --git a/src/ipa/simple/meson.build b/src/ipa/simple/meson.build\nindex 3e863db7..44b5f1d7 100644\n--- a/src/ipa/simple/meson.build\n+++ b/src/ipa/simple/meson.build\n@@ -2,8 +2,13 @@\n \n ipa_name = 'ipa_soft_simple'\n \n+soft_simple_sources = files([\n+    'soft_simple.cpp',\n+    'black_level.cpp',\n+])\n+\n mod = shared_module(ipa_name,\n-                    ['soft_simple.cpp', libcamera_generated_ipa_headers],\n+                    [soft_simple_sources, libcamera_generated_ipa_headers],\n                     name_prefix : '',\n                     include_directories : [ipa_includes, libipa_includes],\n                     dependencies : libcamera_private,\ndiff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp\nindex ff1d8e0c..b9fb58b5 100644\n--- a/src/ipa/simple/soft_simple.cpp\n+++ b/src/ipa/simple/soft_simple.cpp\n@@ -26,8 +26,9 @@\n \n #include \"libipa/camera_sensor_helper.h\"\n \n-namespace libcamera {\n+#include \"black_level.h\"\n \n+namespace libcamera {\n LOG_DEFINE_CATEGORY(IPASoft)\n \n namespace ipa::soft {\n@@ -54,7 +55,8 @@ class IPASoftSimple : public ipa::soft::IPASoftInterface\n {\n public:\n \tIPASoftSimple()\n-\t\t: params_(nullptr), stats_(nullptr), ignoreUpdates_(0)\n+\t\t: params_(nullptr), stats_(nullptr), blackLevel_(BlackLevel()),\n+\t\t  ignoreUpdates_(0)\n \t{\n \t}\n \n@@ -78,6 +80,7 @@ private:\n \tSwIspStats *stats_;\n \tstd::unique_ptr<CameraSensorHelper> camHelper_;\n \tControlInfoMap sensorInfoMap_;\n+\tBlackLevel blackLevel_;\n \n \tint32_t exposureMin_, exposureMax_;\n \tint32_t exposure_;\n@@ -255,6 +258,10 @@ void IPASoftSimple::processStats(const ControlList &sensorControls)\n \tparams_->gainG = 256;\n \tparams_->gamma = 0.5;\n \n+\tif (ignoreUpdates_ > 0)\n+\t\tblackLevel_.update(stats_->yHistogram);\n+\tparams_->blackLevel = blackLevel_.get();\n+\n \tsetIspParams.emit();\n \n \t/* \\todo Switch to the libipa/algorithm.h API someday. */\n@@ -273,18 +280,20 @@ void IPASoftSimple::processStats(const ControlList &sensorControls)\n \t * Calculate Mean Sample Value (MSV) according to formula from:\n \t * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf\n \t */\n-\tconstexpr unsigned int yHistValsPerBin =\n-\t\tSwIspStats::kYHistogramSize / kExposureBinsCount;\n-\tconstexpr unsigned int yHistValsPerBinMod =\n-\t\tSwIspStats::kYHistogramSize /\n-\t\t(SwIspStats::kYHistogramSize % kExposureBinsCount + 1);\n+\tconst unsigned int blackLevelHistIdx =\n+\t\tparams_->blackLevel / (256 / SwIspStats::kYHistogramSize);\n+\tconst unsigned int histogramSize =\n+\t\tSwIspStats::kYHistogramSize - blackLevelHistIdx;\n+\tconst unsigned int yHistValsPerBin = histogramSize / kExposureBinsCount;\n+\tconst unsigned int yHistValsPerBinMod =\n+\t\thistogramSize / (histogramSize % kExposureBinsCount + 1);\n \tint exposureBins[kExposureBinsCount] = {};\n \tunsigned int denom = 0;\n \tunsigned int num = 0;\n \n-\tfor (unsigned int i = 0; i < SwIspStats::kYHistogramSize; i++) {\n+\tfor (unsigned int i = 0; i < histogramSize; i++) {\n \t\tunsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin;\n-\t\texposureBins[idx] += stats_->yHistogram[i];\n+\t\texposureBins[idx] += stats_->yHistogram[blackLevelHistIdx + i];\n \t}\n \n \tfor (unsigned int i = 0; i < kExposureBinsCount; i++) {\n@@ -320,7 +329,8 @@ void IPASoftSimple::processStats(const ControlList &sensorControls)\n \n \tLOG(IPASoft, Debug) << \"exposureMSV \" << exposureMSV\n \t\t\t    << \" exp \" << exposure_ << \" again \" << again_\n-\t\t\t    << \" gain R/B \" << params_->gainR << \"/\" << params_->gainB;\n+\t\t\t    << \" gain R/B \" << params_->gainR << \"/\" << params_->gainB\n+\t\t\t    << \" black level \" << params_->blackLevel;\n }\n \n void IPASoftSimple::updateExposure(double exposureMSV)\ndiff --git a/src/libcamera/software_isp/TODO b/src/libcamera/software_isp/TODO\nindex fcb02588..4fcee39b 100644\n--- a/src/libcamera/software_isp/TODO\n+++ b/src/libcamera/software_isp/TODO\n@@ -267,3 +267,13 @@ This could be handled better with DelayedControls.\n > \t\t\t\t\t\t    V4L2_CID_EXPOSURE }));\n \n You should use the DelayedControls class.\n+\n+---\n+\n+13. Improve black level and colour gains application\n+\n+I think the black level should eventually be moved before debayering, and\n+ideally the colour gains as well. I understand the need for optimizations to\n+lower the CPU consumption, but at the same time I don't feel comfortable\n+building up on top of an implementation that may work a bit more by chance than\n+by correctness, as that's not very maintainable.\ndiff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp\nindex 5b553162..88d6578b 100644\n--- a/src/libcamera/software_isp/debayer_cpu.cpp\n+++ b/src/libcamera/software_isp/debayer_cpu.cpp\n@@ -35,7 +35,7 @@ namespace libcamera {\n  * \\param[in] stats Pointer to the stats object to use\n  */\n DebayerCpu::DebayerCpu(std::unique_ptr<SwStatsCpu> stats)\n-\t: stats_(std::move(stats)), gammaCorrection_(1.0)\n+\t: stats_(std::move(stats)), gammaCorrection_(1.0), blackLevel_(0)\n {\n \t/*\n \t * Reading from uncached buffers may be very slow.\n@@ -699,11 +699,16 @@ void DebayerCpu::process(FrameBuffer *input, FrameBuffer *output, DebayerParams\n \t}\n \n \t/* Apply DebayerParams */\n-\tif (params.gamma != gammaCorrection_) {\n-\t\tfor (unsigned int i = 0; i < kGammaLookupSize; i++)\n-\t\t\tgamma_[i] = UINT8_MAX * powf(i / (kGammaLookupSize - 1.0), params.gamma);\n+\tif (params.gamma != gammaCorrection_ || params.blackLevel != blackLevel_) {\n+\t\tconst unsigned int blackIndex =\n+\t\t\tparams.blackLevel * kGammaLookupSize / 256;\n+\t\tstd::fill(gamma_.begin(), gamma_.begin() + blackIndex, 0);\n+\t\tconst float divisor = kGammaLookupSize - blackIndex - 1.0;\n+\t\tfor (unsigned int i = blackIndex; i < kGammaLookupSize; i++)\n+\t\t\tgamma_[i] = UINT8_MAX * powf((i - blackIndex) / divisor, params.gamma);\n \n \t\tgammaCorrection_ = params.gamma;\n+\t\tblackLevel_ = params.blackLevel;\n \t}\n \n \tif (swapRedBlueGains_)\ndiff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h\nindex e7a8ba74..689c1075 100644\n--- a/src/libcamera/software_isp/debayer_cpu.h\n+++ b/src/libcamera/software_isp/debayer_cpu.h\n@@ -147,6 +147,7 @@ private:\n \tbool enableInputMemcpy_;\n \tbool swapRedBlueGains_;\n \tfloat gammaCorrection_;\n+\tunsigned int blackLevel_;\n \tunsigned int measuredFrames_;\n \tint64_t frameProcessTime_;\n \t/* Skip 30 frames for things to stabilize then measure 30 frames */\ndiff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp\nindex d746d893..e4e56086 100644\n--- a/src/libcamera/software_isp/software_isp.cpp\n+++ b/src/libcamera/software_isp/software_isp.cpp\n@@ -64,7 +64,7 @@ LOG_DEFINE_CATEGORY(SoftwareIsp)\n  */\n SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor)\n \t: debayerParams_{ DebayerParams::kGain10, DebayerParams::kGain10,\n-\t\t\t  DebayerParams::kGain10, 0.5f },\n+\t\t\t  DebayerParams::kGain10, 0.5f, 0 },\n \t  dmaHeap_(DmaHeap::DmaHeapFlag::Cma | DmaHeap::DmaHeapFlag::System)\n {\n \tif (!dmaHeap_.isValid()) {\n","prefixes":["v8","18/18"]}