Lidar_Muxa/docs/IMPROVEMENT_PLAN.md
Zhirik1337 062bdfd68c аугментации бенчмарка, зонный пол в колее, фильтр платформы, экспорт 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: 38 юнит-тестов и автономный раннер.
2026-09-22 21:25:39 +03:00

254 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Комплексный план развития FlyGuard · Хакатон ЛЦТ-2026 (Кейс 05)
Документ фиксирует согласованную архитектурную стратегию и детальный план доработок ядра `FlyGuard` для максимизации качества решения на хакатоне ЛЦТ-2026 («Обнаружение посторонних объектов в тоннеле метро по данным 3D-лидара»).
---
## 1. Контекст и текущее состояние
Система [`FlyGuard`](../flyguard/pipeline.py) построена на принципах зрительной системы и грибовидных тел дрозофилы:
```
облако точек 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`](../flyguard/track_readout.py)**:
Признаки $s_{std}$ и $u_{std}$ перевернулись на реальном объекте ($0.365$ м у человека против $0.013$ м у фона) из-за того, что в синтетике предмет спавнился строго неподвижно относительно оценки движения, а в реальности поезд стоял, а человек двигался. Признаки $closed$ и $age$ заучили фиксированный спавн с 200 м. В текущей версии `track_blend = 0.0` (выключен).
3. **Неиспользуемая неопределённость коридора [`Corridor.sigma`](../flyguard/geometry.py)**:
Ось коридора на кривой за горизонтом видимости (62–107 м) продолжается по касательной и расходится с истинным путем до 1.19 м на 150 м. Полуширина габарита жестко зафиксирована на $1.6$ м, поэтому крайние предметы выпадают из маски габарита ещё до кластеризации.
4. **Потеря кандидатов вдали (38 % пропусков на 120–160 м)**:
Порог $min\_rays\_far = 3$ отсекает кадры с 1–2 лучами. При этом накопитель [`FanBody`](../flyguard/fan_body.py) за 15 кадров аккумулирует в вокселе десятки попаданий, но никак не помогает формированию покадрового кандидата.
5. **Скачок ложных тревог на новой линии ($8.0 \to 20.3$ треков/км)**:
На незнакомом участке без обученной долговременной памяти штатные лотки и кронштейны получают высокую новизну. Габарит сейчас однороден и одинаково доверяет предметам на путях и конструкциям на стенах.
6. **Слипание с платформой на 30–50 м**:
При смещении предмета $+0.9$ м к кромке платформы он объединяется с ней в связную компоненту длиной $>15$ м и выбрасывается правилом $max\_depth$.
7. **Отсутствие лежащего человека**:
В [`catalogue`](../flyguard/synth.py) нет класса лежащего человека, а нижняя граница габарита $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`](../flyguard/track_readout.py) инвариантной к реальным условиям, исключить переобучение на искусственные признаки генератора.
#### 1.1. Аугментация генерации в [`tools/make_benchmark.py`](../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`](../flyguard/synth.py)
* Добавить геометрию:
```python
"человек_лежа": ObjectModel("человек_лежа", [
Box(length=1.80, width=0.50, height=0.30, h_base=0.0)
], reflectivity=0.35)
```
* В [`_quality`](../flyguard/central_complex.py) учесть, что вытянутость вдоль пути ($depth \approx 1.8$ м) при малой высоте ($h \le 0.35$ м) и расположении в межрельсовой зоне ($|u| \le 0.75$ м) не должна штрафоваться множителем $compact$.
#### 1.3. Переобучение [`TrackReadout`](../tools/train_track.py)
* Собрать обновлённый кэш треков:
```bash
python tools/make_benchmark.py --memory artifacts/mushroom_body.npz \
--mbon-dir artifacts/mbon_folds --tracks-out data/cache/track_set_aug.npz
```
* Обучить [`TrackReadout`](../tools/train_track.py) с контролем ROC AUC по всем полосам (целевой AUC $>0.90$ на полосе 110–160 м).
* Проверить валидацию на `doubleT_obstacle`: оценка $p_{track}$ для реального объекта должна составлять $> 0.95$.
---
### Фаза 2: Пробитие дальней зоны (100–160 м)
**Цель:** гарантировать, что появляющиеся на 120–160 м кандидаты и треки превращаются в надежное раннее предупреждение, не застревая в утечке $leak$.
#### 2.1. Двухканальное решение в [`DescendingNeurons`](../flyguard/descending.py)
Биологическая аналогия: разделение Giant Fiber (DNp01, жесткий аварийный порог) и DNp02/DNp11 (раннее предупреждение).
```python
# 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)`](../flyguard/geometry.py)
В [`geometry.py`](../flyguard/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`](../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`](../flyguard/fan_body.py) $\to$ [`find_candidates`](../flyguard/lobula.py)
[`FanBody`](../flyguard/fan_body.py) аккумулирует лучи в воксельной сетке пути $2.0 \times 0.2 \times 0.25$ м.
В [`find_candidates`](../flyguard/lobula.py) вводится адаптивный порог числа лучей:
```python
# Если в данной точке пространства накопитель уже видит стабильную опору:
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`](../flyguard/central_complex.py) не штрафует протяженность $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`](../flyguard/lobula.py):
```python
# Маска горизонтальной поверхности платформы:
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`](../flyguard/pipeline.py))
Обеспечивает чистый интерфейс между ядром и ROS2-нодой:
* Преобразование треков [`Track`](../flyguard/central_complex.py) в 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`](../flyguard/fan_body.py) |
| **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 (Данные и синтетика)**:
* [x] В [`flyguard/synth.py`](../flyguard/synth.py) добавлен класс `человек_лежа`.
* [x] В [`flyguard/central_complex.py`](../flyguard/central_complex.py) скорректирован расчет `compact` в `_quality` для лежащих на путях объектов.
* [ ] В [`tools/make_benchmark.py`](../tools/make_benchmark.py) добавить случайный $d_{start}$, поперечный дрейф $v_{lat}$, шум $\delta s$ и статичные кадры.
* [ ] Пересобрать кэш `track_set.npz` и переобучить [`TrackReadout`](../tools/train_track.py).
- [x] **Шаг 2 (Дальняя зона)**:
* [x] В [`flyguard/descending.py`](../flyguard/descending.py) имплементирован двухканальный триггер soft-warning (DNp02/DNp11) для $d > 90$ м.
* [x] В [`flyguard/lobula.py`](../flyguard/lobula.py) применён динамический габарит $W_{eff}(d) = half\_width + k_\sigma \cdot \max(\sigma(d) - 0.25, 0.0)$.
* [x] Оптимизированы параметры `warn_far = 0.35` и `leak_far = 0.06` для предотвращения вымывания улики на дальности.
- [x] **Шаг 3 (Зонирование и платформы)**:
* [x] Двухзонный габарит: колея ($|u| \le 0.85$ м, $h_{lo\_core} = 0.16$ м) и периферия ($h_{lo} = 0.28$ м).
* [x] Геометрическая фильтрация настила платформ ($|u| \ge 1.30$ м, $h \in [1.05, 1.25]$ м) для устранения слипания объектов со стенкой.
- [ ] **Шаг 4 (ROS2 и валидация)**:
* [x] Создан модуль экспорта [`flyguard/export.py`](../flyguard/export.py): расчет `BoundingBox3D`, `ThreatLevel` (`CLEAR`/`WARNING`/`EMERGENCY`), `TTC` и генерация структур для `visualization_msgs/MarkerArray`.
* [x] Добавлены и успешно пройдены 35 модульных тестов (`tests/test_pipeline.py`).
* [ ] Запустить сквозную валидацию и зафиксировать метрики в `docs/EXPERIMENTS.md`.