libcamera: ipa: simple: Separate saturation from CCM
Saturation adjustments are implemented using matrix operations. They are currently applied to the colour correction matrix. Let's move them to a newly introduced separate "Adjust" algorithm. This separation has the following advantages: - It allows disabling general colour adjustments algorithms without disabling the CCM algorithm. - It keeps the CCM separated from other corrections. - It's generally cleaner. Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> Reviewed-by: Robert Mader <robert.mader@collabora.com> Signed-off-by: Milan Zamazal <mzamazal@redhat.com> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
This commit is contained in:
committed by
Kieran Bingham
parent
82ed6c19c2
commit
d92f5f5402
@@ -0,0 +1,106 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/*
|
||||
* Copyright (C) 2024, Ideas On Board
|
||||
* Copyright (C) 2024-2025, Red Hat Inc.
|
||||
*
|
||||
* Common image adjustments
|
||||
*/
|
||||
|
||||
#include "adjust.h"
|
||||
|
||||
#include <libcamera/base/log.h>
|
||||
#include <libcamera/base/utils.h>
|
||||
|
||||
#include <libcamera/control_ids.h>
|
||||
|
||||
#include "libcamera/internal/matrix.h"
|
||||
|
||||
namespace libcamera {
|
||||
|
||||
namespace ipa::soft::algorithms {
|
||||
|
||||
LOG_DEFINE_CATEGORY(IPASoftAdjust)
|
||||
|
||||
int Adjust::init(IPAContext &context, [[maybe_unused]] const YamlObject &tuningData)
|
||||
{
|
||||
if (context.ccmEnabled)
|
||||
context.ctrlMap[&controls::Saturation] = ControlInfo(0.0f, 2.0f, 1.0f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Adjust::configure(IPAContext &context,
|
||||
[[maybe_unused]] const IPAConfigInfo &configInfo)
|
||||
{
|
||||
context.activeState.knobs.saturation = std::optional<double>();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Adjust::queueRequest(typename Module::Context &context,
|
||||
[[maybe_unused]] const uint32_t frame,
|
||||
[[maybe_unused]] typename Module::FrameContext &frameContext,
|
||||
const ControlList &controls)
|
||||
{
|
||||
const auto &saturation = controls.get(controls::Saturation);
|
||||
if (saturation.has_value()) {
|
||||
context.activeState.knobs.saturation = saturation;
|
||||
LOG(IPASoftAdjust, Debug) << "Setting saturation to " << saturation.value();
|
||||
}
|
||||
}
|
||||
|
||||
void Adjust::applySaturation(Matrix<float, 3, 3> &matrix, float saturation)
|
||||
{
|
||||
/* https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion */
|
||||
const Matrix<float, 3, 3> rgb2ycbcr{
|
||||
{ 0.256788235294, 0.504129411765, 0.0979058823529,
|
||||
-0.148223529412, -0.290992156863, 0.439215686275,
|
||||
0.439215686275, -0.367788235294, -0.0714274509804 }
|
||||
};
|
||||
const Matrix<float, 3, 3> ycbcr2rgb{
|
||||
{ 1.16438356164, 0, 1.59602678571,
|
||||
1.16438356164, -0.391762290094, -0.812967647235,
|
||||
1.16438356164, 2.01723214285, 0 }
|
||||
};
|
||||
const Matrix<float, 3, 3> saturationMatrix{
|
||||
{ 1, 0, 0,
|
||||
0, saturation, 0,
|
||||
0, 0, saturation }
|
||||
};
|
||||
matrix =
|
||||
ycbcr2rgb * saturationMatrix * rgb2ycbcr * matrix;
|
||||
}
|
||||
|
||||
void Adjust::prepare(IPAContext &context,
|
||||
[[maybe_unused]] const uint32_t frame,
|
||||
IPAFrameContext &frameContext,
|
||||
[[maybe_unused]] DebayerParams *params)
|
||||
{
|
||||
if (!context.ccmEnabled)
|
||||
return;
|
||||
|
||||
auto &saturation = context.activeState.knobs.saturation;
|
||||
frameContext.saturation = saturation;
|
||||
if (saturation)
|
||||
applySaturation(context.activeState.combinedMatrix, saturation.value());
|
||||
|
||||
if (saturation != lastSaturation_) {
|
||||
context.activeState.matrixChanged = true;
|
||||
lastSaturation_ = saturation;
|
||||
}
|
||||
}
|
||||
|
||||
void Adjust::process([[maybe_unused]] IPAContext &context,
|
||||
[[maybe_unused]] const uint32_t frame,
|
||||
IPAFrameContext &frameContext,
|
||||
[[maybe_unused]] const SwIspStats *stats,
|
||||
ControlList &metadata)
|
||||
{
|
||||
const auto &saturation = frameContext.saturation;
|
||||
metadata.set(controls::Saturation, saturation.value_or(1.0));
|
||||
}
|
||||
|
||||
REGISTER_IPA_ALGORITHM(Adjust, "Adjust")
|
||||
|
||||
} /* namespace ipa::soft::algorithms */
|
||||
|
||||
} /* namespace libcamera */
|
||||
@@ -0,0 +1,52 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/*
|
||||
* Copyright (C) 2024-2025, Red Hat Inc.
|
||||
*
|
||||
* Color correction matrix
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "libcamera/internal/matrix.h"
|
||||
|
||||
#include <libipa/interpolator.h>
|
||||
|
||||
#include "algorithm.h"
|
||||
|
||||
namespace libcamera {
|
||||
|
||||
namespace ipa::soft::algorithms {
|
||||
|
||||
class Adjust : public Algorithm
|
||||
{
|
||||
public:
|
||||
Adjust() = default;
|
||||
~Adjust() = default;
|
||||
|
||||
int init(IPAContext &context, const YamlObject &tuningData) override;
|
||||
int configure(IPAContext &context,
|
||||
const IPAConfigInfo &configInfo) override;
|
||||
void queueRequest(typename Module::Context &context,
|
||||
const uint32_t frame,
|
||||
typename Module::FrameContext &frameContext,
|
||||
const ControlList &controls) override;
|
||||
void prepare(IPAContext &context,
|
||||
const uint32_t frame,
|
||||
IPAFrameContext &frameContext,
|
||||
DebayerParams *params) override;
|
||||
void process(IPAContext &context, const uint32_t frame,
|
||||
IPAFrameContext &frameContext,
|
||||
const SwIspStats *stats,
|
||||
ControlList &metadata) override;
|
||||
|
||||
private:
|
||||
void applySaturation(Matrix<float, 3, 3> &ccm, float saturation);
|
||||
|
||||
std::optional<float> lastSaturation_;
|
||||
};
|
||||
|
||||
} /* namespace ipa::soft::algorithms */
|
||||
|
||||
} /* namespace libcamera */
|
||||
@@ -1,9 +1,9 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/*
|
||||
* Copyright (C) 2024, Ideas On Board
|
||||
* Copyright (C) 2024-2025, Red Hat Inc.
|
||||
* Copyright (C) 2024-2026, Red Hat Inc.
|
||||
*
|
||||
* Color correction matrix + saturation
|
||||
* Color correction matrix
|
||||
*/
|
||||
|
||||
#include "ccm.h"
|
||||
@@ -37,74 +37,25 @@ int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData
|
||||
}
|
||||
|
||||
context.ccmEnabled = true;
|
||||
context.ctrlMap[&controls::Saturation] = ControlInfo(0.0f, 2.0f, 1.0f);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Ccm::configure(IPAContext &context,
|
||||
[[maybe_unused]] const IPAConfigInfo &configInfo)
|
||||
{
|
||||
context.activeState.knobs.saturation = std::optional<double>();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Ccm::queueRequest(typename Module::Context &context,
|
||||
[[maybe_unused]] const uint32_t frame,
|
||||
[[maybe_unused]] typename Module::FrameContext &frameContext,
|
||||
const ControlList &controls)
|
||||
{
|
||||
const auto &saturation = controls.get(controls::Saturation);
|
||||
if (saturation.has_value()) {
|
||||
context.activeState.knobs.saturation = saturation;
|
||||
LOG(IPASoftCcm, Debug) << "Setting saturation to " << saturation.value();
|
||||
}
|
||||
}
|
||||
|
||||
void Ccm::applySaturation(Matrix<float, 3, 3> &ccm, float saturation)
|
||||
{
|
||||
/* https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion */
|
||||
const Matrix<float, 3, 3> rgb2ycbcr{
|
||||
{ 0.256788235294, 0.504129411765, 0.0979058823529,
|
||||
-0.148223529412, -0.290992156863, 0.439215686275,
|
||||
0.439215686275, -0.367788235294, -0.0714274509804 }
|
||||
};
|
||||
const Matrix<float, 3, 3> ycbcr2rgb{
|
||||
{ 1.16438356164, 0, 1.59602678571,
|
||||
1.16438356164, -0.391762290094, -0.812967647235,
|
||||
1.16438356164, 2.01723214285, 0 }
|
||||
};
|
||||
const Matrix<float, 3, 3> saturationMatrix{
|
||||
{ 1, 0, 0,
|
||||
0, saturation, 0,
|
||||
0, 0, saturation }
|
||||
};
|
||||
ccm = ycbcr2rgb * saturationMatrix * rgb2ycbcr * ccm;
|
||||
}
|
||||
|
||||
void Ccm::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,
|
||||
IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params)
|
||||
{
|
||||
auto &saturation = context.activeState.knobs.saturation;
|
||||
|
||||
const unsigned int ct = context.activeState.awb.temperatureK;
|
||||
|
||||
/* Change CCM only on saturation or bigger temperature changes. */
|
||||
/* Change CCM only on bigger temperature changes. */
|
||||
if (!currentCcm_ ||
|
||||
utils::abs_diff(ct, lastCt_) >= kTemperatureThreshold ||
|
||||
saturation != lastSaturation_) {
|
||||
utils::abs_diff(ct, lastCt_) >= kTemperatureThreshold) {
|
||||
currentCcm_ = ccm_.getInterpolated(ct);
|
||||
if (saturation)
|
||||
applySaturation(currentCcm_.value(), saturation.value());
|
||||
lastCt_ = ct;
|
||||
lastSaturation_ = saturation;
|
||||
context.activeState.matrixChanged = true;
|
||||
}
|
||||
|
||||
context.activeState.combinedMatrix =
|
||||
currentCcm_.value() * context.activeState.combinedMatrix;
|
||||
frameContext.saturation = saturation;
|
||||
frameContext.ccm = currentCcm_.value();
|
||||
}
|
||||
|
||||
@@ -115,9 +66,6 @@ void Ccm::process([[maybe_unused]] IPAContext &context,
|
||||
ControlList &metadata)
|
||||
{
|
||||
metadata.set(controls::ColourCorrectionMatrix, frameContext.ccm.data());
|
||||
|
||||
const auto &saturation = frameContext.saturation;
|
||||
metadata.set(controls::Saturation, saturation.value_or(1.0));
|
||||
}
|
||||
|
||||
REGISTER_IPA_ALGORITHM(Ccm, "Ccm")
|
||||
|
||||
@@ -26,12 +26,6 @@ public:
|
||||
~Ccm() = default;
|
||||
|
||||
int init(IPAContext &context, const YamlObject &tuningData) override;
|
||||
int configure(IPAContext &context,
|
||||
const IPAConfigInfo &configInfo) override;
|
||||
void queueRequest(typename Module::Context &context,
|
||||
const uint32_t frame,
|
||||
typename Module::FrameContext &frameContext,
|
||||
const ControlList &controls) override;
|
||||
void prepare(IPAContext &context,
|
||||
const uint32_t frame,
|
||||
IPAFrameContext &frameContext,
|
||||
@@ -42,10 +36,7 @@ public:
|
||||
ControlList &metadata) override;
|
||||
|
||||
private:
|
||||
void applySaturation(Matrix<float, 3, 3> &ccm, float saturation);
|
||||
|
||||
unsigned int lastCt_;
|
||||
std::optional<float> lastSaturation_;
|
||||
Interpolator<Matrix<float, 3, 3>> ccm_;
|
||||
std::optional<Matrix<float, 3, 3>> currentCcm_;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
soft_simple_ipa_algorithms = files([
|
||||
'adjust.cpp',
|
||||
'awb.cpp',
|
||||
'agc.cpp',
|
||||
'blc.cpp',
|
||||
|
||||
@@ -14,6 +14,7 @@ algorithms:
|
||||
ccm: [ 1, 0, 0,
|
||||
0, 1, 0,
|
||||
0, 0, 1]
|
||||
- Adjust:
|
||||
- Lut:
|
||||
- Agc:
|
||||
...
|
||||
|
||||
Reference in New Issue
Block a user