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:
@@ -24,9 +24,22 @@ static DeviceConfig g_cfg;
|
||||
static CVState g_cv;
|
||||
static SemaphoreHandle_t s_cv_mutex = nullptr;
|
||||
|
||||
// LED: simple on/off — blink patterns can be added later
|
||||
static void led_set(bool on) { digitalWrite(LED_PIN, on ? HIGH : LOW); }
|
||||
|
||||
// Non-blocking-ish detection blink. Saves and restores the current LED state
|
||||
// so it doesn't clobber upload/no-wifi indicators. Total duration: ~60ms per
|
||||
// pulse + 80ms gap between pulses.
|
||||
static void led_blink_pattern(int pulses) {
|
||||
bool prev = digitalRead(LED_PIN);
|
||||
for (int i = 0; i < pulses; i++) {
|
||||
led_set(true);
|
||||
vTaskDelay(pdMS_TO_TICKS(60));
|
||||
led_set(false);
|
||||
if (i < pulses - 1) vTaskDelay(pdMS_TO_TICKS(80));
|
||||
}
|
||||
led_set(prev);
|
||||
}
|
||||
|
||||
static void check_factory_reset() {
|
||||
if (digitalRead(BUTTON_PIN) != LOW) return;
|
||||
uint32_t held = millis();
|
||||
@@ -49,6 +62,8 @@ static void task_camera(void*) {
|
||||
if (r.entries_delta) Serial.printf("[CV] entry +%d (total %d)\n", r.entries_delta, g_cv.entries);
|
||||
if (r.exits_delta) Serial.printf("[CV] exit +%d (total %d)\n", r.exits_delta, g_cv.exits);
|
||||
xSemaphoreGive(s_cv_mutex);
|
||||
if (r.entries_delta) led_blink_pattern(1);
|
||||
if (r.exits_delta) led_blink_pattern(2);
|
||||
}
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(CAM_INTERVAL_MS));
|
||||
|
||||
Reference in New Issue
Block a user