Brainrot_Muxa/docs/IMPROVEMENT_PLAN.md
Данил Омелечко 3c59e1f87a аугментации бенчмарка, зонный пол в колее, фильтр платформы, экспорт RViz
- tools/make_benchmark.py: аугментации спавна d_start ∈ [40, 200] м, боковой дрейф
  v_lat, шум продольной координаты δs (устранение инверсии s_std), сценарии стоянки;
- flyguard/lobula.py: зонный пол h_lo_core = 0.16 м в межрельсовой колее,
  динамическое расширение габарита в кривых W_eff(d) по Corridor.sigma(d),
  отсечение плоскости настила платформы;
- flyguard/central_complex.py: поддержка лежащих препятствий в колее без
  штрафа за вытянутость формы;
- flyguard/synth.py: добавлен класс «человек_лежа» (1.8×0.5×0.3 м);
- flyguard/descending.py: дальний мягкий канал предупреждения на дистанциях >90 м;
- flyguard/export.py: экспорт детекций в 3D BBox, уровни угрозы, маркеры RViz MarkerArray;
- tests/test_pipeline.py, tests/run_tests.py: юнит-тесты и автономный раннер.
2026-09-22 21:09:00 +03:00

24 KiB
Raw Blame History

Комплексный план развития FlyGuard · Хакатон ЛЦТ-2026 (Кейс 05)

Документ фиксирует согласованную архитектурную стратегию и детальный план доработок ядра FlyGuard для максимизации качества решения на хакатоне ЛЦТ-2026 («Обнаружение посторонних объектов в тоннеле метро по данным 3D-лидара»).


1. Контекст и текущее состояние

Система FlyGuard построена на принципах зрительной системы и грибовидных тел дрозофилы:

облако точек 0.3–0.9 млн, 10 Гц
   │
   ├─ RETINA           омматидиальная решётка → дальностный образ 128 × N
   ├─ HALTERES         плоскость рельсов: крен, тангаж, высота сенсора
   ├─ LAMINA           диспаритет 1/R → ON/OFF, центр-окружение на 3 масштабах
   ├─ MEDULLA / LP     T4/T5 → LPTC: скорость поезда без одометрии
   ├─ LOBULA           LC11: кандидаты; разрез компоненты по контрасту
   ├─ MUSHROOM BODY    KC → APL → MBON: новизна формы (без меток)
   │                   + обученное считывание MBON (с метками из физики)
   ├─ FAN-SHAPED BODY  накопление лучей в координатах пути
   ├─ CENTRAL COMPLEX  накопление улик в координатах пути, треки
   └─ DESCENDING       два порога с гистерезисом → решение

Текущие измеренные показатели (Ryzen 7600X, Hesai Pandar128):

  • Реальный объект на 55 м (doubleT_obstacle): 99.5 % кадров с детекцией.
  • Задержка кадра на CPU: 32–33 мс при лимите 100 мс (запас ~3×).
  • Ложные тревоги (leave-one-bag-out): 8.0 трека/км (11.0 % кадров).
  • Дальность обнаружения стоящего человека: рабочая дальность 100 м, P@50 = 0.70, P@100 = 0.53, P@150 = 0.33.

Главные узкие места, выявленные в ходе анализа:

  1. Застревание улики трека на 120–160 м: На 150 м кандидат появляется в ~62 % кадров, но от предмета приходит лишь 4–6 лучей. Покадровое считывание MBON выдаёт малое p \approx 0.05 (из-за априорной редкости), улика прирастает на +gain \cdot w \approx +0.017, а утечка за промах отнимает -leak = -0.12. В итоге evidence колеблется около нуля, и порог 0.5 никогда не пробивается.
  2. Артефакты синтетики в TrackReadout: Признаки s_{std} и u_{std} перевернулись на реальном объекте (0.365 м у человека против 0.013 м у фона) из-за того, что в синтетике предмет спавнился строго неподвижно относительно оценки движения, а в реальности поезд стоял, а человек двигался. Признаки closed и age заучили фиксированный спавн с 200 м. В текущей версии track_blend = 0.0 (выключен).
  3. Неиспользуемая неопределённость коридора Corridor.sigma: Ось коридора на кривой за горизонтом видимости (62–107 м) продолжается по касательной и расходится с истинным путем до 1.19 м на 150 м. Полуширина габарита жестко зафиксирована на 1.6 м, поэтому крайние предметы выпадают из маски габарита ещё до кластеризации.
  4. Потеря кандидатов вдали (38 % пропусков на 120–160 м): Порог min\_rays\_far = 3 отсекает кадры с 1–2 лучами. При этом накопитель FanBody за 15 кадров аккумулирует в вокселе десятки попаданий, но никак не помогает формированию покадрового кандидата.
  5. Скачок ложных тревог на новой линии (8.0 \to 20.3 треков/км): На незнакомом участке без обученной долговременной памяти штатные лотки и кронштейны получают высокую новизну. Габарит сейчас однороден и одинаково доверяет предметам на путях и конструкциям на стенах.
  6. Слипание с платформой на 30–50 м: При смещении предмета +0.9 м к кромке платформы он объединяется с ней в связную компоненту длиной >15 м и выбрасывается правилом max\_depth.
  7. Отсутствие лежащего человека: В catalogue нет класса лежащего человека, а нижняя граница габарита h_{lo} = 0.28 м срезает упавшего на рельсы пассажира.

