feat: CV line-crossing entry/exit detection with tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -82,6 +82,76 @@ void test_tracking_spawns_track_for_new_blob() {
|
||||
TEST_ASSERT_FLOAT_WITHIN(5.0f, 20.0f, state.tracks[0].y);
|
||||
}
|
||||
|
||||
static void make_blob_frame(uint8_t* f, int cx, int cy) {
|
||||
fill_frame(f, 100);
|
||||
for (int y = cy - 12; y <= cy + 12; y++)
|
||||
for (int x = cx - 12; x <= cx + 12; x++)
|
||||
if (y >= 0 && y < CV_H && x >= 0 && x < CV_W)
|
||||
f[y * CV_W + x] = 200;
|
||||
}
|
||||
|
||||
void test_blob_crossing_line_top_to_bottom_is_entry() {
|
||||
CVState state;
|
||||
cv_init(state);
|
||||
|
||||
// Line at 50% = y=48; step ≤14px per frame to stay within CV_MAX_MOVE
|
||||
uint8_t bg[CV_PIXELS];
|
||||
fill_frame(bg, 100);
|
||||
cv_process(state, bg, 50); // init background
|
||||
|
||||
// Walk blob from y=20 toward line; crossing occurs at y=48 (above→below)
|
||||
// Stop at crossing frame and assert its result
|
||||
int setup[] = {20, 34};
|
||||
for (int i = 0; i < 2; i++) {
|
||||
uint8_t f[CV_PIXELS]; make_blob_frame(f, 48, setup[i]);
|
||||
cv_process(state, f, 50);
|
||||
}
|
||||
uint8_t fcross[CV_PIXELS]; make_blob_frame(fcross, 48, 48);
|
||||
CVResult r = cv_process(state, fcross, 50);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(1, r.entries_delta);
|
||||
TEST_ASSERT_EQUAL_INT(0, r.exits_delta);
|
||||
TEST_ASSERT_EQUAL_INT(1, state.entries);
|
||||
}
|
||||
|
||||
void test_blob_crossing_line_bottom_to_top_is_exit() {
|
||||
CVState state;
|
||||
cv_init(state);
|
||||
|
||||
uint8_t bg[CV_PIXELS]; fill_frame(bg, 100);
|
||||
cv_process(state, bg, 50);
|
||||
|
||||
// Walk blob from y=76 toward line; crossing occurs at y=34 (below→above)
|
||||
// Stop at crossing frame and assert its result
|
||||
int setup[] = {76, 62, 48};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
uint8_t f[CV_PIXELS]; make_blob_frame(f, 48, setup[i]);
|
||||
cv_process(state, f, 50);
|
||||
}
|
||||
uint8_t fcross[CV_PIXELS]; make_blob_frame(fcross, 48, 34);
|
||||
CVResult r = cv_process(state, fcross, 50);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, r.entries_delta);
|
||||
TEST_ASSERT_EQUAL_INT(1, r.exits_delta);
|
||||
}
|
||||
|
||||
void test_no_crossing_same_side_no_count() {
|
||||
CVState state;
|
||||
cv_init(state);
|
||||
|
||||
uint8_t bg[CV_PIXELS]; fill_frame(bg, 100);
|
||||
cv_process(state, bg, 50);
|
||||
|
||||
uint8_t f1[CV_PIXELS]; make_blob_frame(f1, 48, 20); // above line
|
||||
cv_process(state, f1, 50);
|
||||
|
||||
uint8_t f2[CV_PIXELS]; make_blob_frame(f2, 48, 30); // still above line, moved closer
|
||||
CVResult r = cv_process(state, f2, 50);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, r.entries_delta);
|
||||
TEST_ASSERT_EQUAL_INT(0, r.exits_delta);
|
||||
}
|
||||
|
||||
int main() {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_frame_diff_no_change_gives_no_fg);
|
||||
@@ -89,5 +159,8 @@ int main() {
|
||||
RUN_TEST(test_cv_init_clears_state);
|
||||
RUN_TEST(test_cv_reset_counts);
|
||||
RUN_TEST(test_tracking_spawns_track_for_new_blob);
|
||||
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);
|
||||
return UNITY_END();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user