Some checks failed
image / image (push) Has been cancelled
The DW9784 VCM works and libcamera discovers it through the sensor's ancillary link, but nothing in the software ISP ever moves it. Backport the out-of-tree autofocus work onto v0.7.2: - 0007-0009 are Vasiliy Doylov's focus control, contrast autofocus and focus-loss detection from the softisp-playground branch; - 0010 is Pavel Machek's Librem5-tested robustness work (centre-window, brightness-normalised sharpness, two-phase sweep, settle skip), squashed and adapted; - 0011-0013 are ours: the lens write no longer sits inside the no-frame-start-emitter branch, the lensless-camera paths are guarded, and AfMode is advertised with a continuous default so that stock applications get autofocus without sending AfTrigger. Provenance and the adaptations made to each patch are recorded in the patch commit messages.
224 lines
7.8 KiB
Diff
224 lines
7.8 KiB
Diff
From 832c016ad79e1280d1a2e840cc47f434cba4e382 Mon Sep 17 00:00:00 2001
|
|
From: Vasiliy Doylov <nekocwd@mainlining.org>
|
|
Date: Wed, 9 Jul 2025 16:07:14 +0300
|
|
Subject: [PATCH] libcamera: software_isp: Add autofocus
|
|
|
|
Signed-off-by: Vasiliy Doylov <nekocwd@mainlining.org>
|
|
---
|
|
.../internal/software_isp/swisp_stats.h | 4 ++
|
|
src/ipa/simple/algorithms/af.cpp | 59 ++++++++++++++++++-
|
|
src/ipa/simple/algorithms/af.h | 1 +
|
|
src/ipa/simple/ipa_context.h | 5 ++
|
|
src/libcamera/software_isp/swstats_cpu.cpp | 22 +++++--
|
|
5 files changed, 86 insertions(+), 5 deletions(-)
|
|
|
|
diff --git a/include/libcamera/internal/software_isp/swisp_stats.h b/include/libcamera/internal/software_isp/swisp_stats.h
|
|
index d9d0d9be8..c2ec20e42 100644
|
|
--- a/include/libcamera/internal/software_isp/swisp_stats.h
|
|
+++ b/include/libcamera/internal/software_isp/swisp_stats.h
|
|
@@ -43,6 +43,10 @@ struct SwIspStats {
|
|
* \brief A histogram of luminance values of all the sampled pixels
|
|
*/
|
|
Histogram yHistogram;
|
|
+ /**
|
|
+ * \brief Holds the sharpness of an image
|
|
+ */
|
|
+ uint64_t sharpness;
|
|
};
|
|
|
|
} /* namespace libcamera */
|
|
diff --git a/src/ipa/simple/algorithms/af.cpp b/src/ipa/simple/algorithms/af.cpp
|
|
index 6197f3271..3f0f98f93 100644
|
|
--- a/src/ipa/simple/algorithms/af.cpp
|
|
+++ b/src/ipa/simple/algorithms/af.cpp
|
|
@@ -27,14 +27,17 @@ int Af::init(IPAContext &context,
|
|
[[maybe_unused]] const ValueNode &tuningData)
|
|
{
|
|
context.ctrlMap[&controls::LensPosition] = ControlInfo(0.0f, 100.0f, 50.0f);
|
|
+ context.ctrlMap[&controls::AfTrigger] = ControlInfo(0, 1, 0);
|
|
return 0;
|
|
}
|
|
|
|
int Af::configure(IPAContext &context,
|
|
[[maybe_unused]] const IPAConfigInfo &configInfo)
|
|
{
|
|
+ context.activeState.knobs.focus_sweep = std::optional<bool>();
|
|
context.activeState.knobs.focus_pos = std::optional<double>();
|
|
-
|
|
+ context.activeState.knobs.focus_sweep = false;
|
|
+ context.activeState.knobs.focus_pos = 0;
|
|
return 0;
|
|
}
|
|
|
|
@@ -44,10 +47,24 @@ void Af::queueRequest([[maybe_unused]] typename Module::Context &context,
|
|
const ControlList &controls)
|
|
{
|
|
const auto &focus_pos = controls.get(controls::LensPosition);
|
|
+ const auto &af_trigger = controls.get(controls::AfTrigger);
|
|
if (focus_pos.has_value()) {
|
|
context.activeState.knobs.focus_pos = focus_pos;
|
|
LOG(IPASoftAutoFocus, Debug) << "Setting focus position to " << focus_pos.value();
|
|
}
|
|
+ if (af_trigger.has_value()) {
|
|
+ context.activeState.knobs.focus_sweep = af_trigger.value() == 1;
|
|
+ if(context.activeState.knobs.focus_sweep){
|
|
+ context.activeState.knobs.focus_pos = 0;
|
|
+ context.configuration.focus.focus_max_pos = 0;
|
|
+ context.configuration.focus.sharpness_max = 0;
|
|
+ context.configuration.focus.start = 0;
|
|
+ context.configuration.focus.stop = 100;
|
|
+ context.configuration.focus.step = 25;
|
|
+ LOG(IPASoftAutoFocus, Info) << "Starting focus sweep";
|
|
+ }
|
|
+ }
|
|
+
|
|
}
|
|
|
|
void Af::updateFocus([[maybe_unused]] IPAContext &context, [[maybe_unused]] IPAFrameContext &frameContext, [[maybe_unused]] double exposureMSV)
|
|
@@ -55,12 +72,52 @@ void Af::updateFocus([[maybe_unused]] IPAContext &context, [[maybe_unused]] IPAF
|
|
frameContext.lens.focus_pos = context.activeState.knobs.focus_pos.value_or(50.0) / 100.0 * (context.configuration.focus.focus_max - context.configuration.focus.focus_min);
|
|
}
|
|
|
|
+void Af::step(double& start, double& stop, double& step, double& focus_pos, double& max_pos, uint64_t& max_sharp, uint64_t sharp, bool& sweep){
|
|
+ if(!sweep)
|
|
+ return;
|
|
+ if(focus_pos < start) {
|
|
+ focus_pos = start;
|
|
+ return;
|
|
+ }
|
|
+ if(sharp > max_sharp) {
|
|
+ max_sharp = sharp;
|
|
+ max_pos = focus_pos;
|
|
+ }
|
|
+ if(focus_pos >= stop) {
|
|
+ LOG(IPASoftAutoFocus, Info) << "Best focus on step " <<step << ": " << focus_pos;
|
|
+ start = std::clamp(max_pos - step, 0.0, 100.0);
|
|
+ stop = std::clamp(max_pos + step, 0.0, 100.0);
|
|
+ focus_pos = start;
|
|
+ max_sharp = 0;
|
|
+ step /= 2;
|
|
+ if(step <= 0.2){
|
|
+ sweep = false;
|
|
+ LOG(IPASoftAutoFocus, Info) << "Sweep end. Best focus: " << max_pos;
|
|
+ focus_pos = max_pos;
|
|
+ }
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ focus_pos += step;
|
|
+}
|
|
+
|
|
void Af::process([[maybe_unused]] IPAContext &context,
|
|
[[maybe_unused]] const uint32_t frame,
|
|
[[maybe_unused]] IPAFrameContext &frameContext,
|
|
[[maybe_unused]] const SwIspStats *stats,
|
|
[[maybe_unused]] ControlList &metadata)
|
|
{
|
|
+ if (stats->valid) {
|
|
+ step(context.configuration.focus.start,
|
|
+ context.configuration.focus.stop,
|
|
+ context.configuration.focus.step,
|
|
+ context.activeState.knobs.focus_pos.value(),
|
|
+ context.configuration.focus.focus_max_pos,
|
|
+ context.configuration.focus.sharpness_max,
|
|
+ stats->sharpness,
|
|
+ context.activeState.knobs.focus_sweep.value());
|
|
+ }
|
|
+
|
|
updateFocus(context, frameContext, 0);
|
|
}
|
|
|
|
diff --git a/src/ipa/simple/algorithms/af.h b/src/ipa/simple/algorithms/af.h
|
|
index b138b4d63..4d0ea58b3 100644
|
|
--- a/src/ipa/simple/algorithms/af.h
|
|
+++ b/src/ipa/simple/algorithms/af.h
|
|
@@ -33,6 +33,7 @@ public:
|
|
|
|
private:
|
|
void updateFocus(IPAContext &context, IPAFrameContext &frameContext, double focus);
|
|
+ void step(double& start, double& stop, double& step, double& focus_pos, double& max_pos, uint64_t& max_sharp, uint64_t sharp, bool& sweep);
|
|
};
|
|
|
|
} /* namespace ipa::soft::algorithms */
|
|
diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h
|
|
index 2b6bd4b3a..f869d1a67 100644
|
|
--- a/src/ipa/simple/ipa_context.h
|
|
+++ b/src/ipa/simple/ipa_context.h
|
|
@@ -35,6 +35,9 @@ struct IPASessionConfiguration {
|
|
} black;
|
|
struct {
|
|
int32_t focus_min, focus_max;
|
|
+ double focus_max_pos;
|
|
+ uint64_t sharpness_max;
|
|
+ double start, stop, step;
|
|
} focus;
|
|
};
|
|
|
|
@@ -65,6 +68,8 @@ struct IPAActiveState {
|
|
std::optional<float> saturation;
|
|
/* 0..100 range, 50.0 = normal */
|
|
std::optional<double> focus_pos;
|
|
+ /* 0..1 range, 0 = normal */
|
|
+ std::optional<bool> focus_sweep;
|
|
} knobs;
|
|
};
|
|
|
|
diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp
|
|
index 7fb77ce7d..f243992fb 100644
|
|
--- a/src/libcamera/software_isp/swstats_cpu.cpp
|
|
+++ b/src/libcamera/software_isp/swstats_cpu.cpp
|
|
@@ -177,7 +177,10 @@ static constexpr unsigned int kBlueYMul = 29; /* 0.114 * 256 */
|
|
\
|
|
uint64_t sumR = 0; \
|
|
uint64_t sumG = 0; \
|
|
- uint64_t sumB = 0;
|
|
+ uint64_t sumB = 0; \
|
|
+ pixel_t r0 = 0, r1 = 0, b0 = 0, \
|
|
+ b1 = 0, g0 = 0, g1 = 0; \
|
|
+ uint64_t sharpness = 0;
|
|
|
|
#define SWSTATS_ACCUMULATE_LINE_STATS(div) \
|
|
sumR += r; \
|
|
@@ -187,12 +190,20 @@ static constexpr unsigned int kBlueYMul = 29; /* 0.114 * 256 */
|
|
yVal = r * kRedYMul; \
|
|
yVal += g * kGreenYMul; \
|
|
yVal += b * kBlueYMul; \
|
|
- stats.yHistogram[yVal * SwIspStats::kYHistogramSize / (256 * 256 * (div))]++;
|
|
-
|
|
+ stats.yHistogram[yVal * SwIspStats::kYHistogramSize / (256 * 256 * (div))]++; \
|
|
+ if (r0 != 0) \
|
|
+ sharpness += abs(r - 2 * r1 + r0) * kRedYMul + abs(g - 2 * g1 + g0) * kGreenYMul + abs(b - 2 * b1 + b0) * kBlueYMul; \
|
|
+ r0 = r1; \
|
|
+ g0 = g1; \
|
|
+ b0 = b1; \
|
|
+ r1 = r; \
|
|
+ g1 = g; \
|
|
+ b1 = b;
|
|
#define SWSTATS_FINISH_LINE_STATS() \
|
|
stats.sum_.r() += sumR; \
|
|
stats.sum_.g() += sumG; \
|
|
- stats.sum_.b() += sumB;
|
|
+ stats.sum_.b() += sumB; \
|
|
+ stats.sharpness += sharpness;
|
|
|
|
void SwStatsCpu::statsBGGR8Line0(const uint8_t *src[], SwIspStats &stats)
|
|
{
|
|
@@ -392,6 +403,7 @@ void SwStatsCpu::startFrame(uint32_t frame)
|
|
for (auto &s : stats_) {
|
|
s.sum_ = RGB<uint64_t>({ 0, 0, 0 });
|
|
s.yHistogram.fill(0);
|
|
+ s.sharpness = 0;
|
|
}
|
|
}
|
|
|
|
@@ -408,8 +420,10 @@ void SwStatsCpu::finishFrame(uint32_t frame, uint32_t bufferId)
|
|
|
|
if (valid) {
|
|
sharedStats_->sum_ = RGB<uint64_t>({ 0, 0, 0 });
|
|
+ sharedStats_->sharpness = 0;
|
|
sharedStats_->yHistogram.fill(0);
|
|
for (const auto &s : stats_) {
|
|
+ sharedStats_->sharpness += s.sharpness;
|
|
sharedStats_->sum_ += s.sum_;
|
|
for (unsigned int j = 0; j < SwIspStats::kYHistogramSize; j++)
|
|
sharedStats_->yHistogram[j] += s.yHistogram[j];
|