Phase 11b: detection-box bbox следует за камерой при смене layout

User: "при движении объект оборачивается в рамку, но функционал не
учитывает что сетки могут переключаться и координаты ячеек меняются".

Был баг: detbox-overlay хранил cell_x/y/w/h из --detection-cell CLI
(заданы при старте), при смене layout рамки рисовались по старым
координатам — мимо камеры.

Изменения:
  - overlay.h/.c: новый API cfc_overlay_detbox_set_cell_geom(ov,x,y,w,h).
    Mutex-защищённое обновление detbox config'а — composer вызывает
    перед каждым draw.
  - CameraCell: добавлено поле source_key (хранит cuframes-key камеры,
    рендерящейся в этом cell). Layout::apply передаёт его из pool entry.
  - Layout::find_camera_cell_rect(key) — возвращает Rect текущей cell
    для камеры с заданным cuframes-key (или nullptr если её нет в layout).
  - SourcePool::by_frigate_camera(name) — lookup pool-entry по
    Frigate-camera-key (frigate event'ы приходят с этим именем).
  - Composer::compose_frame: перед draw каждого DETECTION_BOXES overlay'я
    — lookup frigate→cuframes_key→layout cell rect, обновляет detbox geom.
    Если камера не в layout сейчас — cell_w/h=0, detbox draw skip'ает.

Теперь bbox от Frigate переезжает за камерой:
  - tpl_1 → bbox в full screen 1920×1080
  - tpl_3 → bbox в main 1440×810
  - tpl_4 → bbox в quad ячейке 960×540
  - камера не в layout → bbox скрыт

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-04 10:25:25 +01:00
parent 88fa73f922
commit d8e69c6392
8 changed files with 75 additions and 3 deletions
@@ -17,21 +17,25 @@
#include "../source.h"
#include "cell.hpp"
#include <string>
namespace cfc {
class CameraCell : public Cell {
public:
CameraCell(const Rect& geom, cfc_source_t* source)
: Cell(geom), source_(source) {}
CameraCell(const Rect& geom, cfc_source_t* source, std::string source_key = {})
: Cell(geom), source_(source), source_key_(std::move(source_key)) {}
void set_source(cfc_source_t* src) noexcept { source_ = src; }
cfc_source_t* source() const noexcept { return source_; }
const std::string& source_key() const noexcept { return source_key_; }
protected:
void draw_content(CUstream stream, NV12Ref& dst) override;
private:
cfc_source_t* source_; /* non-owning — pool владеет */
std::string source_key_;
};
} // namespace cfc
+5
View File
@@ -47,6 +47,11 @@ public:
const std::string& name() const noexcept { return current_name_; }
int cell_count() const noexcept { return static_cast<int>(cells_.size()); }
/* Найти текущий pixel-rect для камеры с заданным cuframes-key. NULL
* если этой камеры в layout сейчас нет. Используется detbox-overlay'ями
* для пересчёта bbox при смене layout. */
const Rect* find_camera_cell_rect(const std::string& source_key) const;
private:
std::vector<std::unique_ptr<Cell>> cells_;
std::string current_name_;
@@ -73,6 +73,7 @@ public:
int size() const { return static_cast<int>(entries_.size()); }
PoolEntry* by_index(int i) { return i >= 0 && i < size() ? entries_[i].get() : nullptr; }
PoolEntry* by_key(const std::string& key);
PoolEntry* by_frigate_camera(const std::string& frigate_camera);
/* Уведомить о motion (вызывается из Frigate MQTT subscriber'а через
* C-shim). Если zone-filter задан — проверяет пересечение. */
+7
View File
@@ -176,6 +176,13 @@ int cfc_overlay_create_detection_boxes(
* правильный overlay по incoming event'у. */
const char *cfc_overlay_detbox_camera_key(cfc_overlay_t *ov);
/* Обновить cell-геометрию runtime (при смене layout композитора). Композитор
* вызывает перед draw каждым detbox-overlay'ем — пересчитывает положение
* рамки под текущую позицию камеры. */
int cfc_overlay_detbox_set_cell_geom(cfc_overlay_t *ov,
int cell_x, int cell_y,
int cell_w, int cell_h);
/* Проверить пересечение current_zones события с required_zones overlay'я.
* - если required_zones пуст → всегда 1 (filter off)
* - если current_zones пуст → 0 (объект вне зон)
+20
View File
@@ -362,6 +362,26 @@ NV12Ref Composer::compose_frame()
/* Backward-compat overlays (CLI text/icon, detbox) — поверх Layout. */
for (auto* ov : overlays_) {
if (!ov) continue;
/* Detection box рисуется в координатах cell камеры. Cell может
* перемещаться по экрану при смене layout — синхронизируем cell-geom
* перед каждым draw. */
if (cfc_overlay_get_type(ov) == CFC_OVERLAY_DETECTION_BOXES) {
const char* fcam = cfc_overlay_detbox_camera_key(ov);
if (fcam) {
PoolEntry* e = pool_.by_frigate_camera(fcam);
if (e) {
const Rect* r = layout_.find_camera_cell_rect(e->cuframes_key);
if (r) {
cfc_overlay_detbox_set_cell_geom(ov, r->x, r->y, r->w, r->h);
} else {
/* Камеры нет в текущем layout — скрываем рамки. */
cfc_overlay_detbox_set_cell_geom(ov, 0, 0, 0, 0);
}
}
}
}
cfc_overlay_draw(ov, reinterpret_cast<CUstream>(stream_),
y, pitch_y_, uv, pitch_uv_,
cfg_.width, cfg_.height);
+13 -1
View File
@@ -45,7 +45,8 @@ void Layout::apply(const LayoutTemplate& tpl,
for (std::size_t i = 0; i < camera_templates.size(); ++i) {
Rect r = to_pixels(*camera_templates[i], frame_w, frame_h);
if (i < active_sorted.size() && active_sorted[i] && active_sorted[i]->source) {
auto cell = std::make_unique<CameraCell>(r, active_sorted[i]->source);
auto cell = std::make_unique<CameraCell>(r, active_sorted[i]->source,
active_sorted[i]->cuframes_key);
cell->add_decoration(std::make_unique<BorderDecoration>(border_style));
/* Label с именем камеры и приоритетом. */
char label_buf[96];
@@ -79,4 +80,15 @@ void Layout::render(CUstream stream, NV12Ref& dst)
for (auto& c : cells_) c->draw(stream, dst);
}
const Rect* Layout::find_camera_cell_rect(const std::string& source_key) const
{
for (auto& c : cells_) {
auto* cc = dynamic_cast<const CameraCell*>(c.get());
if (cc && cc->source_key() == source_key) {
return &cc->geometry();
}
}
return nullptr;
}
} // namespace cfc
+8
View File
@@ -77,6 +77,14 @@ PoolEntry* SourcePool::by_key(const std::string& key)
return nullptr;
}
PoolEntry* SourcePool::by_frigate_camera(const std::string& fcam)
{
for (auto& e : entries_) {
if (e->frigate_camera == fcam) return e.get();
}
return nullptr;
}
void SourcePool::motion_pulse(const std::string& frigate_camera,
const std::vector<std::string>& current_zones)
{
+15
View File
@@ -647,6 +647,21 @@ const char *cfc_overlay_detbox_camera_key(cfc_overlay_t *ov)
return ov->u.detbox.camera_key;
}
int cfc_overlay_detbox_set_cell_geom(cfc_overlay_t *ov,
int cell_x, int cell_y,
int cell_w, int cell_h)
{
if (!ov || ov->type != CFC_OVERLAY_DETECTION_BOXES) return -1;
detbox_data_t *d = &ov->u.detbox;
pthread_mutex_lock(&d->mu);
d->cfg.cell_x = cell_x;
d->cfg.cell_y = cell_y;
d->cfg.cell_w = cell_w;
d->cfg.cell_h = cell_h;
pthread_mutex_unlock(&d->mu);
return 0;
}
int cfc_overlay_detbox_match_zones(cfc_overlay_t *ov,
const char *const *current_zones,
int n)