@@ -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 */
@@ -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 */
new file mode 100644
@@ -0,0 +1,84 @@
+/* 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()
+ : blackLevelHistIdx_(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()
+{
+ return blackLevelSet_ ? blackLevelHistIdx_ * (256 / SwIspStats::kYHistogramSize) : 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;
+
+ for (unsigned int i = 0, seen = 0;
+ i < blackLevelHistIdx_ && i < SwIspStats::kYHistogramSize;
+ i++) {
+ seen += yHistogram[i];
+ if (seen >= pixelThreshold) {
+ blackLevelHistIdx_ = i;
+ blackLevelSet_ = true;
+ LOG(IPASoftBL, Debug)
+ << "Auto-set black level: "
+ << blackLevelHistIdx_ << "/" << SwIspStats::kYHistogramSize
+ << " (" << 100 * (seen - yHistogram[i]) / total << "% below, "
+ << 100 * seen / total << "% at or below)";
+ break;
+ }
+ };
+}
+} // namespace libcamera
new file mode 100644
@@ -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();
+ void update(std::array<unsigned int, SwIspStats::kYHistogramSize> &yHistogram);
+
+private:
+ unsigned int blackLevelHistIdx_;
+ bool blackLevelSet_;
+};
+
+} // namespace libcamera
@@ -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,
@@ -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)
@@ -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;
@@ -676,11 +676,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_)
@@ -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 */
@@ -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()) {