forked from Dan4ick/Lidar_Muxa
168 lines
9.9 KiB
Markdown
168 lines
9.9 KiB
Markdown
# FlyGuard · ML-ядро
|
||
|
||
Обнаружение посторонних объектов в тоннеле метро по данным 3D-лидара
|
||
Hesai Pandar128. Кейс 05, ЛЦТ-2026.
|
||
|
||
Это **ядро обработки**: облако точек на входе, решение о препятствии на выходе.
|
||
Узел ROS 2, транспорт, контейнер и визуализация живут отдельно и сюда не
|
||
входят — ядро от них не зависит и проверяется без ROS вообще.
|
||
|
||
---
|
||
|
||
## Чем это не является
|
||
|
||
Не нейросетевой детектор общего назначения. Конвейер собран по схемам
|
||
зрительной системы дрозофилы, и каждая стадия — это конкретный нейропиль с
|
||
конкретной функцией, а не слой, подобранный перебором:
|
||
|
||
```
|
||
облако точек 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 два порога с гистерезисом → решение
|
||
```
|
||
|
||
Ничего про геометрию сенсора не захардкожено: решётка лучей, высота установки,
|
||
крен и тангаж **калибруются по самим данным** на первых кадрах. В записях
|
||
встречаются две раскладки скана (3600 азимутов на 360° и 1200 на 120°) и две
|
||
высоты установки (1.31 и 1.70 м) — ядро работает с обеими без правок.
|
||
|
||
---
|
||
|
||
## Что нужно интеграции: один класс, один вызов
|
||
|
||
```python
|
||
from flyguard.pipeline import FlyGuard, Params
|
||
from flyguard.mushroom_body import MushroomBody
|
||
from flyguard.mbon_readout import MbonReadout
|
||
|
||
fg = FlyGuard(Params(),
|
||
memory=MushroomBody.load("artifacts/mushroom_body.npz"),
|
||
readout=MbonReadout.load("artifacts/mbon_readout.npz"))
|
||
|
||
res = fg.process(cloud) # cloud: flyguard.cdr.PointCloud2
|
||
if res is None:
|
||
... # первые 12 кадров уходят на калибровку решётки
|
||
else:
|
||
d = res.decision # detected, distance, confidence, emergency, objects
|
||
res.total_ms # время обработки кадра
|
||
```
|
||
|
||
Требования к входу: `flyguard.cdr.PointCloud2` — поля `x, y, z, intensity`,
|
||
порядок точек как в сыром CDR. Конвейер **хранит состояние между кадрами**
|
||
(решётка, плоскость пути, ось пути, треки, накопитель), поэтому один экземпляр
|
||
обслуживает один поток данных; для параллельных сценариев нужны разные
|
||
экземпляры.
|
||
|
||
Выход `Decision`: `detected`, `distance` (м), `confidence` (0…1),
|
||
`emergency` (флаг экстренного торможения), `objects` (список подтверждённых
|
||
треков с id, дистанцией и габаритами).
|
||
|
||
Всё считается на **CPU**, GPU не требуется. Медиана обработки кадра — 33 мс
|
||
при бюджете 100 мс.
|
||
|
||
---
|
||
|
||
## Структура
|
||
|
||
```
|
||
flyguard/ ядро: стадии обработки, память, считывание
|
||
bag.py cdr.py чтение rosbag2 и разбор CDR без ROS
|
||
retina.py geometry.py решётка лучей, плоскость рельсов, ось пути
|
||
lamina.py medulla.py контраст, движение
|
||
lobula.py кандидаты
|
||
mushroom_body.py память тоннеля (без меток)
|
||
mbon_readout.py обученное считывание (с метками)
|
||
fan_body.py накопление в координатах пути
|
||
central_complex.py треки и улики
|
||
descending.py решение
|
||
pipeline.py сборка
|
||
synth.py вставка предметов трассировкой лучей
|
||
tools/ обучение, оценка, разбор
|
||
tests/ 28 тестов, запускаются без данных и без ROS
|
||
docs/ методика и результаты
|
||
artifacts/ обученные модели
|
||
```
|
||
|
||
---
|
||
|
||
## Как запустить
|
||
|
||
```bash
|
||
pip install -r requirements.txt
|
||
pytest tests -q
|
||
```
|
||
|
||
Записи лидара в репозиторий не кладутся. Положите их рядом
|
||
(`../data/for_hackathon/...`) или укажите путь:
|
||
|
||
```bash
|
||
set FLYGUARD_DATA=D:\lidar\data
|
||
```
|
||
|
||
Обучение и оценка:
|
||
|
||
```bash
|
||
python tools/make_training_set.py # размеченная выборка
|
||
python tools/train_mbon.py --device cuda --baseline # считывание MBON
|
||
python tools/evaluate.py --mbon-dir artifacts/mbon_folds # ложные тревоги
|
||
python tools/make_benchmark.py --memory artifacts/mushroom_body.npz \
|
||
--mbon-dir artifacts/mbon_folds # дальность обнаружения
|
||
python tools/plot_benchmark.py # кривые и график
|
||
```
|
||
|
||
---
|
||
|
||
## Где мы сейчас
|
||
|
||
| Метрика | Значение | Чем измерено |
|
||
|---|---|---|
|
||
| Реальный объект 0.67 × 1.35 м на 55 м | **99.5 %** кадров | `tools/check_obstacle.py` |
|
||
| Ложные тревоги, leave-one-bag-out | **3.3 трека на км**, 4.5 % кадров | `tools/evaluate.py --mbon-dir` |
|
||
| То же без обученного считывания | 7.4 на км, 16.7 % кадров | там же, без `--mbon-dir` |
|
||
| То же на новой линии, без обученной памяти | 21.8 на км | там же, холодный старт |
|
||
| Рабочая дальность (полигон, 14 004 наблюдения) | **80 м** человек стоя и сидя, P@50 = 0.70, P@100 = 0.31 | `tools/plot_benchmark.py` |
|
||
| Обработка кадра | медиана 33 мс из бюджета 100 мс | `tools/run_pipeline.py` |
|
||
| Разделение «знакомое / новое» (без учителя) | ROC AUC 0.905 | `tools/tune_memory.py` |
|
||
| Считывание MBON «предмет / тоннель» (с учителем) | ROC AUC **0.986** | `tools/train_mbon.py` |
|
||
|
||
Проверка всегда **leave-one-bag-out**: память обучается на всех записях, кроме
|
||
проверяемой. Иначе цифры лгут — подавлять конструкции, которые сам же и
|
||
запомнил, умеет кто угодно, а на приватном тесте будет новый участок.
|
||
|
||
---
|
||
|
||
## Что честно не работает
|
||
|
||
Разобрано замерами, подробности — в `docs/EXPERIMENTS.md`:
|
||
|
||
* **За 200 м на этих участках не увидит никто**: прямая видимость в тоннелях
|
||
121–167 м, дальше линия взгляда упирается в стену кривой. На отдельных
|
||
перегонах и того меньше — 49–90 м.
|
||
* **Мелкие предметы на большой дальности невозможны с этим сенсором**: ведро
|
||
(0.1 м²) на 160–190 м даёт один луч, каска и бутылка — ноль.
|
||
* **За 80–90 м прирельсовая зона не наблюдается вовсе**: луч скользит по
|
||
полотну, и самая низкая видимая точка у оси пути оказывается выше головки
|
||
рельса на 0.1–0.6 м.
|
||
* Привыкание внутри проезда сделано и **отвергнуто замером** — п. 10.
|
||
* **Яркость как признак мы себе не засчитываем.** Абсолютной шкалы
|
||
интенсивности в записях нет: медиана по кандидатам обстановки 3…7 в пяти
|
||
бэгах и 23.5 в шестом. Вставка берёт яркость реальных возвратов с тех же
|
||
лучей, то есть признак намеренно обесточен, и дальность из-за этого
|
||
занижена — настоящий предмет был в 1.47 раза ярче окружения. Разбор — п. 11.1.
|
||
|
||
---
|
||
|
||
## Документация
|
||
|
||
* `docs/ALGORITHM.md` — что делает каждая стадия и почему именно так.
|
||
* `docs/EXPERIMENTS.md` — все замеры, включая отрицательные результаты.
|
||
* `docs/CONNECTOME.md` — что взято из коннектома как число, а что как идея.
|