about summary refs log tree commit diff
path: root/users/flokli/ipu6-softisp/libcamera/0018-libcamera-software_isp-Apply-black-level-compensatio.patch
blob: c746b74dba674725ec307b97053c44318a360dd7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
From bb608d177135d74e3c98b8a61fb459ebe254bca5 Mon Sep 17 00:00:00 2001
From: Milan Zamazal <mzamazal@redhat.com>
Date: Mon, 11 Mar 2024 15:15:21 +0100
Subject: [PATCH 18/21] libcamera: software_isp: Apply black level compensation

Black may not be represented as 0 pixel value for given hardware, it may be
higher.  If this is not compensated then various problems may occur such as low
contrast or suboptimal exposure.

The black pixel value can be either retrieved from a tuning file for the given
hardware, or automatically on fly.  The former is the right and correct method,
while the latter can be used when a tuning file is not available for the given
hardware.  Since there is currently no support for tuning files in software ISP,
the automatic, hardware independent way, is always used.  Support for tuning
files should be added in future but it will require more work than this patch.

The patch looks at the image histogram and assumes that black starts when pixel
values start occurring on the left.  A certain amount of the darkest pixels is
ignored; it doesn't matter whether they represent various kinds of noise or are
real, they are better to omit in any case to make the image looking better.  It
also doesn't matter whether the darkest pixels occur around the supposed black
level or are spread between 0 and the black level, the difference is not
important.

An arbitrary threshold of 2% darkest pixels is applied; there is no magic about
that value.

The patch assumes that the black values for different colors are the same and
doesn't attempt any other non-primitive enhancements.  It cannot completely
replace tuning files and simplicity, while providing visible benefit, is its
goal.  Anything more sophisticated is left for future patches.

A possible cheap enhancement, if needed, could be setting exposure + gain to
minimum values temporarily, before setting the black level.  In theory, the
black level should be fixed but it may not be reached in all images.  For this
reason, the patch updates black level only if the observed value is lower than
the current one; it should be never increased.

The purpose of the patch is to compensate for hardware properties.  General
image contrast enhancements are out of scope of this patch.

Stats are still gathered as an uncorrected histogram, to avoid any confusion and
to represent the raw image data.  Exposure must be determined after the black
level correction -- it has no influence on the sub-black area and must be
correct after applying the black level correction.  The granularity of the
histogram is increased from 16 to 64 to provide a better precision (there is no
theory behind either of those numbers).

Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
 .../internal/software_isp/debayer_params.h    |  4 +
 .../internal/software_isp/swisp_stats.h       | 10 ++-
 src/ipa/simple/black_level.cpp                | 86 +++++++++++++++++++
 src/ipa/simple/black_level.h                  | 28 ++++++
 src/ipa/simple/meson.build                    |  7 +-
 src/ipa/simple/soft_simple.cpp                | 28 ++++--
 src/libcamera/software_isp/debayer_cpu.cpp    | 13 ++-
 src/libcamera/software_isp/debayer_cpu.h      |  1 +
 src/libcamera/software_isp/software_isp.cpp   |  2 +-
 9 files changed, 162 insertions(+), 17 deletions(-)
 create mode 100644 src/ipa/simple/black_level.cpp
 create mode 100644 src/ipa/simple/black_level.h

diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h
index 98965fa1..5e38e08b 100644
--- a/include/libcamera/internal/software_isp/debayer_params.h
+++ b/include/libcamera/internal/software_isp/debayer_params.h
@@ -43,6 +43,10 @@ struct DebayerParams {
 	 * \brief Gamma correction, 1.0 is no correction
 	 */
 	float gamma;
+	/**
+	 * \brief Level of the black point, 0..255, 0 is no correction.
+	 */
+	unsigned int blackLevel;
 };
 
 } /* namespace libcamera */
diff --git a/include/libcamera/internal/software_isp/swisp_stats.h b/include/libcamera/internal/software_isp/swisp_stats.h
index afe42c9a..25cd5abd 100644
--- a/include/libcamera/internal/software_isp/swisp_stats.h
+++ b/include/libcamera/internal/software_isp/swisp_stats.h
@@ -7,6 +7,8 @@
 
 #pragma once
 
