Brainrot_Muxa/README.md

241 lines
16 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 · 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 не требуется. Медиана обработки кадра — 43 мс
при бюджете 100 мс (замер по стадиям, один процесс, EXPERIMENTS п. 7.4).
Ламину можно перенести на **NVIDIA GPU** (`Params(device="auto")` или
`"cuda"`, модуль `flyguard/device.py` с переходом на CPU при любом сбое карты).
Замерено на RTX 5070 Ti: ламина 5.8 → 2.9 мс с копированием туда и обратно,
то есть кадр 43 → ~40 мс, — остальные 80 % времени кластеризация и геометрия,
которые видеокарта не ускоряет. Поэтому по умолчанию `device="cpu"`: выигрыш
в 3 мс не стоит отдельного образа и `--gpus all` на машине проверки.
---
## Структура
```
flyguard/ ядро: стадии обработки, память, считывание
bag.py cdr.py чтение rosbag2 и разбор CDR без ROS
device.py выбор CPU / NVIDIA GPU и переход на CPU при сбое
retina.py geometry.py решётка лучей, плоскость рельсов, ось пути
lamina.py medulla.py контраст (на CPU или GPU), движение
lobula.py кандидаты
mushroom_body.py память тоннеля (без меток)
mbon_readout.py обученное считывание (с метками)
track_readout.py считывание по истории трека — инструмент замера
fan_body.py накопление в координатах пути
central_complex.py треки и улики
descending.py решение
pipeline.py сборка
export.py 3D-рамки, время до столкновения, маркеры RViz
synth.py вставка предметов трассировкой лучей
tools/ обучение, оценка, разбор, полигон с аугментациями
tests/ 45 тестов, запускаются без данных и без ROS
docs/ методика и результаты
artifacts/ обученные модели
```
---
## Как запустить
```bash
pip install -r requirements.txt
pytest tests -q # или без pytest: python tests/run_tests.py
```
Через Docker (подробно — [docs/DOCKER.md](docs/DOCKER.md)):
```bash
./docker-run.sh build
docker compose run --rm test # тесты
docker compose run --rm info # что видно из контейнера: CPU, GPU, CUDA
docker compose run --rm evaluate # ложные тревоги
docker compose run --rm benchmark # полигон
```
Для обучения на видеокарте — `pip install -r requirements-gpu.txt` и
`--device cuda` у `train_mbon.py`, `train_mushroom_body.py`, `evaluate.py`.
Записи лидара в репозиторий не кладутся. Положите их рядом
(`../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 # кривые и график
python tools/compare_benchmark.py было.json стало.json # правка парно
```
Большой бэг `new_data` (90 ГБ) не распаковывается целиком — инструменты
читают его кусками прямо из архива (`--tar`, по умолчанию
`../датасет/new_data` или `FLYGUARD_NEW_DATA`):
```bash
python tools/make_training_set.py --new-data 0:110 --out data/cache/training_set_nd.npz
python tools/train_mbon.py --device cuda --data data/cache/training_set.npz \
--data data/cache/training_set_nd.npz --train-only new_data_ --out nd.npz
python tools/eval_new_data.py --shards 110: \
--readout было=artifacts/mbon_readout.npz --readout стало=nd.npz --out nd.json
```
Проверка на второй половине честная: вставки идут только в первую, а
вторую модель не видит ни в каком виде (EXPERIMENTS п. 17.3).
Синтетику организаторов (`cloud_with_fake_obj`, 10 предметов) сверяет с
эталоном отдельный инструмент. Разметки к бэгу нет, но вставленные точки не
лежат на элевациях колец, и эталон извлекается из самого бэга (п. 18.2):
```bash
python tools/eval_org_synth.py # итоговые настройки
python tools/eval_org_synth.py --set half_width=1.6 --set h_top=0
```
В этом бэге нет поля `ring`, а порядок точек сбит вставками: `retina.py`
восстанавливает кольца по элевации и раскладывает такие кадры по углам точек.
До этой правки ядро на нём не обрабатывало ни одного кадра (п. 18.1).
Любое поле `Params` меняется без правки кода: `--set h_lo_core=0.28 --set
k_sigma=0.75` у `make_benchmark.py`, `evaluate.py`, `make_training_set.py` и
`check_obstacle.py`. Два прогона полигона сравнивайте только парно —
`compare_benchmark.py` считает, сколько наблюдений перевернулось в каждую
сторону на одних и тех же вставках. Итоговые таблицы двух прогонов шумят
сильнее, чем меняет их большинство правок (EXPERIMENTS п. 16.1).
Тяжёлые шаги сами раскладываются по бэгам на процессы — записей пять, физических
ядер шесть, и это вся доступная зернистость: конвейер держит состояние между
кадрами, поэтому разрезать одну запись нельзя. Замерено: полигон 134 → 36 с,
сбор выборки 96 → 26 с на облегчённой конфигурации, то есть 3.7–3.8×, и файл на
выходе совпадает с последовательным **побайтово**. Отключается `--jobs 1`.
Для замера задержки кадра `--jobs 1` обязателен: под пятью процессами время
кадра растёт с 32 до 56 мс. Это свойство замера, а не конвейера, поэтому
`evaluate.py` в параллельном режиме печатает задержку как `nan` — чтобы такое
число нельзя было случайно привести в отчёте.
---
## Где мы сейчас
| Метрика | Значение | Чем измерено |
|---|---|---|
| Реальный объект 0.67 × 1.35 м на 55 м | **99.5 %** кадров | `tools/check_obstacle.py` |
| Синтетика организаторов, 10 предметов | **8 из 10** верно | `tools/eval_org_synth.py` |
| Ложные тревоги, leave-one-bag-out | **5.7 трека на км**, 8.4 % кадров | `tools/evaluate.py --mbon-dir` |
| То же без обученного считывания | 11.9 на км, 20.3 % кадров | там же, без `--mbon-dir` |
| На незнакомой линии (памяти нет) | **13.8 на км**; без считывания 36.1 | `tools/evaluate.py --no-memory` |
| Вторая половина `new_data`: другой день, 3.41 км, не видена при обучении | **14.4 на км**, 20.3 % кадров | `tools/eval_new_data.py` |
| Дальность (полигон, 15 560 наблюдений) | человек стоя: рабочая дальность **100 м**, P@50 = 0.72, P@100 = 0.52, P@150 = 0.46 | `tools/plot_benchmark.py` |
| Человек, упавший на пути | P@50 = **0.50** (было 0.27), рабочая дальность 20 м | там же |
| Обработка кадра | медиана 31 мс из бюджета 100 мс; на ядре уровня стенда жюри 50 / 60 мс (медиана / p95) | контейнер, EXPERIMENTS п. 18.5 |
| Разделение «знакомое / новое» (без учителя) | 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` — что взято из коннектома как число, а что как идея.