Skip to content

Commit 5c70b8b

Browse files
ipa: rpi: controller: Autofocus to use AWB statistics; re-trigger
Analyse AWB statistics: used both for scene change detection and to detect IR lighting (when a flag is set in the tuning file). Option to suppress PDAF altogether when IR lighting is detected. Rather than being based solely on PDAF "dropout", allow a scan to be (re-)triggered whenever the scene changes and then stabilizes, based on contrast and average RGB statistics within the AF window. [XXX Until the following patch, this may result in significant extra lens movement when PDAF is unavailable, since every scan will start from the minimum lens position.] Signed-off-by: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>
1 parent 754da2b commit 5c70b8b

File tree

2 files changed

+149
-25
lines changed

2 files changed

+149
-25
lines changed

src/ipa/rpi/controller/rpi/af.cpp

Lines changed: 123 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ Af::SpeedDependentParams::SpeedDependentParams()
4646
: stepCoarse(1.0),
4747
stepFine(0.25),
4848
contrastRatio(0.75),
49+
retriggerRatio(0.75),
50+
retriggerDelay(10),
4951
pdafGain(-0.02),
5052
pdafSquelch(0.125),
5153
maxSlew(2.0),
@@ -60,6 +62,7 @@ Af::CfgParams::CfgParams()
6062
confThresh(16),
6163
confClip(512),
6264
skipFrames(5),
65+
checkForIR(false),
6366
map()
6467
{
6568
}
@@ -87,6 +90,8 @@ void Af::SpeedDependentParams::read(const libcamera::YamlObject &params)
8790
readNumber<double>(stepCoarse, params, "step_coarse");
8891
readNumber<double>(stepFine, params, "step_fine");
8992
readNumber<double>(contrastRatio, params, "contrast_ratio");
93+
readNumber<double>(retriggerRatio, params, "retrigger_ratio");
94+
readNumber<uint32_t>(retriggerDelay, params, "retrigger_delay");
9095
readNumber<double>(pdafGain, params, "pdaf_gain");
9196
readNumber<double>(pdafSquelch, params, "pdaf_squelch");
9297
readNumber<double>(maxSlew, params, "max_slew");
@@ -137,6 +142,7 @@ int Af::CfgParams::read(const libcamera::YamlObject &params)
137142
readNumber<uint32_t>(confThresh, params, "conf_thresh");
138143
readNumber<uint32_t>(confClip, params, "conf_clip");
139144
readNumber<uint32_t>(skipFrames, params, "skip_frames");
145+
readNumber<bool>(checkForIR, params, "check_for_ir");
140146

141147
if (params.contains("map"))
142148
map = params["map"].get<ipa::Pwl>(ipa::Pwl{});
@@ -176,29 +182,37 @@ Af::Af(Controller *controller)
176182
useWindows_(false),
177183
phaseWeights_(),
178184
contrastWeights_(),
185+
awbWeights_(),
179186
scanState_(ScanState::Idle),
180187
initted_(false),
188+
irFlag_(false),
181189
ftarget_(-1.0),
182190
fsmooth_(-1.0),
183191
prevContrast_(0.0),
192+
oldSceneContrast_(0.0),
193+
prevAverage_{ 0.0, 0.0, 0.0 },
194+
oldSceneAverage_{ 0.0, 0.0, 0.0 },
184195
prevPhase_(0.0),
185196
skipCount_(0),
186197
stepCount_(0),
187198
dropCount_(0),
188199
sameSignCount_(0),
200+
sceneChangeCount_(0),
189201
scanMaxContrast_(0.0),
190202
scanMinContrast_(1.0e9),
191203
scanData_(),
192204
reportState_(AfState::Idle)
193205
{
194206
/*
195-
* Reserve space for data, to reduce memory fragmentation. It's too early
196-
* to query the size of the PDAF (from camera) and Contrast (from ISP)
197-
* statistics, but these are plausible upper bounds.
207+
* Reserve space for data structures, to reduce memory fragmentation.
208+
* It's too early to query the size of the PDAF sensor data, so guess.
198209
*/
210+
windows_.reserve(1);
199211
phaseWeights_.w.reserve(16 * 12);
200212
contrastWeights_.w.reserve(getHardwareConfig().focusRegions.width *
201213
getHardwareConfig().focusRegions.height);
214+
contrastWeights_.w.reserve(getHardwareConfig().awbRegions.width *
215+
getHardwareConfig().awbRegions.height);
202216
scanData_.reserve(32);
203217
}
204218

