From ac8653476943e1bd1cff155c7278d04a8f9ea33b Mon Sep 17 00:00:00 2001 From: Evgeny Demchenko Date: Wed, 3 Jun 2026 16:18:56 +0100 Subject: [PATCH] =?UTF-8?q?Phase=207=20#190:=20zone-filter=20=D0=B2=20Frig?= =?UTF-8?q?ate=20MQTT=20subscriber?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Frigate 0.17 schema отвергает objects.filters..required_zones ("Extra inputs not permitted" → safe mode), поэтому фильтрацию по зонам делаем на стороне composer'а. cfc_overlay_detbox_match_zones() сверяет after.current_zones события с whitelist'ом overlay'я. end-события вызываются безусловно (no-op если рамки нет). CLI: --detection-cell key,camera,dx,dy,dw,dh,detect_w,detect_h[,zone1:zone2:...] 9-е поле опционально (colon-separated). Без него filter выключен. Co-Authored-By: Claude Opus 4.7 --- examples/grid_record.c | 49 ++++++++++++++++++++++++----- include/cuframes_composer/overlay.h | 17 ++++++++++ src/frigate_mqtt.c | 26 ++++++++++++++- src/overlay.c | 41 ++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 8 deletions(-) diff --git a/examples/grid_record.c b/examples/grid_record.c index 635cc8f..dc20473 100644 --- a/examples/grid_record.c +++ b/examples/grid_record.c @@ -126,13 +126,21 @@ int main(int argc, char **argv) const char *frigate_mqtt_host = NULL; int frigate_mqtt_port = 1883; const char *frigate_topic = "frigate/events"; - /* --detection-cell key,camera,dx,dy,dw,dh,detect_w,detect_h + /* --detection-cell key,camera,dx,dy,dw,dh,detect_w,detect_h[,zone1:zone2:...] * key — символьное имя для логов (например "parking") * camera — Frigate camera_key для MQTT match'а ("parking_overview") * dx,dy,dw,dh — координаты ячейки composer'а на output frame - * detect_w,detect_h — Frigate detect.{width,height} (640,480) */ + * detect_w,detect_h — Frigate detect.{width,height} (640,480) + * zones (опц.) — colon-separated whitelist; если задан, события вне + * этих зон отбрасываются subscriber'ом (zone-filter + * заменяет Frigate-side objects.filters.required_zones + * который не работает в 0.17 schema). */ + #define DETCELL_ZONE_MAX 8 typedef struct { char key[32], camera[48]; - int dx, dy, dw, dh, detect_w, detect_h; } detcell_t; + int dx, dy, dw, dh, detect_w, detect_h; + char zone_storage[DETCELL_ZONE_MAX][32]; + const char *zone_ptrs[DETCELL_ZONE_MAX]; + int num_zones; } detcell_t; detcell_t detcells[MAX_CELLS] = { 0 }; int num_detcells = 0; @@ -217,7 +225,7 @@ int main(int argc, char **argv) case 'T': frigate_topic = optarg; break; case 'D': { if (num_detcells >= MAX_CELLS) { fprintf(stderr, "max %d detcells\n", MAX_CELLS); return 1; } - char buf[256]; strncpy(buf, optarg, sizeof(buf) - 1); buf[sizeof(buf)-1] = '\0'; + char buf[512]; strncpy(buf, optarg, sizeof(buf) - 1); buf[sizeof(buf)-1] = '\0'; char *p = buf, *q; q = strchr(p, ','); if (!q) { fprintf(stderr, "bad --detection-cell\n"); return 1; } *q = '\0'; strncpy(detcells[num_detcells].key, p, 31); p = q + 1; @@ -233,7 +241,28 @@ int main(int argc, char **argv) *q = '\0'; detcells[num_detcells].dh = atoi(p); p = q + 1; q = strchr(p, ','); if (!q) { fprintf(stderr, "bad --detection-cell\n"); return 1; } *q = '\0'; detcells[num_detcells].detect_w = atoi(p); p = q + 1; - detcells[num_detcells].detect_h = atoi(p); + /* detect_h — может быть последним полем или иметь ',' после. */ + q = strchr(p, ','); + if (q) { + *q = '\0'; detcells[num_detcells].detect_h = atoi(p); p = q + 1; + /* Парсим zones — colon-separated whitelist. */ + detcells[num_detcells].num_zones = 0; + char *z = p; + while (z && *z && detcells[num_detcells].num_zones < DETCELL_ZONE_MAX) { + char *sep = strchr(z, ':'); + int len = sep ? (int)(sep - z) : (int)strlen(z); + if (len > 31) len = 31; + int idx = detcells[num_detcells].num_zones; + memcpy(detcells[num_detcells].zone_storage[idx], z, len); + detcells[num_detcells].zone_storage[idx][len] = '\0'; + detcells[num_detcells].zone_ptrs[idx] = + detcells[num_detcells].zone_storage[idx]; + detcells[num_detcells].num_zones++; + z = sep ? sep + 1 : NULL; + } + } else { + detcells[num_detcells].detect_h = atoi(p); + } num_detcells++; break; } @@ -415,6 +444,8 @@ int main(int argc, char **argv) .color_y = 210, .color_u = 50, .color_v = 100, /* кислотно-зелёный */ .alpha = 240, .stale_ms = 8000, + .required_zones = detcells[i].num_zones ? detcells[i].zone_ptrs : NULL, + .required_zones_count = detcells[i].num_zones, }; if (cfc_overlay_create_detection_boxes(&dc, &detbox_overlays[i]) != 0) { fprintf(stderr, "[grid_record] detbox create failed для '%s'\n", @@ -422,10 +453,14 @@ int main(int argc, char **argv) continue; } cfc_composer_add_overlay(comp, detbox_overlays[i]); - fprintf(stderr, "[grid_record] detbox '%s' → cell %s (%d,%d %dx%d), detect %dx%d\n", + fprintf(stderr, "[grid_record] detbox '%s' → cell %s (%d,%d %dx%d), detect %dx%d, zones=%d", detcells[i].camera, detcells[i].key, detcells[i].dx, detcells[i].dy, detcells[i].dw, detcells[i].dh, - detcells[i].detect_w, detcells[i].detect_h); + detcells[i].detect_w, detcells[i].detect_h, + detcells[i].num_zones); + for (int z = 0; z < detcells[i].num_zones; z++) + fprintf(stderr, " %s", detcells[i].zone_storage[z]); + fprintf(stderr, "\n"); } /* Frigate MQTT subscriber (если задан --frigate-mqtt). */ diff --git a/include/cuframes_composer/overlay.h b/include/cuframes_composer/overlay.h index 2d281b5..5221bf2 100644 --- a/include/cuframes_composer/overlay.h +++ b/include/cuframes_composer/overlay.h @@ -149,6 +149,14 @@ typedef struct cfc_overlay_detbox_config { * Рекомендуется 5000-8000 мс (Frigate publish раз в минуту для stationary, * меньше при движении). */ int stale_ms; + + /* Список разрешённых zones — если задан, MQTT subscriber пропускает + * события у которых after.current_zones пусто либо не пересекается с + * этим списком. Это робастный фильтр когда Frigate 0.17 schema не даёт + * native objects.filters..required_zones (см. commit 1726137). + * NULL или пустой массив → принимать все события. */ + const char *const *required_zones; /* массив строк */ + int required_zones_count; } cfc_overlay_detbox_config_t; int cfc_overlay_create_detection_boxes( @@ -160,6 +168,15 @@ int cfc_overlay_create_detection_boxes( * правильный overlay по incoming event'у. */ const char *cfc_overlay_detbox_camera_key(cfc_overlay_t *ov); +/* Проверить пересечение current_zones события с required_zones overlay'я. + * - если required_zones пуст → всегда 1 (filter off) + * - если current_zones пуст → 0 (объект вне зон) + * - иначе 1 если хотя бы одна current ∈ required, 0 иначе + * Использует overlay subscriber для отсева street-флуда (см. commit 1726137). */ +int cfc_overlay_detbox_match_zones(cfc_overlay_t *ov, + const char *const *current_zones, + int n); + /* Upsert одного active детекта. * event_id — идентификатор Frigate event'а (для трекинга/end). * label — "car", "person", и т.п. (для будущего цветового кодирования). diff --git a/src/frigate_mqtt.c b/src/frigate_mqtt.c index fa26363..7090796 100644 --- a/src/frigate_mqtt.c +++ b/src/frigate_mqtt.c @@ -92,12 +92,13 @@ static void parse_event(cfc_frigate_mqtt_t *f, const char *payload) const char *type = jtype ? json_object_get_string(jtype) : "update"; struct json_object *jcam = NULL, *jid = NULL, *jlabel = NULL, *jbox = NULL, - *jft = NULL; + *jft = NULL, *jzones = NULL; json_object_object_get_ex(jafter, "camera", &jcam); json_object_object_get_ex(jafter, "id", &jid); json_object_object_get_ex(jafter, "label", &jlabel); json_object_object_get_ex(jafter, "box", &jbox); json_object_object_get_ex(jafter, "frame_time", &jft); + json_object_object_get_ex(jafter, "current_zones", &jzones); if (!jcam || !jid) { json_object_put(root); return; } const char *camera = json_object_get_string(jcam); @@ -109,6 +110,9 @@ static void parse_event(cfc_frigate_mqtt_t *f, const char *payload) atomic_fetch_add(&f->events_received, 1); if (!strcmp(type, "end")) { + /* end вызываем безусловно — даже если zone-filter отсеивает: если + * раньше был moment когда событие попало в zone и upsert состоялся, + * end должен убрать рамку. Если рамки нет — no-op. */ cfc_overlay_detbox_end(ov, event_id); json_object_put(root); return; @@ -129,6 +133,26 @@ static void parse_event(cfc_frigate_mqtt_t *f, const char *payload) const char *label = jlabel ? json_object_get_string(jlabel) : ""; double frame_time = jft ? json_object_get_double(jft) : 0.0; + /* Zone-filter — отсев street-флуда. Frigate 0.17 schema не даёт native + * objects.filters.required_zones (Pydantic extra-inputs-not-permitted → + * safe mode), поэтому фильтруем на стороне subscriber'а. */ + const char *zone_buf[16]; + int n_zones = 0; + if (jzones && json_object_is_type(jzones, json_type_array)) { + int len = (int)json_object_array_length(jzones); + if (len > 16) len = 16; + for (int i = 0; i < len; i++) { + struct json_object *jz = json_object_array_get_idx(jzones, i); + if (jz && json_object_is_type(jz, json_type_string)) { + zone_buf[n_zones++] = json_object_get_string(jz); + } + } + } + if (!cfc_overlay_detbox_match_zones(ov, zone_buf, n_zones)) { + json_object_put(root); + return; + } + cfc_overlay_detbox_upsert(ov, event_id, label, x1, y1, x2, y2, (int64_t)(frame_time * 1000)); diff --git a/src/overlay.c b/src/overlay.c index bcf4bad..2127220 100644 --- a/src/overlay.c +++ b/src/overlay.c @@ -30,6 +30,8 @@ /* Detection box — один active детект. event_id = key для upsert/end. */ #define CFC_DETBOX_MAX 16 +#define CFC_DETBOX_ZONE_MAX 8 +#define CFC_DETBOX_ZONE_NAME 32 typedef struct detbox_entry { char event_id[48]; /* "" = slot пустой */ char label[16]; @@ -43,6 +45,10 @@ typedef struct detbox_data { pthread_mutex_t mu; detbox_entry_t entries[CFC_DETBOX_MAX]; int count; + /* Локальная копия required_zones из cfg (cfg.required_zones — это + * указатель caller'а, может быть stack/temp). */ + char required_zones[CFC_DETBOX_ZONE_MAX][CFC_DETBOX_ZONE_NAME]; + int required_zones_count; } detbox_data_t; typedef struct png_data { @@ -586,6 +592,24 @@ int cfc_overlay_create_detection_boxes( if (d->cfg.stale_ms <= 0) d->cfg.stale_ms = 6000; pthread_mutex_init(&d->mu, NULL); + /* Скопировать required_zones — caller владеет источником, мы храним свою копию. */ + d->required_zones_count = 0; + if (cfg->required_zones && cfg->required_zones_count > 0) { + int n = cfg->required_zones_count; + if (n > CFC_DETBOX_ZONE_MAX) n = CFC_DETBOX_ZONE_MAX; + for (int i = 0; i < n; i++) { + if (!cfg->required_zones[i]) continue; + strncpy(d->required_zones[d->required_zones_count], + cfg->required_zones[i], CFC_DETBOX_ZONE_NAME - 1); + d->required_zones[d->required_zones_count][CFC_DETBOX_ZONE_NAME - 1] = '\0'; + d->required_zones_count++; + } + } + /* cfg.required_zones — внешний указатель, не сохраняем — getter обращается + * к локальному storage'у. */ + d->cfg.required_zones = NULL; + d->cfg.required_zones_count = d->required_zones_count; + *out = ov; return 0; } @@ -596,6 +620,23 @@ const char *cfc_overlay_detbox_camera_key(cfc_overlay_t *ov) return ov->u.detbox.camera_key; } +int cfc_overlay_detbox_match_zones(cfc_overlay_t *ov, + const char *const *current_zones, + int n) +{ + if (!ov || ov->type != CFC_OVERLAY_DETECTION_BOXES) return 0; + detbox_data_t *d = &ov->u.detbox; + if (d->required_zones_count == 0) return 1; /* фильтр выключен */ + if (n <= 0 || !current_zones) return 0; /* объект вне зон */ + for (int i = 0; i < n; i++) { + if (!current_zones[i]) continue; + for (int j = 0; j < d->required_zones_count; j++) { + if (!strcmp(current_zones[i], d->required_zones[j])) return 1; + } + } + return 0; +} + int cfc_overlay_detbox_upsert(cfc_overlay_t *ov, const char *event_id, const char *label, int x1, int y1, int x2, int y2,