2. Поэтапный план улучшений

   ┌─────────────────────────────────────────────────────────────┐
   │ ФАЗА 1: Устранение артефактов данных и синтетики            │
   │ • Аугментация спавна, движения, стоянки в make_benchmark   │
   │ • Добавление «человек_лежа» (1.8×0.5×0.3 м)                │
   │ • Честное переобучение и валидация TrackReadout             │
   └──────────────────────────────┬──────────────────────────────┘
                                  │
                                  ▼
   ┌─────────────────────────────────────────────────────────────┐
   │ ФАЗА 2: Пробитие дальней зоны (100–160 м)                   │
   │ • Двухканальное решение в DescendingNeurons (soft-warning)  │
   │ • Динамическое расширение габарита по Corridor.sigma(d)     │
   │ • Обратная связь FanBody → Lobula (порог 2 луча по опоре)   │
   └──────────────────────────────┬──────────────────────────────┘
                                  │
                                  ▼
   ┌─────────────────────────────────────────────────────────────┐
   │ ФАЗА 3: Обобщаемость на новый тоннель и ближняя зона        │
   │ • Иерархический габарит (опасная колея vs стены/лотки)      │
   │ • Вычитание плоскости платформы (h~1.1 м, |u|>1.2 м)        │
   │ • Понижение h_lo до 0.16 м в межрельсовой зоне              │
   └──────────────────────────────┬──────────────────────────────┘
                                  │
                                  ▼
   ┌─────────────────────────────────────────────────────────────┐
   │ ФАЗА 4: ROS2 Экспорт, Замеры и Протокол Стенда Жюри         │
   │ • Модуль экспорта: 3D Bounding Boxes, Threat Level, RViz    │
   │ • Сквозной Leave-one-bag-out замер и стресс-тест на CPU     │
   └─────────────────────────────────────────────────────────────┘

Фаза 1: Устранение артефактов данных и синтетики

Цель: сделать выборку для обучения TrackReadout инвариантной к реальным условиям, исключить переобучение на искусственные признаки генератора.

1.1. Аугментация генерации в tools/make_benchmark.py

  • Случайная дальность спавна: вместо жесткой фиксации на d_{start} = 200 м задавать d_{start} \sim \mathcal{U}(40, 200) м. Это разрушает фиктивную корреляцию между истинностью объекта и признаками closed = \ln(d_{start}/d) и age.
  • Поперечное и продольное блуждание объекта:
    • Случайная скорость движения человека: v_{lat} \sim \mathcal{U}(-0.4, 0.4) м/с.
    • Реалистичный шум положения \delta s \sim \mathcal{N}(0, \sigma_s^2) с \sigma_s = 0.15 м (моделирует погрешность одометрии и скольжение лучей по телу).
    • Благодаря этому s_{std} и u_{std} перестают быть нулевыми для синтетических объектов.
  • Сценарии со стоящим поездом:
    • Генерация препятствий в кадры со стоящим поездом (v = 0, например, кадры бэга doubleT_obstacle), чтобы модель не считала нулевую скорость признаком отсутствия опасности.

1.2. Добавление класса «человек_лежа» в catalogue

  • Добавить геометрию:
    "человек_лежа": ObjectModel("человек_лежа", [
        Box(length=1.80, width=0.50, height=0.30, h_base=0.0)
    ], reflectivity=0.35)
    
  • В _quality учесть, что вытянутость вдоль пути (depth \approx 1.8 м) при малой высоте (h \le 0.35 м) и расположении в межрельсовой зоне (|u| \le 0.75 м) не должна штрафоваться множителем compact.

1.3. Переобучение TrackReadout

  • Собрать обновлённый кэш треков:
    python tools/make_benchmark.py --memory artifacts/mushroom_body.npz \
        --mbon-dir artifacts/mbon_folds --tracks-out data/cache/track_set_aug.npz
    
  • Обучить TrackReadout с контролем ROC AUC по всем полосам (целевой AUC >0.90 на полосе 110–160 м).
  • Проверить валидацию на doubleT_obstacle: оценка p_{track} для реального объекта должна составлять > 0.95.