+#include <array>
+
 namespace libcamera {
 
 /**
@@ -28,11 +30,15 @@ struct SwIspStats {
 	/**
 	 * \brief Number of bins in the yHistogram.
 	 */
-	static constexpr unsigned int kYHistogramSize = 16;
+	static constexpr unsigned int kYHistogramSize = 64;
+	/**
+	 * \brief Type of the histogram.
+	 */
+	using histogram = std::array<unsigned int, kYHistogramSize>;
 	/**
 	 * \brief A histogram of luminance values.
 	 */
-	std::array<unsigned int, kYHistogramSize> yHistogram;
+	histogram yHistogram;
 };
 
 } /* namespace libcamera */
diff --git a/src/ipa/simple/black_level.cpp b/src/ipa/simple/black_level.cpp
new file mode 100644
index 00000000..8d52201b
--- /dev/null
+++ b/src/ipa/simple/black_level.cpp
@@ -0,0 +1,86 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Red Hat Inc.
+ *
+ * black_level.cpp - black level handling
+ */
+
+#include "black_level.h"
+
+#include <numeric>
+
+#include <libcamera/base/log.h>
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(IPASoftBL)
+
+/**
+ * \class BlackLevel
+ * \brief Object providing black point level for software ISP
+ *
+ * Black level can be provided in hardware tuning files or, if no tuning file is
+ * available for the given hardware, guessed automatically, with less accuracy.
+ * As tuning files are not yet implemented for software ISP, BlackLevel
+ * currently provides only guessed black levels.
+ *
+ * This class serves for tracking black level as a property of the underlying
+ * hardware, not as means of enhancing a particular scene or image.
+ *
+ * The class is supposed to be instantiated for the given camera stream.
+ * The black level can be retrieved using BlackLevel::get() method. It is
+ * initially 0 and may change when updated using BlackLevel::update() method.
+ */
+
+BlackLevel::BlackLevel()
+	: blackLevel_(255), blackLevelSet_(false)
+{
+}
+
+/**
+ * \brief Return the current black level
+ *
+ * \return The black level, in the range from 0 (minimum) to 255 (maximum).
+ * If the black level couldn't be determined yet, return 0.
+ */
+unsigned int BlackLevel::get() const
+{
+	return blackLevelSet_ ? blackLevel_ : 0;
+}
+
+/**
+ * \brief Update black level from the provided histogram
+ * \param[in] yHistogram The histogram to be used for updating black level
+ *
+ * The black level is property of the given hardware, not image. It is updated
+ * only if it has not been yet set or if it is lower than the lowest value seen
+ * so far.
+ */
+void BlackLevel::update(SwIspStats::histogram &yHistogram)
+{
+	// The constant is selected to be "good enough", not overly conservative or
+	// aggressive. There is no magic about the given value.
+	constexpr float ignoredPercentage_ = 0.02;
+	const unsigned int total =
+		std::accumulate(begin(yHistogram), end(yHistogram), 0);
+	const unsigned int pixelThreshold = ignoredPercentage_ * total;
+	const unsigned int currentBlackIdx =
+		blackLevel_ / (256 / SwIspStats::kYHistogramSize);
+
+	for (unsigned int i = 0, seen = 0;
+	     i < currentBlackIdx && i < SwIspStats::kYHistogramSize;
+	     i++) {
+		seen += yHistogram[i];
+		if (seen >= pixelThreshold) {
+			blackLevel_ = i * (256 / SwIspStats::kYHistogramSize);
+			blackLevelSet_ = true;
+			LOG(IPASoftBL, Debug)
+				<< "Auto-set black level: "
+				<< i << "/" << SwIspStats::kYHistogramSize
+				<< " (" << 100 * (seen - yHistogram[i]) / total << "% below, "
+				<< 100 * seen / total << "% at or below)";
+			break;
+		}
+	};
+}
+} // namespace libcamera
diff --git a/src/ipa/simple/black_level.h b/src/ipa/simple/black_level.h
new file mode 100644
index 00000000..b3785db0
--- /dev/null
+++ b/src/ipa/simple/black_level.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Red Hat Inc.
+ *
+ * black_level.h - black level handling
+ */
+
+#pragma once
+
+#include <array>
+
+#include "libcamera/internal/software_isp/swisp_stats.h"
+
+namespace libcamera {
+
+class BlackLevel
+{
+public:
+	BlackLevel();
+	unsigned int get() const;
+	void update(std::array<unsigned int, SwIspStats::kYHistogramSize> &yHistogram);
+
+private:
+	unsigned int blackLevel_;
+	bool blackLevelSet_;
+};
+
+} // namespace libcamera
diff --git a/src/ipa/simple/meson.build b/src/ipa/simple/meson.build
index 3e863db7..44b5f1d7 100644
--- a/src/ipa/simple/meson.build
+++ b/src/ipa/simple/meson.build
@@ -2,8 +2,13 @@
 
 ipa_name = 'ipa_soft_simple'
 
