Phase 7 #190: zone-filter в Frigate MQTT subscriber
Frigate 0.17 schema отвергает objects.filters.<obj>.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 <noreply@anthropic.com>
This commit is contained in:
+41
-6
@@ -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;
|
||||
/* detect_h — может быть последним полем или иметь ',<zones>' после. */
|
||||
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). */
|
||||
|
||||
@@ -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.<obj>.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", и т.п. (для будущего цветового кодирования).
|
||||
|
||||
+25
-1
@@ -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));
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user