diff --git a/README.md b/README.md index 3cc6b9a..a636cf5 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ udpsink host=10.81.2.183 port=5000 now moving to automatic exposure (see [gstintervalometer](gst\intervalometer\gstintervalometer.c)) ```pwsh uv run .\scripts\launch-ids.py ` - --config .\ini\2456x4pix-500top-cw.ini ` + --config .\ini\2456x4pix-500top-cw-extragain.ini ` --device-id 1 ` --framerate 750 ` --gain 52 ` @@ -49,11 +49,27 @@ uv run .\scripts\launch-ids.py ` intervalometer enabled=true camera-element=cam ` ramp-rate=vslow ` update-interval=1000 ` - gain-ma52 ` + gain=52 ` log-file=timelapse.csv ! ` videocrop bottom=3 ! queue ! udpsink host=10.81.2.183 port=5000 ``` +```pwsh +$env:GST_DEBUG="linescan:5"; + + gst-launch-1.0 idsueyesrc config-file=ini/roi-night.ini ` + exposure=5.25 framerate=200 gain=42 name=cam device-id=2 ! ` + intervalometer enabled=true camera-element=cam ` + ramp-rate=vslow ` + update-interval=1000 ` + gain-max=52 ` + log-file=timelapse.csv ! ` + videocrop bottom=3 ! ` + queue ! ` + linescan direction=vertical output-size=1900 ! ` + videoconvert ! autovideosink +``` + #### Receive and Display ```pwsh uv run .\scripts\recv_raw_rolling.py --display-fps 60 diff --git a/gst/intervalometer/README.md b/gst/intervalometer/README.md index 09ddbe4..eb6c312 100644 --- a/gst/intervalometer/README.md +++ b/gst/intervalometer/README.md @@ -73,6 +73,7 @@ make install | Property | Type | Range | Default | Description | |----------|------|-------|---------|-------------| | `brightness-smoothing` | double | 0.0-1.0 | 0.1 | Temporal smoothing factor (EMA alpha) | +| `brightness-deadband` | double | 0.0-50.0 | 10.0 | Deadband zone to prevent oscillation (0=disabled) | ### Logging @@ -138,6 +139,7 @@ gst-launch-1.0 \ ramp-rate=vslow \ update-interval=1000 \ brightness-smoothing=0.1 \ + brightness-deadband=10.0 \ log-file=timelapse.csv ! \ videocrop bottom=3 ! queue ! videoconvert ! autovideosink ``` @@ -146,6 +148,7 @@ gst-launch-1.0 \ - `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) +- `brightness-deadband=10.0`: Prevents oscillation by creating a stable zone ### Complete Time-Lapse Recording @@ -166,14 +169,15 @@ gst-launch-1.0 idsueyesrc name=cam framerate=1 ! \ ### Exposure Control Algorithm -The filter uses a YASS-inspired algorithm: +The filter uses a YASS-inspired algorithm with deadband control: 1. **Brightness Analysis**: Calculates average brightness of each frame -2. **Error Calculation**: Compares to target brightness (with compensation) -3. **Ramping Priority**: +2. **Deadband Check**: If brightness is within deadband zone, skip adjustments (prevents oscillation) +3. **Error Calculation**: Compares to target brightness (with compensation) +4. **Ramping Priority**: - When too bright: Decreases exposure first, then gain - When too dark: Increases exposure first (up to max), then gain -4. **Smooth Ramping**: Changes are gradual based on ramp-rate setting +5. **Smooth Ramping**: Changes are gradual based on ramp-rate setting ### Typical Behavior @@ -246,6 +250,29 @@ smoothed_brightness = (alpha × current_brightness) + ((1 - alpha) × previous_s **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. +### Brightness Deadband + +The `brightness-deadband` property creates a tolerance zone around the target brightness where no adjustments are made. This prevents oscillation caused by continuous micro-adjustments. + +**How it works:** +- When brightness is within ±deadband of target, no changes are made +- When brightness exceeds the deadband, normal adjustments resume +- Allows fast corrections when needed, prevents hunting when stable + +| Value | Behavior | Best For | +|-------|----------|----------| +| 0.0 | No deadband (disabled) | Maximum responsiveness (may oscillate) | +| 5.0 | Narrow deadband | Slow update rates (>500ms) | +| **10.0** | **Standard deadband (default)** | **Fast update rates (10-100ms), prevents oscillation** | +| 20.0 | Wide deadband | Very stable, less responsive | + +**Example:** +- With `target-brightness=128` and `brightness-deadband=10.0` +- No adjustments when brightness is between 118-138 +- Adjustments resume when brightness < 118 or > 138 + +**Important:** Higher deadband = more stability but less precision. Lower deadband = more precision but potential oscillation at fast update rates. + ## Tips for Best Results ### Dawn/Dusk Time-Lapse (Recommended Configuration) @@ -254,6 +281,7 @@ smoothed_brightness = (alpha × current_brightness) + ((1 - alpha) × previous_s ramp-rate: vslow (5% steps - very gradual) update-interval: 1000 (1 second between updates) brightness-smoothing: 0.1 (filter moving objects) +brightness-deadband: 10.0 (prevent oscillation) exposure-min: 0.85 (or camera minimum) exposure-max: 1.24 (or 1/framerate) gain-min: 0 (cleanest image) @@ -267,15 +295,18 @@ target-brightness: 128 - Brightness smoothing filters transient changes (cars, people) - Results in smooth, flicker-free time-lapse -### Fast Changing Conditions +### Fast Changing Conditions (with Fast Update Rates) ``` ramp-rate: fast or vfast -update-interval: 100-500 +update-interval: 10-100 (very fast updates) brightness-smoothing: 0.3-1.0 (more responsive) +brightness-deadband: 10.0-15.0 (ESSENTIAL to prevent oscillation) compensation: Adjust to preference (-1.0 for darker, +1.0 for brighter) ``` +**Critical:** When using fast update rates (10-100ms), `brightness-deadband` is ESSENTIAL to prevent oscillation. Without it, the algorithm will continuously overshoot and create flickering. + ### Maximum Image Quality ``` @@ -284,13 +315,15 @@ ramp-rate: slow or vslow (smoother transitions) update-interval: 1000-2000 ``` -### Avoiding Flickering +### Avoiding Flickering and Oscillation 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!) +1. **Enable deadband (MOST IMPORTANT)**: Set `brightness-deadband=10.0` or higher +2. **Increase update-interval**: Start with 1000ms for slow changes, or keep at 10-100ms with deadband for fast response +3. **Decrease ramp-rate**: Use `vslow` or `slow` +4. **Enable brightness-smoothing**: Set to 0.1 or lower + +**The New Solution:** With the `brightness-deadband` parameter, you can now use fast update rates (10ms) with fast ramp rates without oscillation! The deadband creates a stable zone that prevents continuous micro-adjustments. ## Troubleshooting @@ -300,10 +333,11 @@ If you experience flickering or oscillation: - 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 +- **Primary cause:** No deadband zone at fast update rates +- **Solution:** Set `brightness-deadband=10.0` (or higher) +- **Alternative:** Increase `update-interval` to 1000ms - **Also try:** Set `ramp-rate=vslow` and `brightness-smoothing=0.1` -- **At 50fps:** Never use update intervals < 500ms +- **New capability:** With deadband enabled, you CAN use fast update intervals (10-100ms) for rapid response without oscillation! **Changes too fast/slow:** - Adjust `ramp-rate` property @@ -359,6 +393,18 @@ The original implementation used GObject property specs to query exposure limits 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. +### Brightness Deadband (Anti-Oscillation) + +Added deadband control to prevent continuous micro-adjustments that cause oscillation. When brightness is within the deadband zone (default ±10 units), no adjustments are made. This allows: +- Fast update rates (10-100ms) without oscillation +- Rapid response when changes exceed deadband +- Stable operation at any ramp rate + +**Implementation in [`gstintervalometer.c:698-707`](gst/intervalometer/gstintervalometer.c:698-707):** +- Checks absolute error against deadband before making adjustments +- Skips exposure/gain changes when within tolerance +- Allows full-speed corrections when brightness significantly deviates + ## 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 12fd957..803e9f3 100644 --- a/gst/intervalometer/gstintervalometer.c +++ b/gst/intervalometer/gstintervalometer.c @@ -62,7 +62,8 @@ enum PROP_LOG_FILE, PROP_CAMERA_ELEMENT, PROP_UPDATE_INTERVAL, - PROP_BRIGHTNESS_SMOOTHING + PROP_BRIGHTNESS_SMOOTHING, + PROP_BRIGHTNESS_DEADBAND }; #define DEFAULT_PROP_ENABLED TRUE @@ -77,6 +78,7 @@ enum #define DEFAULT_PROP_CAMERA_ELEMENT "" #define DEFAULT_PROP_UPDATE_INTERVAL 100 /* Update every 100ms (10 Hz) */ #define DEFAULT_PROP_BRIGHTNESS_SMOOTHING 0.1 /* 10% new, 90% history - heavy smoothing for time-lapse */ +#define DEFAULT_PROP_BRIGHTNESS_DEADBAND 10.0 /* ±10 brightness units deadband zone */ /* GStreamer boilerplate */ #define gst_intervalometer_parent_class parent_class @@ -242,6 +244,14 @@ gst_intervalometer_class_init (GstIntervalometerClass * klass) 0.0, 1.0, DEFAULT_PROP_BRIGHTNESS_SMOOTHING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_BRIGHTNESS_DEADBAND, + g_param_spec_double ("brightness-deadband", "Brightness Deadband", + "Deadband zone around target brightness where no adjustments are made (0=disabled). " + "Creates a stable zone to prevent oscillation. " + "Recommended: 10.0 for fast update rates, 5.0 for slower rates, 0.0 to disable", + 0.0, 50.0, DEFAULT_PROP_BRIGHTNESS_DEADBAND, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /* Set element metadata */ gst_element_class_add_pad_template (gstelement_class, gst_static_pad_template_get (&gst_intervalometer_sink_template)); @@ -278,6 +288,7 @@ gst_intervalometer_init (GstIntervalometer * filter) filter->camera_element_name = g_strdup (DEFAULT_PROP_CAMERA_ELEMENT); filter->update_interval = DEFAULT_PROP_UPDATE_INTERVAL; filter->brightness_smoothing = DEFAULT_PROP_BRIGHTNESS_SMOOTHING; + filter->brightness_deadband = DEFAULT_PROP_BRIGHTNESS_DEADBAND; /* Initialize internal state */ filter->camera_src = NULL; @@ -350,6 +361,9 @@ gst_intervalometer_set_property (GObject * object, guint prop_id, case PROP_BRIGHTNESS_SMOOTHING: filter->brightness_smoothing = g_value_get_double (value); break; + case PROP_BRIGHTNESS_DEADBAND: + filter->brightness_deadband = g_value_get_double (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -399,6 +413,9 @@ gst_intervalometer_get_property (GObject * object, guint prop_id, case PROP_BRIGHTNESS_SMOOTHING: g_value_set_double (value, filter->brightness_smoothing); break; + case PROP_BRIGHTNESS_DEADBAND: + g_value_set_double (value, filter->brightness_deadband); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -690,6 +707,7 @@ gst_intervalometer_update_camera_settings (GstIntervalometer * filter, { gdouble error, adjusted_target, exposure_range, gain_range; gdouble ramp_multiplier; + gdouble abs_error; if (!filter->camera_src) { return; @@ -699,6 +717,14 @@ gst_intervalometer_update_camera_settings (GstIntervalometer * filter, error = (filter->target_brightness - brightness) * pow (2.0, filter->compensation); + /* Check deadband zone - if enabled and brightness is within tolerance, skip adjustments */ + abs_error = fabs(filter->target_brightness - brightness); + if (filter->brightness_deadband > 0.0 && abs_error < filter->brightness_deadband) { + GST_DEBUG_OBJECT (filter, "Within deadband zone (error=%.2f < %.2f), skipping adjustment", + abs_error, filter->brightness_deadband); + return; + } + /* Adjust target brightness based on error */ adjusted_target = filter->target_brightness + error; diff --git a/gst/intervalometer/gstintervalometer.h b/gst/intervalometer/gstintervalometer.h index 9016bbe..f46fbe6 100644 --- a/gst/intervalometer/gstintervalometer.h +++ b/gst/intervalometer/gstintervalometer.h @@ -75,6 +75,7 @@ struct _GstIntervalometer gchar *camera_element_name; /* Name of upstream idsueyesrc element */ guint update_interval; /* Update interval in milliseconds */ gdouble brightness_smoothing; /* Brightness smoothing factor (0-1, 0=no smoothing) */ + gdouble brightness_deadband; /* Deadband zone to prevent oscillation (0=disabled) */ /* Internal state */ GstElement *camera_src; /* Reference to upstream camera element */