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:
@@ -15,6 +15,8 @@ void cv_init(CVState& state) {
|
||||
state.tracks.clear();
|
||||
state.entries = 0;
|
||||
state.exits = 0;
|
||||
state.last_entry_frame = 0;
|
||||
state.last_exit_frame = 0;
|
||||
}
|
||||
|
||||
void cv_reset_counts(CVState& state) {
|
||||
@@ -160,12 +162,22 @@ CVResult cv_process(CVState& state, const uint8_t* frame, uint8_t line_pct) {
|
||||
if (now_above != track.above_line) {
|
||||
if (!now_above) {
|
||||
// was above, now below → entry
|
||||
state.entries++;
|
||||
result.entries_delta++;
|
||||
bool in_cooldown = state.last_entry_frame != 0 &&
|
||||
(state.frame_index - state.last_entry_frame) < CV_CROSSING_COOLDOWN_FRAMES;
|
||||
if (!in_cooldown) {
|
||||
state.entries++;
|
||||
result.entries_delta++;
|
||||
state.last_entry_frame = state.frame_index;
|
||||
}
|
||||
} else {
|
||||
// was below, now above → exit
|
||||
state.exits++;
|
||||
result.exits_delta++;
|
||||
bool in_cooldown = state.last_exit_frame != 0 &&
|
||||
(state.frame_index - state.last_exit_frame) < CV_CROSSING_COOLDOWN_FRAMES;
|
||||
if (!in_cooldown) {
|
||||
state.exits++;
|
||||
result.exits_delta++;
|
||||
state.last_exit_frame = state.frame_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
track.above_line = now_above;
|
||||
|
||||
@@ -12,6 +12,12 @@ static const int CV_MIN_BLOB_PX = 64;
|
||||
static const float CV_MAX_MOVE = 15.0f;
|
||||
static const int CV_MAX_MISSED = 10;
|
||||
|
||||
// Per-direction crossing cooldown. Any same-direction crossing whose frame gap
|
||||
// is strictly less than this value is dropped. At 5 fps, a value of 5 → ≈0.8s
|
||||
// suppression window. Purpose: mask track churn (blob briefly drops below
|
||||
// min_blob_px, track dies & respawns, re-crosses).
|
||||
static const uint32_t CV_CROSSING_COOLDOWN_FRAMES = 5;
|
||||
|
||||
struct CVTrack {
|
||||
int id;
|
||||
float x, y;
|
||||
@@ -28,6 +34,8 @@ struct CVState {
|
||||
std::vector<CVTrack> tracks;
|
||||
int entries;
|
||||
int exits;
|
||||
uint32_t last_entry_frame; // 0 = never; frame_index of last counted entry
|
||||
uint32_t last_exit_frame; // 0 = never; frame_index of last counted exit
|
||||
};
|
||||
|
||||
struct CVResult {
|
||||
|
||||
Reference in New Issue
Block a user