grid_record: добавить --yw-mqtt + --yw-topic для magenta bbox от YOLO-World

Параллельный MQTT subscriber на отдельный topic (default yoloworld/events)
от yolo-world-detector сервиса. Использует те же --detection-cell что
Frigate, но рендерит bbox magenta цветом (Y=105 U=212 V=234) вместо
зелёного (Y=210 U=50 V=100).

На одной картинке композитор может одновременно показать:
- зелёный bbox от Frigate (person/car/...)
- magenta bbox от yolo-world (fox/dog/drone/...)

Backward compat 100% — без --yw-mqtt никаких изменений в поведении.

Архитектурно:
- Второй cfc_frigate_mqtt_t (тот же envelope schema через
  cfc_frigate_mqtt parser — yolo-world-detector публикует
  Frigate-compat events)
- Параллельный yw_detbox_overlays[] с magenta colors
- yolo-world subscriber не управляет motion-layout (composer=NULL),
  только Frigate шлёт motion-pulse'ы

Verify build:
  cmake --build build  # все 4 target'а (grid_record, simple_record,
                       # grid_record_cpp, cuframes_composer_static)

Live test пока через docker recreate в Phase 5 deploy, после того
как yolo-world publish'ит в реальный MQTT.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 18:32:17 +01:00
parent e54d55371c
commit ce7aa6cb8d
+85 -1
View File
@@ -127,6 +127,12 @@ int main(int argc, char **argv)
const char *frigate_mqtt_host = NULL;
int frigate_mqtt_port = 1883;
const char *frigate_topic = "frigate/events";
/* YOLO-World subscriber (Phase 3 yolo-world-detector) — параллельный
* detection-overlay поток. Использует те же detection-cells что и
* Frigate, но рендерит bbox magenta цветом. По умолчанию выключен. */
const char *yw_mqtt_host = NULL;
int yw_mqtt_port = 1883;
const char *yw_topic = "yoloworld/events";
const char *mqtt_overlays_path = NULL; /* JSON-конфиг MQTT-driven text overlays */
const char *initial_layout = NULL; /* --layout NAME → set_layout после init */
int motion_mode = 0; /* --motion-mode */
@@ -174,6 +180,8 @@ int main(int argc, char **argv)
{"audio-source", required_argument, 0, 'A'}, /* RTSP audio URL */
{"frigate-mqtt", required_argument, 0, 'G'}, /* host[:port] */
{"frigate-topic", required_argument, 0, 'T'},
{"yw-mqtt", required_argument, 0, 'Y'}, /* host[:port] для yolo-world detector */
{"yw-topic", required_argument, 0, 'Q'},
{"detection-cell", required_argument, 0, 'D'},
{"layout", required_argument, 0, 'L'}, /* named layout (quad, single, ...) */
{"source", required_argument, 0, 'S'}, /* pool source: key,frigate=...,priority=N */
@@ -185,7 +193,7 @@ int main(int argc, char **argv)
};
const char *templates_path = NULL;
int c;
while ((c = getopt_long(argc, argv, "o:c:f:b:W:H:s:r:i:t:C:M:I:U:P:RF:A:G:T:D:L:S:mk:z:x:", opts, NULL)) != -1) {
while ((c = getopt_long(argc, argv, "o:c:f:b:W:H:s:r:i:t:C:M:I:U:P:RF:A:G:T:Y:Q:D:L:S:mk:z:x:", opts, NULL)) != -1) {
switch (c) {
case 'o': out_path = optarg; break;
case 'c':
@@ -239,6 +247,21 @@ int main(int argc, char **argv)
break;
}
case 'T': frigate_topic = optarg; break;
case 'Y': {
yw_mqtt_host = optarg;
const char *colon = strchr(optarg, ':');
if (colon) {
static char yw_host_buf[64];
int n = colon - optarg;
if (n >= (int)sizeof(yw_host_buf)) n = sizeof(yw_host_buf) - 1;
memcpy(yw_host_buf, optarg, n);
yw_host_buf[n] = '\0';
yw_mqtt_host = yw_host_buf;
yw_mqtt_port = atoi(colon + 1);
}
break;
}
case 'Q': yw_topic = optarg; break;
case 'L': initial_layout = optarg; break;
case 'm': motion_mode = 1; break;
case 'k': motion_ttl = atoi(optarg); break;
@@ -564,6 +587,39 @@ int main(int argc, char **argv)
fprintf(stderr, "\n");
}
/* YOLO-World detection-box overlays — параллельный набор для второго
* subscriber'а. Magenta цвет (BT.709 limited Y=105 U=212 V=234). Те же
* detection-cells (camera/zones), но bbox рисуется magenta. На один
* frame можно увидеть зелёный bbox от Frigate И magenta от YOLO-World
* — если оба детектят. yolo-world-detector публикует в MQTT topic
* yoloworld/events/<camera> с Frigate-compat envelope. */
cfc_overlay_t *yw_detbox_overlays[MAX_CELLS] = { 0 };
if (yw_mqtt_host) {
for (int i = 0; i < num_detcells; i++) {
cfc_overlay_detbox_config_t yc = {
.camera_key = detcells[i].camera,
.detect_w = detcells[i].detect_w,
.detect_h = detcells[i].detect_h,
.cell_x = detcells[i].dx, .cell_y = detcells[i].dy,
.cell_w = detcells[i].dw, .cell_h = detcells[i].dh,
.thickness = 6,
.color_y = 105, .color_u = 212, .color_v = 234, /* magenta */
.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(&yc, &yw_detbox_overlays[i]) != 0) {
fprintf(stderr, "[grid_record] yw detbox create failed для '%s'\n",
detcells[i].camera);
continue;
}
cfc_composer_add_overlay(comp, yw_detbox_overlays[i]);
fprintf(stderr, "[grid_record] yw detbox '%s' → cell %s (magenta)\n",
detcells[i].camera, detcells[i].key);
}
}
/* Frigate MQTT subscriber: запускаем если есть detection-cells
* (overlay'ные bbox'ы) ИЛИ motion-mode (auto-layout drivers). */
cfc_frigate_mqtt_t *frigate = NULL;
@@ -586,6 +642,33 @@ int main(int argc, char **argv)
}
}
/* YOLO-World MQTT subscriber — параллельный поток detection-events с
* yoloworld/events/<camera>. Использует тот же envelope как Frigate
* (cfc_frigate_mqtt парсер совместим), но рендерит на yw_detbox_overlays
* (magenta). motion-pulse'ы НЕ шлёт (composer NULL), композитор
* управляется только Frigate motion-pulse'ами. */
cfc_frigate_mqtt_t *yw_mqtt = NULL;
if (yw_mqtt_host && num_detcells > 0) {
cfc_frigate_mqtt_config_t yc = {
.host = yw_mqtt_host, .port = yw_mqtt_port,
.username = mqtt_user, .password = mqtt_pass,
.topic = yw_topic,
.composer = NULL, /* yolo-world не управляет motion-layout */
};
if (cfc_frigate_mqtt_create(&yc, &yw_mqtt) == 0) {
for (int i = 0; i < num_detcells; i++) {
if (yw_detbox_overlays[i]) {
cfc_frigate_mqtt_register_overlay(yw_mqtt, yw_detbox_overlays[i]);
}
}
cfc_frigate_mqtt_start(yw_mqtt);
fprintf(stderr, "[grid_record] yw_mqtt started → %s:%d topic=%s\n",
yw_mqtt_host, yw_mqtt_port, yw_topic);
} else {
fprintf(stderr, "[grid_record] yw_mqtt create failed\n");
}
}
/* PNG иконки. */
for (int i = 0; i < num_icons; i++) {
cfc_overlay_png_config_t pc = {
@@ -795,6 +878,7 @@ int main(int argc, char **argv)
cfc_writer_close(wctx.writer);
if (frigate) cfc_frigate_mqtt_destroy(frigate);
if (yw_mqtt) cfc_frigate_mqtt_destroy(yw_mqtt);
if (audio) cfc_audio_destroy(audio);
if (ctl) cfc_control_destroy(ctl);
if (hpub) cfc_health_destroy(hpub);