Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/libcamera/internal/software_isp/swstats_cpu.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ class SwStatsCpu

unsigned int xShift_;
unsigned int stride_;
unsigned int sumShift_;

std::vector<SwIspStats> stats_;
SharedMemObject<SwIspStats> sharedStats_;
Expand Down
14 changes: 14 additions & 0 deletions src/ipa/libipa/camera_sensor_helper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,18 @@ class CameraSensorHelperImx708 : public CameraSensorHelper
};
REGISTER_CAMERA_SENSOR_HELPER("imx708", CameraSensorHelperImx708)

class CameraSensorHelperOv01a10 : public CameraSensorHelper
{
public:
CameraSensorHelperOv01a10()
{
/* From dark frame measurement: 0x40 at 10bits. */
blackLevel_ = 4096;
gain_ = AnalogueGainLinear{ 1, 0, 0, 256 };
}
};
REGISTER_CAMERA_SENSOR_HELPER("ov01a10", CameraSensorHelperOv01a10)

class CameraSensorHelperOv2685 : public CameraSensorHelper
{
public:
Expand All @@ -672,6 +684,8 @@ class CameraSensorHelperOv2740 : public CameraSensorHelper
public:
CameraSensorHelperOv2740()
{
/* From Linux kernel driver: 0x40 at 10bits. */
blackLevel_ = 4096;
gain_ = AnalogueGainLinear{ 1, 0, 0, 128 };
}
};
Expand Down
48 changes: 23 additions & 25 deletions src/ipa/simple/algorithms/adjust.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,37 @@
#include <libcamera/control_ids.h>

#include "libcamera/internal/matrix.h"
#include "libcamera/internal/yaml_parser.h"