+soft_simple_sources = files([
+    'soft_simple.cpp',
+    'black_level.cpp',
+])
+
 mod = shared_module(ipa_name,
-                    ['soft_simple.cpp', libcamera_generated_ipa_headers],
+                    [soft_simple_sources, libcamera_generated_ipa_headers],
                     name_prefix : '',
                     include_directories : [ipa_includes, libipa_includes],
                     dependencies : libcamera_private,
diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp
index 312df4ba..ac027568 100644
--- a/src/ipa/simple/soft_simple.cpp
+++ b/src/ipa/simple/soft_simple.cpp
@@ -22,6 +22,8 @@
 #include "libcamera/internal/software_isp/debayer_params.h"
 #include "libcamera/internal/software_isp/swisp_stats.h"
 
+#include "black_level.h"
+
 namespace libcamera {
 
 LOG_DEFINE_CATEGORY(IPASoft)
@@ -33,7 +35,8 @@ class IPASoftSimple : public ipa::soft::IPASoftInterface
 public:
 	IPASoftSimple()
 		: params_(static_cast<DebayerParams *>(MAP_FAILED)),
-		  stats_(static_cast<SwIspStats *>(MAP_FAILED)), ignore_updates_(0)
+		  stats_(static_cast<SwIspStats *>(MAP_FAILED)),
+		  blackLevel_(BlackLevel()), ignore_updates_(0)
 	{
 	}
 
@@ -63,6 +66,7 @@ private:
 	SharedFD fdParams_;
 	DebayerParams *params_;
 	SwIspStats *stats_;
+	BlackLevel blackLevel_;
 
 	int32_t exposure_min_, exposure_max_;
 	int32_t again_min_, again_max_;
@@ -196,6 +200,10 @@ void IPASoftSimple::processStats(const ControlList &sensorControls)
 	params_->gainG = 256;
 	params_->gamma = 0.5;
 
+	if (ignore_updates_ > 0)
+		blackLevel_.update(stats_->yHistogram);
+	params_->blackLevel = blackLevel_.get();
+
 	setIspParams.emit(0);
 
 	/*
@@ -211,18 +219,19 @@ void IPASoftSimple::processStats(const ControlList &sensorControls)
 	 * Calculate Mean Sample Value (MSV) according to formula from:
 	 * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf
 	 */
-	constexpr unsigned int yHistValsPerBin =
-		SwIspStats::kYHistogramSize / kExposureBinsCount;
-	constexpr unsigned int yHistValsPerBinMod =
-		SwIspStats::kYHistogramSize /
-		(SwIspStats::kYHistogramSize % kExposureBinsCount + 1);
+	const unsigned int blackLevelHistIdx =
+		params_->blackLevel / (256 / SwIspStats::kYHistogramSize);
+	const unsigned int histogramSize = SwIspStats::kYHistogramSize - blackLevelHistIdx;
+	const unsigned int yHistValsPerBin = histogramSize / kExposureBinsCount;
+	const unsigned int yHistValsPerBinMod =
+		histogramSize / (histogramSize % kExposureBinsCount + 1);
 	int ExposureBins[kExposureBinsCount] = {};
 	unsigned int denom = 0;
 	unsigned int num = 0;
 
-	for (unsigned int i = 0; i < SwIspStats::kYHistogramSize; i++) {
+	for (unsigned int i = 0; i < histogramSize; i++) {
 		unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin;
-		ExposureBins[idx] += stats_->yHistogram[i];
+		ExposureBins[idx] += stats_->yHistogram[blackLevelHistIdx + i];
 	}
 
 	for (unsigned int i = 0; i < kExposureBinsCount; i++) {
@@ -256,7 +265,8 @@ void IPASoftSimple::processStats(const ControlList &sensorControls)
 
 	LOG(IPASoft, Debug) << "exposureMSV " << exposureMSV
 			    << " exp " << exposure_ << " again " << again_
-			    << " gain R/B " << params_->gainR << "/" << params_->gainB;
+			    << " gain R/B " << params_->gainR << "/" << params_->gainB
+			    << " black level " << params_->blackLevel;
 }
 
 void IPASoftSimple::updateExposure(double exposureMSV)
diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp
index a1692693..3be3cdfe 100644
--- a/src/libcamera/software_isp/debayer_cpu.cpp
+++ b/src/libcamera/software_isp/debayer_cpu.cpp
@@ -35,7 +35,7 @@ namespace libcamera {
  * \param[in] stats Pointer to the stats object to use.
  */
 DebayerCpu::DebayerCpu(std::unique_ptr<SwStatsCpu> stats)
-	: stats_(std::move(stats)), gamma_correction_(1.0)
+	: stats_(std::move(stats)), gamma_correction_(1.0), blackLevel_(0)
 {
 #ifdef __x86_64__
 	enableInputMemcpy_ = false;
@@ -683,11 +683,16 @@ void DebayerCpu::process(FrameBuffer *input, FrameBuffer *output, DebayerParams
 	}
 
 	/* Apply DebayerParams */
-	if (params.gamma != gamma_correction_) {
-		for (unsigned int i = 0; i < kGammaLookupSize; i++)
-			gamma_[i] = UINT8_MAX * powf(i / (kGammaLookupSize - 1.0), params.gamma);
+	if (params.gamma != gamma_correction_ || params.blackLevel != blackLevel_) {
+		const unsigned int blackIndex =
+			params.blackLevel * kGammaLookupSize / 256;
+		std::fill(gamma_.begin(), gamma_.begin() + blackIndex, 0);
+		const float divisor = kGammaLookupSize - blackIndex - 1.0;
+		for (unsigned int i = blackIndex; i < kGammaLookupSize; i++)
+			gamma_[i] = UINT8_MAX * powf((i - blackIndex) / divisor, params.gamma);
 
 		gamma_correction_ = params.gamma;
+		blackLevel_ = params.blackLevel;
 	}
 
 	if (swapRedBlueGains_)
diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h
index 5f44fc65..ea02f909 100644
--- a/src/libcamera/software_isp/debayer_cpu.h
+++ b/src/libcamera/software_isp/debayer_cpu.h
@@ -147,6 +147,7 @@ private:
 	bool enableInputMemcpy_;
 	bool swapRedBlueGains_;
 	float gamma_correction_;
+	unsigned int blackLevel_;
 	unsigned int measuredFrames_;
 	int64_t frameProcessTime_;
 	/* Skip 30 frames for things to stabilize then measure 30 frames */
diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
index 388b4496..9b49be41 100644
--- a/src/libcamera/software_isp/software_isp.cpp
+++ b/src/libcamera/software_isp/software_isp.cpp
@@ -64,7 +64,7 @@ LOG_DEFINE_CATEGORY(SoftwareIsp)
  */
 SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const ControlInfoMap &sensorControls)
 	: debayer_(nullptr),
-	  debayerParams_{ DebayerParams::kGain10, DebayerParams::kGain10, DebayerParams::kGain10, 0.5f },
+	  debayerParams_{ DebayerParams::kGain10, DebayerParams::kGain10, DebayerParams::kGain10, 0.5f, 0 },
 	  dmaHeap_(DmaHeap::DmaHeapFlag::Cma | DmaHeap::DmaHeapFlag::System)
 {
 	if (!dmaHeap_.isValid()) {
-- 
2.43.2