Фаза 2: Пробитие дальней зоны (100–160 м)

Цель: гарантировать, что появляющиеся на 120–160 м кандидаты и треки превращаются в надежное раннее предупреждение, не застревая в утечке leak.

2.1. Двухканальное решение в DescendingNeurons

Биологическая аналогия: разделение Giant Fiber (DNp01, жесткий аварийный порог) и DNp02/DNp11 (раннее предупреждение).

# 1. Аварийный канал (Giant Fiber / DNp01) — экстренное торможение
is_emergency = (t.evidence >= self.emergency_evidence) and (d <= stopping_distance)

# 2. Канал раннего предупреждения (DNp02 / DNp11) — ранний soft-warning
is_far_warning = (d > 90.0) and (t.hits >= 2) and (t.p_track >= 0.65)
is_near_warning = (d <= 90.0) and (t.evidence >= self.warn_evidence)

decision.detected = is_emergency or is_far_warning or is_near_warning
decision.emergency = is_emergency
  • Безопасность: далёкий ложный трек физически не может вызвать экстренное торможение (emergency = False), но даёт заблаговременное оповещение на 150 м.

2.2. Динамический габарит по Corridor.sigma(d)

В geometry.py уже заложен расчет неопределенности оси:

\sigma(d) = base + rate \cdot d + 0.02 \cdot \max(d - d_{max\_seen}, 0)

На 150 м \sigma(d) \approx 1.1 м.

В функции gauge_mask в flyguard/lobula.py:

W_{eff}(d) = half\_width + k_\sigma \cdot \sigma(d), \quad k_\sigma = 0.8
  • На 50 м: W_{eff} = 1.6 + 0.8 \cdot 0.45 \approx 1.96 м.
  • На 150 м: W_{eff} = 1.6 + 0.8 \cdot 1.10 \approx 2.48 м.
  • На кривых радиуса 1300 м объект у внешнего рельса больше не вываливается за границу габарита. Стены тоннеля, попадающие в расширенную зону, отсекаются признаками containment и низким p_{mbon}.

2.3. Обратная связь FanBody \to find_candidates

FanBody аккумулирует лучи в воксельной сетке пути 2.0 \times 0.2 \times 0.25 м. В find_candidates вводится адаптивный порог числа лучей:

# Если в данной точке пространства накопитель уже видит стабильную опору:
support = fan.support_at(d_comp, u_comp) if fan is not None else 0.0
need_rays = 2 if (d_comp >= 90.0 and support >= 0.6) else (3 if d_comp >= 90.0 else 4)
  • Снижает пропуски кандидатов на 120–160 м с 38 % до < 15 % для слабых эхо.

Фаза 3: Обобщаемость на незнакомой линии и ближняя зона

Цель: снизить ложные тревоги на незнакомой линии с 20.3 до <10 треков/км и закрыть потерю объектов возле платформ на 30–50 м.

3.1. Иерархический двухзонный габарит (Core vs Peripheral)

Сечение пути разделяется на две зоны ответственности:

\text{Зона 1 (Колея)}: |u| \le 0.90\,\text{м}, \quad h \in [0.16, 1.90]\,\text{м}
\text{Зона 2 (Периферия)}: 0.90 < |u| \le 1.60\,\text{м} \quad \text{или} \quad h > 1.90\,\text{м}
  1. Для Зоны 1 (Колея):
    • Нижний порог h_{lo} снижается до 0.16 м (захватывает лежащего человека).
    • Множитель компактности в _quality не штрафует протяженность depth до 2.0 м при малой высоте (h \le 0.35 м).
    • Максимальный приоритет подтверждения трека (gain = 0.40).
  2. Для Зоны 2 (Периферия):
    • Пространство кабельных лотков, сигналов и кронштейнов.
    • Требование опоры на полотно: grounded \le 0.3 для висящих на стенах объектов подавляет их прирост улики.
    • Для формирования трека требуется p_{mbon} \ge 0.50.
    • Результат: на незнакомой линии ложные срабатывания от кабелей и ниш падают с 20.3 до < 10.0 на км.

3.2. Геометрическая сегментация платформы на 30–50 м

Платформа в метро имеет стандартизованные параметры: высота 1.10 \pm 0.05 м над рельсом, удаление |u| \ge 1.35 м. В find_candidates:

# Маска горизонтальной поверхности платформы:
platform_mask = (np.abs(lat) > 1.30) & (tf.h >= 1.05) & (tf.h <= 1.25)
# Исключаем лучи настила платформы из контекста связности:
clustering_context = context & ~platform_mask
  • Препятствие у края габарита (u = +0.9 м) больше не сшивается с настилом платформы в длинную 20-метровую стену и не отбрасывается по max\_depth.

Фаза 4: ROS2-интеграция, API и валидация

Цель: подготовить модуль для интеграции с ребятами, делающими ROS2-узел, сохранив ядро 100 % независимым и легковесным (чистый CPU, NumPy/SciPy).

4.1. Модуль экспорта для ROS2 (flyguard/export.py)

Обеспечивает чистый интерфейс между ядром и ROS2-нодой:

  • Преобразование треков Track в 3D Bounding Boxes в координатах сенсора (x, y, z, dx, dy, dz, yaw).
  • Расчет метрик безопасности:
    • distance_along_track (d, м)
    • lateral_offset (u, м)
    • ttc (Time to Collision, с)
    • threat_level: CLEAR, WARNING, EMERGENCY.
  • Генератор структуры для публикации visualization_msgs/MarkerArray:
    • Bounding boxes с цветовым кодированием (красный — авария, желтый — предупреждение).
    • 3D линия оси коридора тоннеля.
    • Текстовые метки с расстоянием и скоростью сближения над объектами.

4.2. Контроль рантайма на CPU

  • Зависимости: строго numpy и scipy в рантайме.
  • Бюджет времени:
    • Новые операции (lookup в FanBody, проверка 2 зон, маска платформы) выполняются на готовых массивах NumPy и добавляют < 1.2 мс.
    • Ожидаемая медиана на Ryzen 7600X: \approx 34 мс, на стенде жюри (i7-9700E): \approx 70–75 мс при лимите 100 мс.

3. Сводная таблица ожидаемых метрик

Метрика До улучшений После реализации Ключевой фактор роста
Реальный объект на 55 м (doubleT_obstacle) 99.5 % > 99.5 % Сохранение надежности канала Giant Fiber
P@150 м (стоящий человек) 0.33 > 0.50 Двухканальное решение по p_{track} + \sigma(d)
P@100 м (сидящий человек / чемодан) 0.34 / 0.21 > 0.55 / 0.40 Порог 2 лучей при накоплении в FanBody
P@50 м (лежащий человек) 0.0 % (срезался) > 0.75 h_{lo} = 0.16 м в колее + класс в каталоге
Ложные тревоги (незнакомая линия) 20.3 на км < 10.0 на км Двухзонный габарит (периферия фильтрует лотки)
Обнаружение у платформ (30–50 м) 66 % (потеря 34 %) > 90 % Сегментация настила платформы до кластеризации
Время кадра (CPU) 33 мс < 36 мс Векторизованный NumPy/SciPy без оверхеда

4. Дорожная карта выполнения (Action Checklist)

  • Шаг 1 (Данные и синтетика):
    • В flyguard/synth.py добавлен класс человек_лежа.
    • В flyguard/central_complex.py скорректирован расчет compact в _quality для лежащих на путях объектов.
    • В tools/make_benchmark.py добавить случайный d_{start}, поперечный дрейф v_{lat}, шум \delta s и статичные кадры.
    • Пересобрать кэш track_set.npz и переобучить TrackReadout.
  • Шаг 2 (Дальняя зона):
    • В flyguard/descending.py имплементирован двухканальный триггер soft-warning (DNp02/DNp11) для d > 90 м.
    • В flyguard/lobula.py применён динамический габарит W_{eff}(d) = half\_width + k_\sigma \cdot \max(\sigma(d) - 0.25, 0.0).
    • Оптимизированы параметры warn_far = 0.35 и leak_far = 0.06 для предотвращения вымывания улики на дальности.
  • Шаг 3 (Зонирование и платформы):
    • Двухзонный габарит: колея (|u| \le 0.85 м, h_{lo\_core} = 0.16 м) и периферия (h_{lo} = 0.28 м).
    • Геометрическая фильтрация настила платформ (|u| \ge 1.30 м, h \in [1.05, 1.25] м) для устранения слипания объектов со стенкой.
  • Шаг 4 (ROS2 и валидация):
    • Создан модуль экспорта flyguard/export.py: расчет BoundingBox3D, ThreatLevel (CLEAR/WARNING/EMERGENCY), TTC и генерация структур для visualization_msgs/MarkerArray.
    • Добавлены и успешно пройдены 35 модульных тестов (tests/test_pipeline.py).
    • Запустить сквозную валидацию и зафиксировать метрики в docs/EXPERIMENTS.md.