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:
@@ -135,6 +135,39 @@ void test_blob_crossing_line_bottom_to_top_is_exit() {
|
||||
TEST_ASSERT_EQUAL_INT(1, r.exits_delta);
|
||||
}
|
||||
|
||||
void test_cooldown_suppresses_rapid_re_entry() {
|
||||
// Set up post-crossing state with a pre-existing track just above the line.
|
||||
// Force a second above→below crossing within the cooldown window; second
|
||||
// must not increment entries. Then advance past cooldown; third crossing must.
|
||||
CVState state;
|
||||
cv_init(state);
|
||||
state.bg_valid = true;
|
||||
memset(state.background, 100, CV_PIXELS);
|
||||
state.frame_index = 100;
|
||||
state.entries = 1;
|
||||
state.last_entry_frame = 100;
|
||||
|
||||
CVTrack t;
|
||||
t.id = 1; t.x = 48; t.y = 40; t.above_line = true; t.missed = 0;
|
||||
state.tracks.push_back(t);
|
||||
|
||||
// Frame 101: blob at y=52 (below line=48). Track matches (delta=12 < CV_MAX_MOVE).
|
||||
// Crossing above→below occurs but cooldown (101-100=1 < 5) suppresses it.
|
||||
uint8_t f1[CV_PIXELS]; make_blob_frame(f1, 48, 52);
|
||||
CVResult r1 = cv_process(state, f1, 50);
|
||||
TEST_ASSERT_EQUAL_INT(0, r1.entries_delta);
|
||||
TEST_ASSERT_EQUAL_INT(1, state.entries);
|
||||
|
||||
// Advance past cooldown, reset track above the line, then cross again.
|
||||
state.frame_index = 200;
|
||||
state.tracks[0].y = 40;
|
||||
state.tracks[0].above_line = true;
|
||||
uint8_t f2[CV_PIXELS]; make_blob_frame(f2, 48, 52);
|
||||
CVResult r2 = cv_process(state, f2, 50);
|
||||
TEST_ASSERT_EQUAL_INT(1, r2.entries_delta);
|
||||
TEST_ASSERT_EQUAL_INT(2, state.entries);
|
||||
}
|
||||
|
||||
void test_no_crossing_same_side_no_count() {
|
||||
CVState state;
|
||||
cv_init(state);
|
||||
@@ -162,5 +195,6 @@ int main() {
|
||||
RUN_TEST(test_blob_crossing_line_top_to_bottom_is_entry);
|
||||
RUN_TEST(test_blob_crossing_line_bottom_to_top_is_exit);
|
||||
RUN_TEST(test_no_crossing_same_side_no_count);
|
||||
RUN_TEST(test_cooldown_suppresses_rapid_re_entry);
|
||||
return UNITY_END();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user