From 6686cb4534451db62f579682792f24003a8b84b5 Mon Sep 17 00:00:00 2001 From: yair Date: Tue, 18 Nov 2025 01:12:52 +0200 Subject: [PATCH] fit --- gst/intervalometer/DEBUG.md | 559 ------------------------- gst/intervalometer/README.md | 150 ++++++- gst/intervalometer/gstintervalometer.c | 9 +- 3 files changed, 141 insertions(+), 577 deletions(-) delete mode 100644 gst/intervalometer/DEBUG.md diff --git a/gst/intervalometer/DEBUG.md b/gst/intervalometer/DEBUG.md deleted file mode 100644 index 9e02f8c..0000000 --- a/gst/intervalometer/DEBUG.md +++ /dev/null @@ -1,559 +0,0 @@ -# Intervalometer Flickering Debug Session - -## Problem Description - -**Original Pipeline (with flickering):** -```bash -gst-launch-1.0 ` - idsueyesrc config-file=ini/whole-presacler64_autoexp-binningx2_nightcolor2ms.ini ` - exposure=0.85 framerate=50 gain=0 name=cam device-id=2 num-buffers=200 ! ` - intervalometer enabled=true camera-element=cam update-interval=100 log-file=exposure_log.csv ! ` - videocrop bottom=3 ! queue ! videoconvert ! autovideosink -``` - -**Fixed Pipeline (for dawn/dusk time-lapse):** -```bash -gst-launch-1.0 ` - idsueyesrc config-file=ini/whole-presacler64_autoexp-binningx2.ini ` - exposure=0.85 framerate=50 gain=0 name=cam device-id=2 ! ` - intervalometer enabled=true camera-element=cam ` - ramp-rate=vslow ` - update-interval=1000 ` - log-file=timelapse.csv ! ` - videocrop bottom=3 ! queue ! videoconvert ! autovideosink -``` - -**Symptom:** Flickering/oscillation in video output - -**Original Configuration (too aggressive):** -- Framerate: 50 fps (20ms per frame) -- Update interval: 100ms (every 5 frames) ← too frequent -- Ramp rate: medium (20% steps) ← too large -- Result: Algorithm oscillates, visible flicker - -**Fixed Configuration (for dawn/dusk):** -- Framerate: 50 fps -- Update interval: 1000ms (every 50 frames) ← smooth -- Ramp rate: vslow (5% steps) ← gradual -- Result: Smooth convergence, no flicker - ---- - -## Hypothesis: Possible Root Causes - -### 1. **Update Interval Too Aggressive (HIGH PROBABILITY)** -- Update interval (100ms) might be too fast for the camera hardware -- At 50fps, this updates every 5 frames, potentially faster than camera can stabilize -- Camera might not finish applying previous exposure before next update arrives -- **Evidence needed:** Check camera response time vs update frequency - -### 2. **Missing Ramping Implementation (HIGH PROBABILITY)** -- Header defines `ramp_rate` and `ramp_step` but implementation may not be using them -- Abrupt exposure changes without smooth ramping would cause visible flicker -- Target exposure vs current exposure might be jumping instead of ramping -- **Evidence needed:** Verify ramping is actually applied in transform function - -### 3. **Thread Safety / Race Conditions (MEDIUM PROBABILITY)** -- Camera property updates from transform thread -- Frame processing happens in pipeline thread -- Potential race between reading current values and updating them -- **Evidence needed:** Check if property updates are synchronized - -### 4. **Initial Property Read Failure (MEDIUM PROBABILITY)** -- [`camera_src`](gst/intervalometer/gstintervalometer.h:76) reference might not get proper initial values -- If `current_exposure` and `current_gain` start at 0, first update could be a large jump -- **Evidence needed:** Verify initial property read from camera element - -### 5. **Update Timing Issues (LOW PROBABILITY)** -- `last_update_time` logic might allow updates closer than intended -- Multiple updates in quick succession during startup -- **Evidence needed:** Log actual time between updates - -### 6. **Brightness Calculation Instability (LOW PROBABILITY)** -- If brightness varies significantly frame-to-frame (noise), algorithm oscillates -- Compensation factor might be too aggressive -- **Evidence needed:** Log calculated brightness values per frame - -### 7. **Camera Hardware Lag (LOW PROBABILITY)** -- Exposure changes take effect 1-2 frames after being set -- Creates temporal mismatch between measurement and correction -- **Evidence needed:** Check IDS uEye documentation on exposure latency - ---- - -## Most Likely Root Causes - -Based on the configuration and typical auto-exposure issues: - -### Primary Suspect: **Ramping Not Implemented or Disabled** -- Instant exposure jumps between calculated values -- Would cause visible flicker especially with 100ms updates -- Need to verify: Is ramping code actually executing? - -### Secondary Suspect: **Update Interval Too Fast** -- 100ms = 5 frames at 50fps -- Camera hardware may need settling time -- Recommendation: Try 500-1000ms first to establish baseline - ---- - -## Diagnostic Logging Plan - -We need to add strategic logging to [`gstintervalometer.c`](gst/intervalometer/gstintervalometer.c) to validate our hypotheses: - -### Critical Log Points: - -1. **In transform function - Frame Processing:** - ```c - GST_DEBUG("Frame %lu: current_exp=%.3f target_exp=%.3f current_gain=%d target_gain=%d brightness=%.1f", - frame_count, current_exposure, target_exposure, current_gain, target_gain, avg_brightness); - ``` - -2. **In update algorithm - Before Ramping:** - ```c - GST_DEBUG("Update triggered: time_since_last=%.1fms desired_exp=%.3f desired_gain=%d", - time_delta_ms, calculated_exposure, calculated_gain); - ``` - -3. **In ramping logic - Step Application:** - ```c - GST_DEBUG("Ramping: step=%.3f current_exp=%.3f -> new_exp=%.3f (target=%.3f)", - ramp_step, current_exposure, new_current_exposure, target_exposure); - ``` - -4. **In property setter - Actual Camera Update:** - ```c - GST_DEBUG("Setting camera: exposure=%.3f gain=%d (changed=%d)", - exposure_value, gain_value, property_changed); - ``` - -5. **On camera element lookup:** - ```c - GST_DEBUG("Camera element '%s' found=%p initial_exp=%.3f initial_gain=%d", - camera_element_name, camera_src, initial_exposure, initial_gain); - ``` - -### Expected Outcomes: - -- **If ramping not working:** target_exp will jump but current_exp won't smooth towards it -- **If update too fast:** time_since_last will show values < 100ms or camera updates too frequent -- **If race condition:** frame logs will show inconsistent exposure values -- **If initial read fails:** initial values will be 0 or unrealistic - ---- - -## ✅ DIAGNOSIS CONFIRMED - -### **Root Cause: NO RAMPING IMPLEMENTATION** (Lines 689-693) - -The code has a **critical bug** in [`gst_intervalometer_update_camera_settings()`](gst/intervalometer/gstintervalometer.c:640): - -```c -/* Smooth ramping towards target */ -if (fabs (filter->current_exposure - filter->target_exposure) > 0.001) { - filter->current_exposure = filter->target_exposure; // ❌ INSTANT JUMP! - g_object_set (filter->camera_src, "exposure", filter->current_exposure, NULL); -} -``` - -**The Problem:** -1. The comment says "Smooth ramping" but the code does **instant jumps** -2. Line 690: `filter->current_exposure = filter->target_exposure` - immediately sets current = target -3. The [`ramp_step`](gst/intervalometer/gstintervalometer.h:94) variable is **never used** -4. Every 100ms, exposure jumps directly to the new calculated value -5. At 50fps, this causes **visible flickering every 5 frames** - -**What Should Happen:** -```c -// Gradual ramping (should be implemented like this): -gdouble step = (filter->target_exposure - filter->current_exposure) * 0.1; // 10% per update -filter->current_exposure += step; -``` - -This explains the flickering perfectly - the camera exposure is jumping abruptly every 100ms instead of smoothly ramping. - ---- - -## Confirming the Diagnosis (Optional) - -To validate this diagnosis before fixing, you could add temporary debug logging: - -```c -// Add at line 689 in gst_intervalometer_update_camera_settings(): -GST_WARNING_OBJECT (filter, "FLICKER DEBUG: brightness=%.1f target=%.1f | " - "current_exp=%.3f target_exp=%.3f delta=%.3f | frame=%lu", - brightness, filter->target_brightness, - filter->current_exposure, filter->target_exposure, - filter->target_exposure - filter->current_exposure, - filter->frame_count); -``` - -Run with: `$env:GST_DEBUG="intervalometer:3"; .\[your-pipeline-command]` - -Expected output showing instant jumps: -``` -intervalometer WARNING: FLICKER DEBUG: ... delta=0.035 | frame=5 -intervalometer WARNING: FLICKER DEBUG: ... delta=0.000 | frame=6 <- jumped instantly! -intervalometer WARNING: FLICKER DEBUG: ... delta=0.042 | frame=10 -intervalometer WARNING: FLICKER DEBUG: ... delta=0.000 | frame=11 <- jumped again! -``` - ---- - -## The Fix - -Replace lines 688-699 in [`gst_intervalometer_update_camera_settings()`](gst/intervalometer/gstintervalometer.c:688-699): - -```c -/* Apply smooth ramping using ramp_step */ -gdouble exp_delta = filter->target_exposure - filter->current_exposure; -gdouble gain_delta = filter->target_gain - filter->current_gain; - -/* Calculate ramp step based on ramp rate (percentage of delta per update) */ -filter->ramp_step = 0.1 * ramp_multiplier; // 10% base, scaled by ramp rate - -if (fabs(exp_delta) > 0.001) { - filter->current_exposure += exp_delta * filter->ramp_step; - g_object_set (filter->camera_src, "exposure", filter->current_exposure, NULL); - GST_DEBUG_OBJECT (filter, "Ramping exposure: %.3f -> %.3f (target %.3f, step %.1f%%)", - filter->current_exposure - (exp_delta * filter->ramp_step), - filter->current_exposure, filter->target_exposure, - filter->ramp_step * 100); -} - -if (gain_delta != 0) { - gdouble gain_step = gain_delta * filter->ramp_step; - if (fabs(gain_step) < 1.0) gain_step = (gain_delta > 0) ? 1.0 : -1.0; - filter->current_gain += (gint)gain_step; - g_object_set (filter->camera_src, "gain", filter->current_gain, NULL); - GST_DEBUG_OBJECT (filter, "Ramping gain: %d (target %d)", - filter->current_gain, filter->target_gain); -} -``` - -This will: -- Apply gradual 10% steps (base rate) multiplied by ramp_rate setting -- Smooth transitions over multiple frames -- Eliminate visible flickering -- Actually use the [`ramp_step`](gst/intervalometer/gstintervalometer.h:94) variable - ---- - -## Build & Test - -```powershell -# Build the fix -.\build.ps1 - -# Test with optimized dawn/dusk settings -gst-launch-1.0 ` - idsueyesrc config-file=ini/whole-presacler64_autoexp-binningx2.ini ` - exposure=0.85 framerate=50 gain=0 name=cam device-id=2 num-buffers=200 ! ` - intervalometer enabled=true camera-element=cam ` - ramp-rate=vslow ` - update-interval=1000 ` - log-file=timelapse.csv ! ` - videocrop bottom=3 ! queue ! videoconvert ! autovideosink - -# Check log for smooth transitions -cat timelapse.csv -``` - ---- - -## ✅ PRIMARY FIX IMPLEMENTED & TESTED - -### Ramping Fix Results - -**✅ RAMPING WORKS:** Smooth exposure transitions confirmed: -- Frame 0: 0.680ms → Frame 2: 0.544ms → Frame 4: 0.435ms → Frame 8: 0.279ms -- Clean 20% steps per update (medium ramp rate) - **NO MORE INSTANT JUMPS** -- **FLICKERING ELIMINATED** - no more visible flashes - -**About Brightness Variation:** -The brightness changing is **EXPECTED AUTO-EXPOSURE BEHAVIOR**, not a bug: -- Scene bright (239) vs target (128) → algorithm reduces exposure -- Brightness drops as exposure ramps down toward target -- This is convergence, not flickering - ---- - -## ⚠️ SECONDARY ISSUE: Exposure Range Query - -**Problem:** Frame 16 shows exposure overflow to massive values - -**Root Cause:** Using GObject property specs to query limits returns incorrect values: -- `exposure_min` = 0.000ms (impossible, causes math to break) -- `exposure_max` = DBL_MAX overflow - -**Solution:** Use IDS uEye SDK directly instead of GObject properties - -### Changes Made: - -1. **Added IDS SDK support to intervalometer:** - - Added `#include "../../sys/idsueye/include/ueye.h"` to header - - Added `HIDS hCam` field to store camera handle - - Linked against `${IDSUEYE_LIBRARIES}` in CMakeLists.txt - -2. **Added `hcam` property to idsueyesrc:** - - Exposes IDS camera handle via GObject property - - Allows intervalometer to call IDS SDK functions directly - -3. **Updated query function:** - - Now uses `is_Exposure(IS_EXPOSURE_CMD_GET_EXPOSURE_RANGE)` - - Gets proper min/max/increment values from camera hardware - -### To Complete the Fix: - -**The idsueyesrc.dll needs to be reloaded:** -1. Close any running GStreamer pipelines/processes -2. Rebuild using `.\build.ps1` to copy updated plugins -3. Or manually copy `build\sys\idsueye\Release\libgstidsueye.dll` to `GST_PLUGIN_PATH` - -**Current Status:** -- ✅ All code changes complete and compiled -- ✅ All plugins successfully copied to GST_PLUGIN_PATH -- ✅ Tested and VERIFIED - both fixes working perfectly! - ---- - -## ✅ COMPLETE FIX VERIFIED - -### Test Results with IDS SDK Exposure Range Query - -**Camera Handle Retrieved:** 0x1 -**Proper Exposure Range:** [0.019 - 19.943] ms (increment: 0.005 ms) - -**CSV Evidence - No More Overflow:** -``` -Frame 0: 0.772ms brightness 238 -Frame 3: 0.692ms brightness 228 -Frame 6: 0.612ms brightness 221 -Frame 9: 0.533ms brightness 212 -Frame 12: 0.453ms brightness 200 -Frame 15: 0.373ms brightness 184 -... -Frame 180: 0.267ms brightness 142 -Frame 190: 0.277ms brightness 146 -Frame 199: 0.282ms brightness 117 -``` - -**✅ All exposure values are proper** - no overflow, no huge numbers! - -### Both Issues Fixed: - -1. **Flickering (SOLVED):** - - Smooth 20% ramping per update - - No more instant jumps - - Visually smooth exposure transitions - -2. **Exposure Overflow (SOLVED):** - - Using `is_Exposure(IS_EXPOSURE_CMD_GET_EXPOSURE_RANGE)` from IDS SDK - - Proper min/max values queried from camera hardware - - All values stay within valid range [0.019 - 19.943] ms - -The intervalometer auto-exposure system is now fully functional! - ---- - -## ✅ BRIGHTNESS SMOOTHING IMPLEMENTED - -### Solution: Temporal Filtering with Exponential Moving Average - -To handle oscillations from moving objects (people, cars, birds), brightness temporal smoothing has been implemented. - -**How it works:** -- **Sample brightness:** Every frame (high temporal resolution) -- **Smooth brightness:** Exponential moving average (EMA) -- **Adjust exposure:** Based on smoothed brightness trend, not instantaneous values - -This filters out transient changes while staying responsive to actual lighting changes. - -### Implementation - -**New property added: `brightness-smoothing`** - -```c -/* Exponential Moving Average formula: */ -smoothed = (alpha × new_brightness) + ((1 - alpha) × smoothed_old) -``` - -**Parameters:** -- `brightness-smoothing=0.05` → Very heavy smoothing (5% new, 95% history) -- **`brightness-smoothing=0.1`** → **Heavy smoothing (default, recommended for time-lapse)** -- `brightness-smoothing=0.3` → Moderate smoothing -- `brightness-smoothing=0.5` → Light smoothing -- `brightness-smoothing=1.0` → No smoothing (instant response) - -**With brightness-smoothing=0.1:** -- Each new frame contributes 10% to the smoothed value -- Previous history contributes 90% -- Effectively averages over ~10 frames -- Moving objects cause minor ripples instead of large swings -- Background lighting trend still tracked accurately - -### Complete Recommended Pipeline for Dawn/Dusk: - -```bash -gst-launch-1.0 \ - idsueyesrc config-file=ini/whole-presacler64_autoexp-binningx2.ini \ - exposure=0.85 framerate=50 gain=0 name=cam device-id=2 ! \ - intervalometer enabled=true camera-element=cam \ - ramp-rate=vslow \ # 5% exposure steps - update-interval=1000 \ # 1 update/second - brightness-smoothing=0.1 \ # Filter moving objects - log-file=timelapse.csv ! \ - videocrop bottom=3 ! queue ! videoconvert ! autovideosink -``` - -**This configuration:** -- ✅ Samples brightness every frame (50 Hz) -- ✅ Smooths out transient brightness changes from moving objects -- ✅ Updates exposure slowly (1 Hz) based on smoothed brightness trend -- ✅ Ramps exposure gradually (5% steps) -- ✅ Results in smooth, stable time-lapse without visible oscillation - ---- - -## ✅ FIX IMPLEMENTED & BUILT - -### Build Results - -``` -✅ Compiled successfully using .\build.ps1 -✅ Plugin copied to GST_PLUGIN_PATH: libgstintervalometer.dll -``` - -The ramping fix has been applied to [`gstintervalometer.c:688-716`](gst/intervalometer/gstintervalometer.c:688-716). - -### Key Changes: -- Replaced instant `current = target` jumps with gradual ramping -- Now applies `filter->ramp_step` (10% × ramp_multiplier) per update -- Smooth transitions over multiple update cycles -- Proper use of the previously unused [`ramp_step`](gst/intervalometer/gstintervalometer.h:94) variable - ---- - -## Testing Instructions - -```powershell -# Test with the original flickering pipeline -gst-launch-1.0 ` - idsueyesrc config-file=ini/whole-presacler64_autoexp-binningx2_nightcolor2ms.ini ` - exposure=0.85 framerate=50 gain=0 name=cam device-id=2 num-buffers=20 ! ` - intervalometer enabled=true camera-element=cam update-interval=100 log-file=exposure_log.csv ! ` - videocrop bottom=3 ! queue ! videoconvert ! autovideosink -``` - -### Enable Debug Logging (Optional) -```powershell -$env:GST_DEBUG="intervalometer:5" -# Then run the pipeline to see ramping in action -``` - -### Verify Results - -1. **Visual inspection**: No more flickering in video output -2. **Log file analysis**: Check `exposure_log.csv` for smooth exposure curves -3. **Debug logs**: Should show progressive ramping instead of instant jumps - ---- - -## 🎯 Tuning for Dawn/Dusk Time-Lapse - -The current default settings (20% ramp, 100ms update) are **too aggressive for slow lighting changes** and cause visible oscillation. - -### Recommended Settings for Dawn/Dusk: - -```bash -# Slow, smooth transitions for time-lapse -intervalometer enabled=true camera-element=cam \ - ramp-rate=vslow \ # 5% steps - very gradual - update-interval=1000 \ # 1 second between updates - log-file=timelapse.csv -``` - -### Ramp Rate Reference: - -| Setting | Multiplier | Base Step | Actual Step (10% base) | Use Case | -|---------|-----------|-----------|------------------------|----------| -| **`vslow`** | **0.5** | **5%** | **0.5% per update** | **Dawn/dusk time-lapse** | -| `slow` | 1.0 | 10% | 1.0% per update | Time-lapse, slow changes | -| `medium` | 2.0 | 20% | 2.0% per update | General purpose (default) | -| `fast` | 4.0 | 40% | 4.0% per update | Quick adaptation | -| `vfast` | 8.0 | 80% | 8.0% per update | Very fast changes | - -**Note:** The base rate is 10% (`0.1` in code), which is then multiplied by the ramp_rate multiplier. - -### Update Interval Guidelines: - -| Interval | Updates/sec | Best For | -|----------|-------------|----------| -| 100ms | 10 Hz | Fast-changing scenes (clouds, indoor) | -| 500ms | 2 Hz | Moderate changes | -| **1000ms** | **1 Hz** | **Dawn/dusk time-lapse** | -| 2000ms | 0.5 Hz | Very slow lighting changes | -| 5000ms | 0.2 Hz | Extremely slow (sunset over horizon) | - -### Why This Matters: - -At 50fps with 100ms updates and 20% ramp: -- Updates every 5 frames -- Each update changes exposure by 20% of the delta -- Too fast for the algorithm to stabilize → oscillation - -For dawn/dusk with vslow + 1000ms: -- Updates every 50 frames (1 second) -- Each update changes by only 5% of delta -- Much smoother convergence without visible steps - -### Example Pipelines for Different Scenarios: - -```bash -# Dawn/Dusk Time-Lapse (RECOMMENDED) -intervalometer enabled=true camera-element=cam \ - ramp-rate=vslow update-interval=1000 \ - target-brightness=128 compensation=0.0 - -# Indoor/Variable Lighting -intervalometer enabled=true camera-element=cam \ - ramp-rate=medium update-interval=200 \ - target-brightness=128 - -# Fast-Changing Clouds -intervalometer enabled=true camera-element=cam \ - ramp-rate=fast update-interval=100 \ - target-brightness=128 -``` - ---- - -## Summary of All Fixes - -1. ✅ Fixed instant exposure jumps → implemented proper ramping -2. ✅ Fixed exposure range overflow → use IDS SDK queries -3. ✅ Added brightness temporal smoothing → filter moving objects -4. ✅ Comprehensive tuning guide for dawn/dusk time-lapse -5. ✅ All features tested and verified - -**Result:** Fully functional flicker-free auto-exposure system optimized for time-lapse photography. - ---- - -## Log Analysis Template - -When running with logs enabled, look for: - -``` -[ ] Initial camera element lookup successful -[ ] Initial exposure/gain read from camera matches CLI args -[ ] Update intervals are consistent (~100ms) -[ ] Ramping step values are non-zero -[ ] Exposure values change gradually, not in jumps -[ ] Target and current values converge smoothly -[ ] No property update failures/errors -``` - ---- - -*Debug session started: 2025-11-17* \ No newline at end of file diff --git a/gst/intervalometer/README.md b/gst/intervalometer/README.md index f98462b..09ddbe4 100644 --- a/gst/intervalometer/README.md +++ b/gst/intervalometer/README.md @@ -66,6 +66,13 @@ make install | Property | Type | Values | Default | Description | |----------|------|--------|---------|-------------| | `ramp-rate` | enum | VSlow, Slow, Medium, Fast, VFast | Medium | Speed of parameter changes | +| `update-interval` | int | 10-10000 | 100 | Time between exposure updates (ms) | + +### Brightness Filtering + +| Property | Type | Range | Default | Description | +|----------|------|-------|---------|-------------| +| `brightness-smoothing` | double | 0.0-1.0 | 0.1 | Temporal smoothing factor (EMA alpha) | ### Logging @@ -119,7 +126,28 @@ gst-launch-1.0 idsueyesrc name=cam ! \ videoconvert ! autovideosink ``` -### Complete Time-Lapse Pipeline +### Dawn/Dusk Time-Lapse (Recommended) + +Optimized settings for smooth sunrise/sunset time-lapse: + +```bash +gst-launch-1.0 \ + idsueyesrc config-file=ini/whole-presacler64_autoexp-binningx2.ini \ + exposure=0.85 framerate=50 gain=0 name=cam device-id=2 ! \ + intervalometer enabled=true camera-element=cam \ + ramp-rate=vslow \ + update-interval=1000 \ + brightness-smoothing=0.1 \ + log-file=timelapse.csv ! \ + videocrop bottom=3 ! queue ! videoconvert ! autovideosink +``` + +**Key settings:** +- `ramp-rate=vslow`: 5% exposure steps per update (smooth transitions) +- `update-interval=1000`: Updates every 1 second (not too aggressive) +- `brightness-smoothing=0.1`: Filters out moving objects (cars, people, birds) + +### Complete Time-Lapse Recording Record a time-lapse with auto-exposure: @@ -174,33 +202,77 @@ The filter finds and controls the upstream `idsueyesrc` element using the `camer Ensure your camera source is named and the name matches the `camera-element` property. -## Ramp Rates +## Configuration Reference -| Rate | Multiplier | Best For | -|------|------------|----------| -| VSlow | 0.5x | Very slow light changes, maximum smoothness | -| Slow | 1.0x | Gradual sunset/sunrise over hours | -| Medium | 2.0x | Normal time-lapse scenarios | -| Fast | 4.0x | Faster light changes, clouds passing | -| VFast | 8.0x | Quick adaptation, testing | +### Ramp Rates + +| Rate | Multiplier | Step Size | Best For | +|------|------------|-----------|----------| +| VSlow | 0.5x | 5% per update | Dawn/dusk time-lapse (recommended) | +| Slow | 1.0x | 10% per update | Gradual sunset/sunrise over hours | +| Medium | 2.0x | 20% per update | Normal time-lapse scenarios | +| Fast | 4.0x | 40% per update | Faster light changes, clouds passing | +| VFast | 8.0x | 80% per update | Quick adaptation, testing | + +**Note:** The base ramping rate is 10% of the delta between current and target values, multiplied by the ramp rate setting. + +### Update Intervals + +| Interval | Updates/sec | Best For | +|----------|-------------|----------| +| 100ms | 10 Hz | Fast-changing scenes (clouds, indoor) | +| 500ms | 2 Hz | Moderate changes | +| **1000ms** | **1 Hz** | **Dawn/dusk time-lapse (recommended)** | +| 2000ms | 0.5 Hz | Very slow lighting changes | +| 5000ms | 0.2 Hz | Extremely slow changes | + +**Important:** At high frame rates (50fps), avoid very short update intervals (< 500ms) to prevent oscillation and flickering. + +### Brightness Smoothing + +The `brightness-smoothing` property uses Exponential Moving Average (EMA) to filter out transient brightness changes from moving objects: + +``` +smoothed_brightness = (alpha × current_brightness) + ((1 - alpha) × previous_smoothed) +``` + +| Value | Behavior | Best For | +|-------|----------|----------| +| 0.05 | Very heavy smoothing | High traffic scenes | +| **0.1** | **Heavy smoothing (default)** | **Time-lapse with moving objects** | +| 0.3 | Moderate smoothing | Some filtering needed | +| 0.5 | Light smoothing | Quick response | +| 1.0 | No smoothing | Instant response to changes | + +**Effect:** With `brightness-smoothing=0.1`, the algorithm effectively averages brightness over ~10 frames, filtering out cars, people, and birds while still tracking slow lighting trends. ## Tips for Best Results -### Time-Lapse Settings +### Dawn/Dusk Time-Lapse (Recommended Configuration) ``` -exposure-min: 0.85 (or camera-specific minimum) -exposure-max: 1.24 (or 1/framerate to avoid motion blur) +ramp-rate: vslow (5% steps - very gradual) +update-interval: 1000 (1 second between updates) +brightness-smoothing: 0.1 (filter moving objects) +exposure-min: 0.85 (or camera minimum) +exposure-max: 1.24 (or 1/framerate) gain-min: 0 (cleanest image) -gain-max: 52 (or camera's limit) -ramp-rate: slow or medium -target-brightness: 128-140 +gain-max: 52 (or camera limit) +target-brightness: 128 ``` +**Why these settings:** +- `vslow` ramp rate prevents visible exposure jumps +- 1000ms update interval allows camera hardware to settle +- Brightness smoothing filters transient changes (cars, people) +- Results in smooth, flicker-free time-lapse + ### Fast Changing Conditions ``` ramp-rate: fast or vfast +update-interval: 100-500 +brightness-smoothing: 0.3-1.0 (more responsive) compensation: Adjust to preference (-1.0 for darker, +1.0 for brighter) ``` @@ -208,9 +280,18 @@ compensation: Adjust to preference (-1.0 for darker, +1.0 for brighter) ``` gain-max: 20-30 (lower max gain = less noise) -ramp-rate: slow (smoother transitions) +ramp-rate: slow or vslow (smoother transitions) +update-interval: 1000-2000 ``` +### Avoiding Flickering + +If you experience flickering or oscillation: +1. **Increase update-interval**: Start with 1000ms +2. **Decrease ramp-rate**: Use `vslow` or `slow` +3. **Enable brightness-smoothing**: Set to 0.1 or lower +4. **Check your settings**: At 50fps, 100ms updates = every 5 frames (too fast!) + ## Troubleshooting **Filter not adjusting exposure:** @@ -218,10 +299,22 @@ ramp-rate: slow (smoother transitions) - Check that camera allows runtime exposure/gain changes - Ensure `enabled=true` is set +**Flickering or oscillating exposure:** +- **Primary cause:** Update interval too fast for your frame rate +- **Solution:** Increase `update-interval` to 1000ms +- **Also try:** Set `ramp-rate=vslow` and `brightness-smoothing=0.1` +- **At 50fps:** Never use update intervals < 500ms + **Changes too fast/slow:** - Adjust `ramp-rate` property +- Modify `update-interval` (higher = slower convergence) - Check `exposure-min`/`exposure-max` range is appropriate +**Brightness oscillates with moving objects:** +- Enable `brightness-smoothing=0.1` to filter transients +- Lower values (0.05) provide even more smoothing +- This filters cars, people, birds while tracking lighting trends + **Brightness not reaching target:** - Increase `gain-max` to allow more gain - Increase `exposure-max` if not motion-limited @@ -241,6 +334,31 @@ ramp-rate: slow (smoother transitions) | Real-time | Script-based intervals | Frame-by-frame analysis | | Logging | CSV to SD card | CSV to filesystem | +## Technical Notes + +### Flickering Fix (2025) + +The original implementation had a critical bug where exposure ramping was not actually implemented - the code would instantly jump to target values instead of gradually ramping. This caused visible flickering, especially with short update intervals. + +**Fixed in [`gstintervalometer.c:688-716`](gst/intervalometer/gstintervalometer.c:688-716):** +- Implemented proper gradual ramping using the `ramp_step` variable +- Each update now applies a percentage of the delta (not instant jumps) +- Formula: `current_exposure += (target_exposure - current_exposure) × ramp_step` + +### Exposure Range Query Fix + +The original implementation used GObject property specs to query exposure limits, which returned incorrect values (0.0 min, DBL_MAX max). This has been fixed to use the IDS uEye SDK directly: + +**Changes made:** +- Added IDS SDK header include and camera handle support +- Added `hcam` property to `idsueyesrc` to expose camera handle +- Use `is_Exposure(IS_EXPOSURE_CMD_GET_EXPOSURE_RANGE)` for proper hardware limits +- Result: Proper min/max values (e.g., [0.019 - 19.943] ms) from camera + +### Brightness Smoothing + +Added Exponential Moving Average (EMA) filtering to handle transient brightness changes from moving objects (cars, people, birds). This prevents exposure oscillation while maintaining responsiveness to actual lighting changes. + ## License This filter is part of gst-plugins-vision and released under the GNU Library General Public License (LGPL). diff --git a/gst/intervalometer/gstintervalometer.c b/gst/intervalometer/gstintervalometer.c index d3612f7..12fd957 100644 --- a/gst/intervalometer/gstintervalometer.c +++ b/gst/intervalometer/gstintervalometer.c @@ -710,9 +710,12 @@ gst_intervalometer_update_camera_settings (GstIntervalometer * filter, gain_range = filter->gain_max - filter->gain_min; /* Determine target exposure and gain using YASS-like algorithm */ + /* Use tolerance for exposure comparisons to avoid asymptotic stalling */ + #define EXPOSURE_TOLERANCE 0.01 /* 0.01 ms tolerance */ + if (brightness > adjusted_target) { /* Too bright - decrease exposure first, then gain */ - if (filter->current_exposure > filter->exposure_min) { + if (filter->current_exposure > (filter->exposure_min + EXPOSURE_TOLERANCE)) { filter->target_exposure = filter->current_exposure - (exposure_range * 0.01 * ramp_multiplier); filter->target_exposure = MAX (filter->target_exposure, filter->exposure_min); @@ -722,7 +725,7 @@ gst_intervalometer_update_camera_settings (GstIntervalometer * filter, } } else { /* Too dark - increase exposure first up to max, then increase gain */ - if (filter->current_exposure < filter->exposure_max) { + if (filter->current_exposure < (filter->exposure_max - EXPOSURE_TOLERANCE)) { filter->target_exposure = filter->current_exposure + (exposure_range * 0.01 * ramp_multiplier); filter->target_exposure = MIN (filter->target_exposure, filter->exposure_max); @@ -731,6 +734,8 @@ gst_intervalometer_update_camera_settings (GstIntervalometer * filter, filter->target_gain = MIN (filter->target_gain, filter->gain_max); } } + + #undef EXPOSURE_TOLERANCE /* Apply smooth ramping using ramp_step */ gdouble exp_delta = filter->target_exposure - filter->current_exposure;