fix(cv): add per-direction crossing cooldown to suppress track-churn double-counts
When a blob briefly drops below CV_MIN_BLOB_PX, its track is killed and respawns, causing the same person to generate multiple counts per visit (~50/min observed in field). Add a per-direction cooldown (default 5 frames ≈ 0.8s @ 5 fps) that drops subsequent entries (or exits) within the window of the last counted one. Entry and exit cooldowns are tracked independently. Fixed at compile time for now; exposing as a server-push tunable is deferred until the server-push-config branch lands. See docs/server-prompt-crossing- cooldown.md for the server-side coordination notes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
78
docs/server-prompt-crossing-cooldown.md
Normal file
78
docs/server-prompt-crossing-cooldown.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Server-Side Prompt — Crossing Cooldown Awareness
|
||||
|
||||
> Give this to your server-side agent after the firmware commit that introduces
|
||||
> `CV_CROSSING_COOLDOWN_FRAMES` in `firmware/lib/cv/cv.h` has been flashed to devices.
|
||||
|
||||
## Context
|
||||
|
||||
The DoorCounter firmware now enforces a **per-direction crossing cooldown** in
|
||||
its CV pipeline. After a counted entry, subsequent entries within 5 frames
|
||||
(~1 second at 5 fps) are silently dropped on-device. Exits follow the same
|
||||
rule independently. This is a device-side fix for the known track-churn bug
|
||||
(single person producing 5+ counts per visit when their blob briefly drops
|
||||
below the minimum-blob-pixel threshold).
|
||||
|
||||
Constants live in `firmware/lib/cv/cv.h`:
|
||||
|
||||
```c++
|
||||
static const uint32_t CV_CROSSING_COOLDOWN_FRAMES = 5; // per-direction
|
||||
```
|
||||
|
||||
Cooldown is **fixed at compile time**. It is **not** currently a server-
|
||||
pushable tuning parameter.
|
||||
|
||||
## What the server should do
|
||||
|
||||
1. **Expect lower per-hour entry/exit counts** from devices running this
|
||||
firmware compared to their historical baseline. This reflects suppression
|
||||
of false positives, not a device regression. Do not alert on the drop.
|
||||
|
||||
2. **If you have a per-device tuning-config push mechanism** (the one planned
|
||||
in `.agent/plan.md` — server-push CV config via heartbeat response), add
|
||||
`cooldown_frames` to the sendable tuning set with:
|
||||
- Default: `5`
|
||||
- Valid range: `0..60` (0 disables cooldown; 60 ≈ 12s at 5 fps)
|
||||
- Semantics: per-direction, applied to both entries and exits
|
||||
- Persist alongside other CV tuning keys in NVS under a new key `cv_cool`.
|
||||
- Bump the `cfg_version` scheme accordingly.
|
||||
|
||||
**Do not ship this server-side change yet** — the firmware change in this
|
||||
commit keeps the cooldown as a compile-time constant. A future firmware
|
||||
commit on `feature/server-push-config` will expose it as a runtime
|
||||
tunable and bump `cfg_version`. Coordinate the rollout: firmware first,
|
||||
then server.
|
||||
|
||||
3. **Dashboard**: if you render device CV parameters in a per-device settings
|
||||
view, add a read-only row "Crossing cooldown (frames): 5" sourced from
|
||||
the firmware's compiled default. Mark it editable only once the firmware
|
||||
exposes it as a tunable.
|
||||
|
||||
4. **Telemetry (optional, low priority)**: consider adding a field
|
||||
`suppressed_crossings_delta` to the heartbeat or camera-events payload
|
||||
so operators can see how often cooldown is kicking in. This would require
|
||||
a firmware change; flag it as future work only if churn continues.
|
||||
|
||||
## What NOT to do
|
||||
|
||||
- Do not attempt to push `cooldown_frames` via the existing config channel
|
||||
today — the firmware will ignore unknown fields, which is fine, but
|
||||
shipping server changes that assume the firmware-side plumbing exists
|
||||
will break the integration contract.
|
||||
- Do not "correct" the lower counts via server-side multiplication. The
|
||||
cooldown is the correct behavior; old counts were inflated by the churn
|
||||
bug.
|
||||
|
||||
## Verification checklist
|
||||
|
||||
- [ ] Historical counts chart annotated with "firmware v{N} deployed"
|
||||
marker on the rollout date.
|
||||
- [ ] Per-device tuning view renders cooldown row (read-only for now).
|
||||
- [ ] No alert fires on the per-device count drop post-rollout.
|
||||
|
||||
## Reference
|
||||
|
||||
- Firmware change: `firmware/lib/cv/cv.h` (`CV_CROSSING_COOLDOWN_FRAMES`),
|
||||
`firmware/lib/cv/cv.cpp` (suppression logic in `cv_process`).
|
||||
- Design spec: `docs/superpowers/specs/2026-04-13-door-counter-design.md`
|
||||
§ 3.1 "Counting logic".
|
||||
- Unit test: `firmware/test/test_cv/test_cv.cpp::test_cooldown_suppresses_rapid_re_entry`.
|
||||
@@ -92,10 +92,12 @@ Capture → Grayscale → Downscale 96×96 → Frame diff → Threshold → Blob
|
||||
| Blob detect | Connected components; blobs < 8×8 px discarded as noise |
|
||||
| Centroid track | Nearest-centroid matching frame-to-frame (max 15px), tracks persist up to 10 missed frames |
|
||||
| Line crossing | Virtual horizontal line at configurable vertical position (default: 50% of frame height) |
|
||||
| Cooldown | Per-direction cooldown between counted crossings (default 5 frames ≈ 0.8s @ 5 fps) — suppresses duplicate counts from track churn |
|
||||
|
||||
**Counting logic:**
|
||||
- Centroid crosses line top→bottom = **entry**
|
||||
- Centroid crosses line bottom→top = **exit**
|
||||
- After a counted entry (resp. exit), subsequent entries (resp. exits) within `CV_CROSSING_COOLDOWN_FRAMES` are ignored. Entry and exit cooldowns are tracked independently, so an entry immediately followed by an exit (or vice versa) still counts both.
|
||||
|
||||
Counts accumulate as `{entries, exits}` in RAM and reset each hour on report.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user