namespace libcamera {

namespace ipa::soft::algorithms {

constexpr float kDefaultContrast = 1.0f;
constexpr float kDefaultSaturation = 1.0f;

LOG_DEFINE_CATEGORY(IPASoftAdjust)

int Adjust::init(IPAContext &context, [[maybe_unused]] const ValueNode &tuningData)
int Adjust::init(IPAContext &context, const ValueNode &tuningData)
{
defaultGamma_ = tuningData["gamma"].get<float>().value_or(kDefaultGamma);
defaultContrast_ = tuningData["contrast"].get<float>().value_or(1.0f);
defaultSaturation_ = tuningData["saturation"].get<float>().value_or(1.0f);

context.ctrlMap[&controls::Gamma] =
ControlInfo(0.1f, 10.0f, kDefaultGamma);
ControlInfo(0.1f, 10.0f, defaultGamma_);
context.ctrlMap[&controls::Contrast] =
ControlInfo(0.0f, 2.0f, kDefaultContrast);
ControlInfo(0.0f, 2.0f, defaultContrast_);
if (context.ccmEnabled)
context.ctrlMap[&controls::Saturation] =
ControlInfo(0.0f, 2.0f, kDefaultSaturation);
ControlInfo(0.0f, 2.0f, defaultSaturation_);

return 0;
}

int Adjust::configure(IPAContext &context,
[[maybe_unused]] const IPAConfigInfo &configInfo)
{
context.activeState.knobs.gamma = kDefaultGamma;
context.activeState.knobs.contrast = std::optional<float>();
context.activeState.knobs.saturation = std::optional<float>();
context.activeState.knobs.gamma = defaultGamma_;
context.activeState.knobs.contrast = defaultContrast_;
context.activeState.knobs.saturation = defaultSaturation_;

return 0;
}
Expand All @@ -59,13 +62,13 @@ void Adjust::queueRequest(typename Module::Context &context,

const auto &contrast = controls.get(controls::Contrast);
if (contrast.has_value()) {
context.activeState.knobs.contrast = contrast;
context.activeState.knobs.contrast = contrast.value();
LOG(IPASoftAdjust, Debug) << "Setting contrast to " << contrast.value();
}

const auto &saturation = controls.get(controls::Saturation);
if (saturation.has_value()) {
context.activeState.knobs.saturation = saturation;
context.activeState.knobs.saturation = saturation.value();
LOG(IPASoftAdjust, Debug) << "Setting saturation to " << saturation.value();
}
}
Expand Down Expand Up @@ -100,15 +103,15 @@ void Adjust::prepare(IPAContext &context,
frameContext.gamma = context.activeState.knobs.gamma;
frameContext.contrast = context.activeState.knobs.contrast;

auto &saturation = context.activeState.knobs.saturation;
if (context.ccmEnabled && saturation) {
applySaturation(context.activeState.combinedMatrix, saturation.value());
const float saturation = context.activeState.knobs.saturation;
if (context.ccmEnabled) {
applySaturation(context.activeState.combinedMatrix, saturation);
frameContext.saturation = saturation;
}

params->gamma = 1.0 / context.activeState.knobs.gamma;
const float contrast = context.activeState.knobs.contrast.value_or(kDefaultContrast);
params->contrastExp = tan(std::clamp(contrast * M_PI_4, 0.0, M_PI_2 - 0.00001));
params->contrastExp = tan(std::clamp(context.activeState.knobs.contrast * M_PI_4,
0.0, M_PI_2 - 0.00001));
}

void Adjust::process([[maybe_unused]] IPAContext &context,
Expand All @@ -117,14 +120,9 @@ void Adjust::process([[maybe_unused]] IPAContext &context,
[[maybe_unused]] const SwIspStats *stats,
ControlList &metadata)
{
const auto &gamma = frameContext.gamma;
metadata.set(controls::Gamma, gamma);

const auto &contrast = frameContext.contrast;
metadata.set(controls::Contrast, contrast.value_or(kDefaultContrast));

const auto &saturation = frameContext.saturation;
metadata.set(controls::Saturation, saturation.value_or(kDefaultSaturation));
metadata.set(controls::Gamma, frameContext.gamma);
metadata.set(controls::Contrast, frameContext.contrast);
metadata.set(controls::Saturation, frameContext.saturation);
}

REGISTER_IPA_ALGORITHM(Adjust, "Adjust")
Expand Down
4 changes: 4 additions & 0 deletions src/ipa/simple/algorithms/adjust.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ class Adjust : public Algorithm

private:
void applySaturation(Matrix<float, 3, 3> &ccm, float saturation);

float defaultGamma_;
float defaultContrast_;
float defaultSaturation_;
};

} /* namespace ipa::soft::algorithms */
Expand Down
82 changes: 55 additions & 27 deletions src/ipa/simple/algorithms/agc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#include "agc.h"

#include <algorithm>
#include <cmath>
#include <stdint.h>

#include <libcamera/base/log.h>
Expand All @@ -26,63 +28,88 @@ static constexpr unsigned int kExposureBinsCount = 5;

/*
* The exposure is optimal when the mean sample value of the histogram is
* in the middle of the range.
* in the middle of the range. Overridable via YAML exposureTarget.
*/
static constexpr float kExposureOptimal = kExposureBinsCount / 2.0;
static constexpr float kExposureTargetDefault = kExposureBinsCount / 2.0;

/*
* This implements the hysteresis for the exposure adjustment.
* It is small enough to have the exposure close to the optimal, and is big
* enough to prevent the exposure from wobbling around the optimal value.
*/
static constexpr float kExposureSatisfactory = 0.2;
static constexpr float kHysteresisDefault = 0.2;

/*
* Proportional gain for exposure/gain adjustment. Maps the MSV error to a
* multiplicative correction factor:
*
* factor = 1.0 + proportionalGain_ * error
*
* With proportionalGain_ = 0.04:
* - max error ~2.5 -> factor 1.10 (~10% step, same as before)
* - error 1.0 -> factor 1.04 (~4% step)
* - error 0.3 -> factor 1.012 (~1.2% step)
*
* Overridable via YAML proportionalGain.
*/
static constexpr float kProportionalGainDefault = 0.04;

Agc::Agc()
{
}

void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV)
int Agc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
{
/*
* kExpDenominator of 10 gives ~10% increment/decrement;
* kExpDenominator of 5 - about ~20%
*/
static constexpr uint8_t kExpDenominator = 10;
static constexpr uint8_t kExpNumeratorUp = kExpDenominator + 1;
static constexpr uint8_t kExpNumeratorDown = kExpDenominator - 1;
exposureTarget_ = tuningData["exposureTarget"].get<float>()
.value_or(kExposureTargetDefault);
hysteresis_ = tuningData["hysteresis"].get<float>()
.value_or(kHysteresisDefault);
proportionalGain_ = tuningData["proportionalGain"].get<float>()
.value_or(kProportionalGainDefault);

return 0;
}

void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV)
{
int32_t &exposure = frameContext.sensor.exposure;
double &again = frameContext.sensor.gain;

if (exposureMSV < kExposureOptimal - kExposureSatisfactory) {
double error = exposureTarget_ - exposureMSV;

if (std::abs(error) <= hysteresis_)
return;

/*
* Compute a proportional correction factor. The sign of the error
* determines the direction: positive error means too dark (increase),
* negative means too bright (decrease).
*/
float factor = 1.0f + static_cast<float>(error) * proportionalGain_;

if (factor > 1.0f) {
/* Scene too dark: increase exposure first, then gain. */
if (exposure < context.configuration.agc.exposureMax) {
int32_t next = exposure * kExpNumeratorUp / kExpDenominator;
if (next - exposure < 1)
exposure += 1;
else
exposure = next;
int32_t next = static_cast<int32_t>(exposure * factor);
exposure = std::max(next, exposure + 1);
} else {
double next = again * kExpNumeratorUp / kExpDenominator;
double next = again * factor;
if (next - again < context.configuration.agc.againMinStep)
again += context.configuration.agc.againMinStep;
else
again = next;
}
}

if (exposureMSV > kExposureOptimal + kExposureSatisfactory) {
} else {
/* Scene too bright: decrease gain first, then exposure. */
if (again > context.configuration.agc.again10) {
double next = again * kExpNumeratorDown / kExpDenominator;
double next = again * factor;
if (again - next < context.configuration.agc.againMinStep)
again -= context.configuration.agc.againMinStep;
else
again = next;
} else {
int32_t next = exposure * kExpNumeratorDown / kExpDenominator;
if (exposure - next < 1)
exposure -= 1;
else
exposure = next;
int32_t next = static_cast<int32_t>(exposure * factor);
exposure = std::min(next, exposure - 1);
}
}

Expand All @@ -96,6 +123,7 @@ void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou

LOG(IPASoftExposure, Debug)
<< "exposureMSV " << exposureMSV
<< " error " << error << " factor " << factor
<< " exp " << exposure << " again " << again;
}

Expand Down
8 changes: 8 additions & 0 deletions src/ipa/simple/algorithms/agc.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#pragma once

#include <libcamera/internal/yaml_parser.h>

#include "algorithm.h"

namespace libcamera {
Expand All @@ -19,13 +21,19 @@ class Agc : public Algorithm
Agc();
~Agc() = default;

int init(IPAContext &context, const ValueNode &tuningData) override;

void process(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
const SwIspStats *stats,
ControlList &metadata) override;

private:
void updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV);

float exposureTarget_;
float hysteresis_;
float proportionalGain_;
};

} /* namespace ipa::soft::algorithms */
Expand Down
34 changes: 29 additions & 5 deletions src/ipa/simple/algorithms/awb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

