From 0637b386bf41c8acaf24608c739908763a8f9fc3 Mon Sep 17 00:00:00 2001 From: Jeff Shee Date: Sat, 12 Sep 2026 13:42:20 +0900 Subject: [PATCH] fix(visaged): warm up with the IR emitter active so the first verify is not blown out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sensor auto-gain only adapts while streaming, drops in about two seconds when a quirked emitter saturates it, and rises slowly afterwards. The startup warmup streamed with the emitter off, so gain settled against ambient light and the first capture after every daemon start — the first sudo after boot or resume — was saturated white and failed detection. Activate the emitter before the warmup and run it as one continuous capture_frames stream. VISAGE_WARMUP_FRAMES now counts usable frames (the strobe's unlit half is skipped) and is documented; 25 works well on the camera below. Measured A/B on Syntek 174f:11a8, same starting state (gain adapted to ambient, emitter off), reproducing each path's exact capture sequence: unfixed first verify: detector gets frame means [33, 255, 33] (silhouette, white-out, silhouette); 3 s later [208, 206, 172] fixed first verify: detector gets [24, 24, 24], face crisply lit Co-Authored-By: Claude Fable 5.1 Signed-off-by: Jeff Shee --- crates/visaged/src/engine.rs | 11 +++++++---- docs/operations-guide.md | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/visaged/src/engine.rs b/crates/visaged/src/engine.rs index 274c649..b92e600 100644 --- a/crates/visaged/src/engine.rs +++ b/crates/visaged/src/engine.rs @@ -164,12 +164,15 @@ pub fn spawn_engine( None }; - // Discard warmup frames for camera AGC/AE stabilization + // Discard warmup frames for camera AGC/AE stabilization — with the emitter + // active, in one continuous stream. Auto-gain only adapts while streaming; + // warming up against ambient light leaves it high, and the first lit + // capture after start is then saturated white and fails detection. if warmup_frames > 0 { tracing::info!(count = warmup_frames, "discarding warmup frames"); - for _ in 0..warmup_frames { - let _ = camera.capture_frame(); - } + activate_emitter(&emitter); + let _ = camera.capture_frames(warmup_frames); + deactivate_emitter(&emitter); } let (tx, mut rx) = mpsc::channel::(4); diff --git a/docs/operations-guide.md b/docs/operations-guide.md index 298e992..e6a0f9e 100644 --- a/docs/operations-guide.md +++ b/docs/operations-guide.md @@ -322,6 +322,7 @@ Environment=VARIABLE=value | `VISAGE_VERIFY_TIMEOUT_SECS` | `10` | Max seconds for a verify attempt | | `VISAGE_FRAMES_PER_VERIFY` | `3` | Frames captured per authentication | | `VISAGE_FRAMES_PER_ENROLL` | `5` | Frames captured per enrollment | +| `VISAGE_WARMUP_FRAMES` | `4` | Usable frames discarded at daemon start, captured with the IR emitter active so auto-gain settles against it. Raise it if the first verify after start fails and later ones work. | | `VISAGE_EMITTER_ENABLED` | `1` | Set to `0` to disable IR emitter | | `VISAGE_LIVENESS_ENABLED` | `1` | Set to `0` to disable passive liveness detection (development only) | | `VISAGE_LIVENESS_MIN_DISPLACEMENT` | `0.8` | Minimum eye landmark displacement (px) for liveness check |