@@ -309,6 +323,7 @@ void Af::invalidateWeights()
309323
{
310324
phaseWeights_.sum = 0;
311325
contrastWeights_.sum = 0;
326+
awbWeights_.sum = 0;
312327
}
313328

314329
bool Af::getPhase(PdafRegions const &regions, double &phase, double &conf)
@@ -365,6 +380,54 @@ double Af::getContrast(const FocusRegions &focusStats)
365380
return (contrastWeights_.sum > 0) ? ((double)sumWc / (double)contrastWeights_.sum) : 0.0;
366381
}
367382

383+
/*
384+
* Get the average R, G, B values in AF window[s] (from AWB statistics).
385+
* Optionally, check if all of {R,G,B} are within 4:5 of each other
386+
* across more than 50% of the counted area and within the AF window:
387+
* for an RGB sensor this strongly suggests that IR lighting is in use.
388+
*/
389+
390+
bool Af::getAverageAndTestIr(const RgbyRegions &awbStats, double rgb[3])
391+
{
392+
libcamera::Size size = awbStats.size();
393+
if (size.height != awbWeights_.rows ||
394+
size.width != awbWeights_.cols || awbWeights_.sum == 0) {
395+
LOG(RPiAf, Debug) << "Recompute RGB weights " << size.width << 'x' << size.height;
396+
computeWeights(&awbWeights_, size.height, size.width);
397+
}
398+
399+
uint64_t sr = 0, sg = 0, sb = 0, sw = 1;
400+
uint64_t greyCount = 0, allCount = 0;
401+
for (unsigned i = 0; i < awbStats.numRegions(); ++i) {
402+
uint64_t r = awbStats.get(i).val.rSum;
403+
uint64_t g = awbStats.get(i).val.gSum;
404+
uint64_t b = awbStats.get(i).val.bSum;
405+
uint64_t w = awbWeights_.w[i];
406+
if (w) {
407+
sw += w;
408+
sr += w * r;
409+
sg += w * g;
410+
sb += w * b;
411+
}
412+
if (cfg_.checkForIR) {
413+
if (4 * r < 5 * b && 4 * b < 5 * r &&
414+
4 * r < 5 * g && 4 * g < 5 * r &&
415+
4 * b < 5 * g && 4 * g < 5 * b)
416+
greyCount += awbStats.get(i).counted;
417+
allCount += awbStats.get(i).counted;
418+
}
419+
}
420+
421+
rgb[0] = sr / (double)sw;
422+
rgb[1] = sg / (double)sw;
423+
rgb[2] = sb / (double)sw;
424+
425+
return (cfg_.checkForIR && 2 * greyCount > allCount &&
426+
4 * sr < 5 * sb && 4 * sb < 5 * sr &&
427+
4 * sr < 5 * sg && 4 * sg < 5 * sr &&
428+
4 * sb < 5 * sg && 4 * sg < 5 * sb);
429+
}
430+
368431
void Af::doPDAF(double phase, double conf)
369432
{
370433
/* Apply loop gain */
@@ -473,6 +536,8 @@ void Af::doScan(double contrast, double phase, double conf)
473536
if (scanData_.empty() || contrast > scanMaxContrast_) {
474537
scanMaxContrast_ = contrast;
475538
scanMaxIndex_ = scanData_.size();
539+
if (scanState_ != ScanState::Fine)
540+
std::copy(prevAverage_, prevAverage_ + 3, oldSceneAverage_);
476541
}
477542
if (contrast < scanMinContrast_)
478543
scanMinContrast_ = contrast;
@@ -523,27 +588,63 @@ void Af::doAF(double contrast, double phase, double conf)
523588
sameSignCount_++;
524589
prevPhase_ = phase;
525590

591+
if (mode_ == AfModeManual)
592+
return; /* nothing to do */
593+
526594
if (scanState_ == ScanState::Pdaf) {
527595
/*
528596
* Use PDAF closed-loop control whenever available, in both CAF
529597
* mode and (for a limited number of iterations) when triggered.
530-
* If PDAF fails (due to poor contrast, noise or large defocus),
531-
* fall back to a CDAF-based scan. To avoid "nuisance" scans,
532-
* scan only after a number of frames with low PDAF confidence.
598+
* If PDAF fails (due to poor contrast, noise or large defocus)
599+
* for at least dropoutFrames, fall back to a CDAF-based scan
600+
* immediately (in triggered-auto) or on scene change (in CAF).
533601
*/
534-
if (conf > (dropCount_ ? 1.0 : 0.25) * cfg_.confEpsilon) {
602+
if (conf >= cfg_.confEpsilon) {
535603
if (mode_ == AfModeAuto || sameSignCount_ >= 3)
536604
doPDAF(phase, conf);
537605
if (stepCount_ > 0)
538606
stepCount_--;
539607
else if (mode_ != AfModeContinuous)
540608
scanState_ = ScanState::Idle;
609+
oldSceneContrast_ = contrast;
610+
std::copy(prevAverage_, prevAverage_ + 3, oldSceneAverage_);
611+
sceneChangeCount_ = 0;
541612
dropCount_ = 0;
542-
} else if (++dropCount_ == cfg_.speeds[speed_].dropoutFrames)
613+
return;
614+
} else {
615+
dropCount_++;
616+
if (dropCount_ < cfg_.speeds[speed_].dropoutFrames)
617+
return;
618+
if (mode_ != AfModeContinuous) {
619+
startProgrammedScan();
620+
return;
621+
}
622+
/* else fall through to waiting for a scene change */
623+
}
624+
}
625+
if (scanState_ < ScanState::Coarse && mode_ == AfModeContinuous) {
626+
/*
627+
* In CAF mode, not in a scan, and PDAF is unavailable.
628+
* Wait for a scene change, followed by stability.
629+
*/
630+
if (contrast + 1.0 < cfg_.speeds[speed_].retriggerRatio * oldSceneContrast_ ||
631+
oldSceneContrast_ + 1.0 < cfg_.speeds[speed_].retriggerRatio * contrast ||
632+
prevAverage_[0] + 1.0 < cfg_.speeds[speed_].retriggerRatio * oldSceneAverage_[0] ||
633+
oldSceneAverage_[0] + 1.0 < cfg_.speeds[speed_].retriggerRatio * prevAverage_[0] ||
634+
prevAverage_[1] + 1.0 < cfg_.speeds[speed_].retriggerRatio * oldSceneAverage_[1] ||
635+
oldSceneAverage_[1] + 1.0 < cfg_.speeds[speed_].retriggerRatio * prevAverage_[1] ||
636+
prevAverage_[2] + 1.0 < cfg_.speeds[speed_].retriggerRatio * oldSceneAverage_[2] ||
637+
oldSceneAverage_[2] + 1.0 < cfg_.speeds[speed_].retriggerRatio * prevAverage_[2]) {
638+
oldSceneContrast_ = contrast;
639+
std::copy(prevAverage_, prevAverage_ + 3, oldSceneAverage_);
640+
sceneChangeCount_ = 1;
641+
} else if (sceneChangeCount_)
642+
sceneChangeCount_++;
643+
if (sceneChangeCount_ >= cfg_.speeds[speed_].retriggerDelay)
543644
startProgrammedScan();
544645
} else if (scanState_ >= ScanState::Coarse && fsmooth_ == ftarget_) {
545646
/*
546-
* Scanning sequence. This means PDAF has become unavailable.
647+
* CDAF-based scanning sequence.
547648
* Allow a delay between steps for CDAF FoM statistics to be
548649
* updated, and a "settling time" at the end of the sequence.
549650
* [A coarse or fine scan can be abandoned if two PDAF samples
@@ -562,11 +663,14 @@ void Af::doAF(double contrast, double phase, double conf)
562663
scanState_ = ScanState::Pdaf;
563664
else
564665
scanState_ = ScanState::Idle;
666+
dropCount_ = 0;
667+
sceneChangeCount_ = 0;
668+
oldSceneContrast_ = std::max(scanMaxContrast_, prevContrast_);
565669
scanData_.clear();
566670
} else if (conf >= cfg_.confThresh && earlyTerminationByPhase(phase)) {
671+
std::copy(prevAverage_, prevAverage_ + 3, oldSceneAverage_);
567672
scanState_ = ScanState::Settle;
568-
stepCount_ = (mode_ == AfModeContinuous) ? 0
569-
: cfg_.speeds[speed_].stepFrames;
673+
stepCount_ = (mode_ == AfModeContinuous) ? 0 : cfg_.speeds[speed_].stepFrames;
570674
} else
571675
doScan(contrast, phase, conf);
572676
}
@@ -596,7 +700,8 @@ void Af::updateLensPosition()
596700
void Af::startAF()
597701
{
598702
/* Use PDAF if the tuning file allows it; else CDAF. */
599-
if (cfg_.speeds[speed_].dropoutFrames > 0 &&
703+
if (cfg_.speeds[speed_].pdafGain != 0.0 &&
704+
cfg_.speeds[speed_].dropoutFrames > 0 &&
600705
(mode_ == AfModeContinuous || cfg_.speeds[speed_].pdafFrames > 0)) {
601706
if (!initted_) {
602707
ftarget_ = cfg_.ranges[range_].focusDefault;
@@ -606,6 +711,8 @@ void Af::startAF()
606711
scanState_ = ScanState::Pdaf;
607712
scanData_.clear();
608713
dropCount_ = 0;
714+
oldSceneContrast_ = 0.0;
715+
sceneChangeCount_ = 0;
609716
reportState_ = AfState::Scanning;
610717
} else
611718
startProgrammedScan();
@@ -656,7 +763,7 @@ void Af::prepare(Metadata *imageMetadata)
656763
uint32_t oldSt = stepCount_;
657764
if (imageMetadata->get("pdaf.regions", regions) == 0)
658765
getPhase(regions, phase, conf);
659-
doAF(prevContrast_, phase, conf);
766+
doAF(prevContrast_, phase, irFlag_ ? 0 : conf);
660767
updateLensPosition();
661768
LOG(RPiAf, Debug) << std::fixed << std::setprecision(2)
662769
<< static_cast<unsigned int>(reportState_)
@@ -666,7 +773,8 @@ void Af::prepare(Metadata *imageMetadata)
666773
<< " ft" << oldFt << "->" << ftarget_
667774
<< " fs" << oldFs << "->" << fsmooth_
668775
<< " cont=" << (int)prevContrast_
669-
<< " phase=" << (int)phase << " conf=" << (int)conf;
776+
<< " phase=" << (int)phase << " conf=" << (int)conf
777+
<< (irFlag_ ? " IR" : "");
670778
}
671779

672780
/* Report status and produce new lens setting */
@@ -690,6 +798,7 @@ void Af::process(StatisticsPtr &stats, [[maybe_unused]] Metadata *imageMetadata)
690798
{
691799
(void)imageMetadata;
692800
prevContrast_ = getContrast(stats->focusRegions);
801+
irFlag_ = getAverageAndTestIr(stats->awbRegions, prevAverage_);
693802
}
694803

695804
/* Controls */

src/ipa/rpi/controller/rpi/af.h

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,28 @@
1515
/*
1616
* This algorithm implements a hybrid of CDAF and PDAF, favouring PDAF.
1717
*
18-
* Whenever PDAF is available, it is used in a continuous feedback loop.
19-
* When triggered in auto mode, we simply enable AF for a limited number
20-
* of frames (it may terminate early if the delta becomes small enough).
18+
* Whenever PDAF is available (and reports sufficiently high confidence),
19+
* it is used for continuous feedback control of the lens position. When
20+
* triggered in Auto mode, we enable the loop for a limited number of frames
21+
* (it may terminate sooner if the phase becomes small). In CAF mode, the
22+
* PDAF loop runs continuously. Very small lens movements are suppressed.
2123
*
2224
* When PDAF confidence is low (due e.g. to low contrast or extreme defocus)
2325
* or PDAF data are absent, fall back to CDAF with a programmed scan pattern.
24-
* A coarse and fine scan are performed, using ISP's CDAF focus FoM to
25-
* estimate the lens position with peak contrast. This is slower due to
26-
* extra latency in the ISP, and requires a settling time between steps.
26+
* A coarse and fine scan are performed, using the ISP's CDAF contrast FoM
27+
* to estimate the lens position with peak contrast. (This is slower due to
28+
* extra latency in the ISP, and requires a settling time between steps.)
29+
* The scan may terminate early if PDAF recovers and allows the zero-phase
30+
* lens position to be interpolated.
2731
*
28-
* Some hysteresis is applied to the switch between PDAF and CDAF, to avoid
29-
* "nuisance" scans. During each interval where PDAF is not working, only
30-
* ONE scan will be performed; CAF cannot track objects using CDAF alone.
32+
* In CAF mode, the fallback to a CDAF scan is triggered when PDAF fails to
33+
* report high confidence and a configurable number of frames have elapsed
34+
* since the last image change since either PDAF was working or a previous
35+
* scan found peak contrast. Image changes are detected using both contrast
36+
* and AWB statistics (within the AF window[s]).
3137
*
38+
* IR lighting can interfere with the correct operation of PDAF, so we
39+
* optionally try to detect it (from AWB statistics).
3240
*/
3341

3442
namespace RPiController {
@@ -85,6 +93,8 @@ class Af : public AfAlgorithm
8593
double stepCoarse; /* used for scans */
8694
double stepFine; /* used for scans */
8795
double contrastRatio; /* used for scan termination and reporting */
96+
double retriggerRatio; /* contrast and RGB ratio for re-triggering */
97+
uint32_t retriggerDelay; /* frames of stability before re-triggering */
8898
double pdafGain; /* coefficient for PDAF feedback loop */
8999
double pdafSquelch; /* PDAF stability parameter (device-specific) */
90100
double maxSlew; /* limit for lens movement per frame */
@@ -103,6 +113,7 @@ class Af : public AfAlgorithm
103113
uint32_t confThresh; /* PDAF confidence cell min (sensor-specific) */
104114
uint32_t confClip; /* PDAF confidence cell max (sensor-specific) */
105115
uint32_t skipFrames; /* frames to skip at start or modeswitch */
116+
bool checkForIR; /* Set this if PDAF is unreliable in IR light */
106117
libcamera::ipa::Pwl map; /* converts dioptres -> lens driver position */
107118

108119
CfgParams();
@@ -131,6 +142,7 @@ class Af : public AfAlgorithm
131142
void invalidateWeights();
132143
bool getPhase(PdafRegions const &regions, double &phase, double &conf);
133144
double getContrast(const FocusRegions &focusStats);
145+
bool getAverageAndTestIr(const RgbyRegions &awbStats, double rgb[3]);
134146
void doPDAF(double phase, double conf);
135147
bool earlyTerminationByPhase(double phase);
136148
double findPeak(unsigned index) const;
@@ -152,15 +164,18 @@ class Af : public AfAlgorithm
152164
bool useWindows_;
153165
RegionWeights phaseWeights_;
154166
RegionWeights contrastWeights_;
167+
RegionWeights awbWeights_;
155168

156169
/* Working state. */
157170
ScanState scanState_;
158-
bool initted_;
171+
bool initted_, irFlag_;
159172
double ftarget_, fsmooth_;
160-
double prevContrast_;
173+
double prevContrast_, oldSceneContrast_;
174+
double prevAverage_[3], oldSceneAverage_[3];
161175
double prevPhase_;
162176
unsigned skipCount_, stepCount_, dropCount_;
163177
unsigned sameSignCount_;
178+
unsigned sceneChangeCount_;
164179
unsigned scanMaxIndex_;
165180
double scanMaxContrast_, scanMinContrast_;
166181
std::vector<ScanRecord> scanData_;

0 commit comments

Comments
 (0)