9948eb64d1
Refresh them with the patches from https://patchwork.libcamera.org/cover/19663/. This is still based off v0.2.0. Change-Id: I875fd64e3bb71a95c92af1108a23d27c0f3494e0 Reviewed-on: https://cl.tvl.fyi/c/depot/+/11179 Tested-by: BuildkiteCI Reviewed-by: flokli <flokli@flokli.de> Autosubmit: flokli <flokli@flokli.de>
396 lines
14 KiB
Diff
396 lines
14 KiB
Diff
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
|
|
|