#include <libcamera/control_ids.h>

#include "libcamera/internal/yaml_parser.h"

#include "libipa/colours.h"
#include "simple/ipa_context.h"

Expand All @@ -23,6 +25,21 @@ LOG_DEFINE_CATEGORY(IPASoftAwb)

namespace ipa::soft::algorithms {

int Awb::init([[maybe_unused]] IPAContext &context,
const ValueNode &tuningData)
{
maxGainR_ = tuningData["maxGainR"].get<float>().value_or(4.0f);
maxGainB_ = tuningData["maxGainB"].get<float>().value_or(4.0f);
speed_ = tuningData["speed"].get<float>().value_or(1.0f);

LOG(IPASoftAwb, Debug)
<< "AWB: maxGainR " << maxGainR_
<< ", maxGainB " << maxGainB_
<< ", speed " << speed_;

return 0;
}

int Awb::configure(IPAContext &context,
[[maybe_unused]] const IPAConfigInfo &configInfo)
{
Expand Down Expand Up @@ -84,14 +101,21 @@ void Awb::process(IPAContext &context,
const RGB<uint64_t> sum = stats->sum_.max(offset + minValid) - offset;

/*
* Calculate red and blue gains for AWB.
* Clamp max gain at 4.0, this also avoids 0 division.
* Calculate red and blue gains for AWB. Clamp max gain to avoid
* division by zero and extreme color casts.
*/
auto &gains = context.activeState.awb.gains;
float rawRGain = sum.r() <= sum.g() / maxGainR_ ? maxGainR_ :
static_cast<float>(sum.g()) / sum.r();
float rawBGain = sum.b() <= sum.g() / maxGainB_ ? maxGainB_ :
static_cast<float>(sum.g()) / sum.b();

/* Apply temporal smoothing to avoid rapid white balance changes. */
float alpha = std::clamp(speed_, 0.01f, 1.0f);
gains = { {
sum.r() <= sum.g() / 4 ? 4.0f : static_cast<float>(sum.g()) / sum.r(),
1.0,
sum.b() <= sum.g() / 4 ? 4.0f : static_cast<float>(sum.g()) / sum.b(),
gains.r() * (1.0f - alpha) + rawRGain * alpha,
1.0f,
gains.b() * (1.0f - alpha) + rawBGain * alpha,
} };

RGB<double> rgbGains{ { 1 / gains.r(), 1 / gains.g(), 1 / gains.b() } };
Expand Down
6 changes: 6 additions & 0 deletions src/ipa/simple/algorithms/awb.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class Awb : public Algorithm
Awb() = default;
~Awb() = default;

int init(IPAContext &context, const ValueNode &tuningData) override;
int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
void prepare(IPAContext &context,
const uint32_t frame,
Expand All @@ -29,6 +30,11 @@ class Awb : public Algorithm
IPAFrameContext &frameContext,
const SwIspStats *stats,
ControlList &metadata) override;

private:
float maxGainR_;
float maxGainB_;
float speed_;
};

} /* namespace ipa::soft::algorithms */
Expand Down
1 change: 1 addition & 0 deletions src/ipa/simple/data/meson.build
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: CC0-1.0

conf_files = files([
'ov01a10.yaml',
'uncalibrated.yaml',
])

Expand Down
Loading