feat(cv): directional once-per-track counting + detection LED blinks
A single person walking under the overhead camera was generating both an entry and an exit within a few seconds — the line-crossing logic treated a blob's traversal into one side of the frame and out the other as two separate events whenever the track spawned near the line, oscillated against shadows, or churned at creation. Replaced line-crossing semantics with directional traversal: - Each track records spawn_y at creation and a counted flag. - An event fires only if the track is not yet counted, spawned firm on one side of the line (|spawn_y - line_y| > CV_TRAVERSAL_MARGIN_PX), and is now firm on the opposite side. Direction of travel determines entry vs exit. The track is then flagged counted — one trip, one count. - Cooldown remains as a secondary safety net. main.cpp: single/double LED pulse on entry/exit detections. Saves and restores the current LED state so upload (yellow-on) and no-WiFi indicators aren't clobbered. Tests updated to walk blobs beyond the margin and register two new cases: wobble-at-line doesn't count, and a reversed full traversal doesn't double-count on the same track. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -92,12 +92,17 @@ 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 |
|
||||
| Directional traversal | Each track records its **spawn y** and fires **at most once**. An event fires only when the track's spawn position and current position are both ≥ `CV_TRAVERSAL_MARGIN_PX` (14 px) from the line and on opposite sides — i.e. a true traversal, not a wobble. |
|
||||
| Cooldown | Per-direction cooldown between counted events (default 5 frames ≈ 0.8s @ 5 fps) — safety net on top of directional logic |
|
||||
|
||||
**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.
|
||||
- Each track has a `spawn_y` (recorded at creation) and a `counted` flag.
|
||||
- An event fires only if the track is **not yet counted**, spawned **firm** on one side of the line (|spawn_y − line_y| > `CV_TRAVERSAL_MARGIN_PX`), and is **now firm** on the opposite side. On fire, the track is flagged counted — it will not produce another event for its lifetime.
|
||||
- Spawn firm above + now firm below = **entry**
|
||||
- Spawn firm below + now firm above = **exit**
|
||||
- Cooldown (per-direction, independent entries/exits) is a secondary gate: within `CV_CROSSING_COOLDOWN_FRAMES` of the last counted event in that direction, a new event is suppressed even if a different track's traversal would otherwise qualify.
|
||||
|
||||
**Rationale**: a single person traversing the doorway produces one track with a clear origin and destination — that's one count. Shadows that appear near the line and wobble, or tracks that churn at spawn, lack a firm origin and never count.
|
||||
|
||||
Counts accumulate as `{entries, exits}` in RAM and reset each hour on report.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user