178 lines
12 KiB
Markdown
178 lines
12 KiB
Markdown
# Архитектура решения
|
||
|
||
Цепочка, которую просит ТЗ: **ROS 2 bag → обработка облака → алгоритм обнаружения →
|
||
результат детекции → визуализация.**
|
||
|
||
---
|
||
|
||
## 1. Общая схема
|
||
|
||
```
|
||
ros2 bag play / реальный лидар
|
||
│
|
||
sensor_msgs/PointCloud2
|
||
0.3–0.9 млн точек, 10 Гц
|
||
│
|
||
┌──────────────────────────▼──────────────────────────────┐
|
||
│ flyguard/node.py — ROS 2-нода │
|
||
│ подписка (BEST_EFFORT) → очередь на 1 кадр │
|
||
│ обработка в отдельном потоке, старые кадры отброшены │
|
||
└──────────────────────────┬──────────────────────────────┘
|
||
│ flyguard.cdr.PointCloud2 (без копирования)
|
||
┌──────────────────────────▼──────────────────────────────┐
|
||
│ flyguard/pipeline.py — конвейер, хранит состояние │
|
||
└──────────────────────────┬──────────────────────────────┘
|
||
│
|
||
retina.py RETINA облако → дальностный образ 128 × N
|
||
│ выпрямление скоса каналов, слияние эх
|
||
▼
|
||
geometry.py HALTERES плоскость рельсов: крен, тангаж, высота
|
||
│ ось пути: дуга u(d) = c₁d + c₂d²
|
||
▼
|
||
lamina.py LAMINA диспаритет 1/R → ON/OFF, центр-окружение ×3
|
||
│
|
||
├────────────────► medulla.py MEDULLA / LOBULA PLATE
|
||
│ T4/T5 → LPTC: скорость без одометрии
|
||
│ LPLC2: надвигание
|
||
▼
|
||
lobula.py LOBULA связность с учётом глубины → кандидаты
|
||
│ разрез по контрасту: фигура из компоненты,
|
||
│ растёкшейся вдоль стены
|
||
│ признаки: габариты, целостность, тень, опора
|
||
▼
|
||
mushroom_body MUSHROOM BODY PN→KC (случайно, 6 входов) → APL → MBON
|
||
│ новизна: 1 — незнакомо, 0 — штатная обстановка
|
||
│ (привыкание внутри проезда сделано и выключено: п. 10
|
||
│ EXPERIMENTS — избирательности у механизма нет)
|
||
▼
|
||
fan_body.py FAN-SHAPED BODY сетка в координатах пути: опора для кандидата
|
||
│ там, где контраст структурно равен нулю
|
||
▼
|
||
central_complex CENTRAL COMPLEX накопление улик в координатах пути, треки
|
||
│
|
||
▼
|
||
descending.py DESCENDING два порога с гистерезисом → решение
|
||
│
|
||
▼
|
||
┌──────────────────────────────────────────────────────────┐
|
||
│ /flyguard/obstacle ObstacleStatus — программный выход │
|
||
│ /flyguard/detected Bool — бинарный статус │
|
||
│ /flyguard/distance Float32 — расстояние, м │
|
||
│ /flyguard/markers MarkerArray — рамки для RViz2 │
|
||
│ /flyguard/brain Image — схема мозга мухи │
|
||
│ /flyguard/diagnostics DiagnosticArray — задержки, скорость│
|
||
└──────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Состояние между кадрами
|
||
|
||
Конвейер не обрабатывает кадры независимо. Между вызовами он хранит:
|
||
|
||
| Что | Где | Зачем |
|
||
|---|---|---|
|
||
| решётка лучей | `FlyGuard.layout` | калибруется по первым 12 кадрам, дальше не меняется |
|
||
| плоскость пути | `FlyGuard.plane` | сглаживание по кадрам, устойчивость к качке |
|
||
| ось пути | `FlyGuard.corridor` | сглаживание и ограничение скорости изменения |
|
||
| профиль и точки | `EgoMotionEstimator` | сопоставление с предыдущим кадром → скорость |
|
||
| задержанный сигнал | `EmdBank` | вторая половина коррелятора T4/T5 |
|
||
| сетка пути | `FanBody` | накопление лучей в координатах мира |
|
||
| треки | `CentralComplex` | накопление улик в координатах пути |
|
||
| гистерезис | `DescendingNeurons` | защёлка тревоги |
|
||
|
||
Поэтому один экземпляр `FlyGuard` обслуживает один поток данных. Для офлайн-экспериментов
|
||
с несколькими сценариями одновременно создаётся несколько экземпляров.
|
||
|
||
---
|
||
|
||
## 3. Потоки и реальное время
|
||
|
||
Нода разделена на два потока:
|
||
|
||
* **поток ROS** принимает облака и кладёт в слот на один кадр; если предыдущий ещё не
|
||
обработан, он **отбрасывается** и счётчик `dropped_frames` растёт;
|
||
* **рабочий поток** берёт последний кадр и гоняет конвейер.
|
||
|
||
Так система реального времени отвечает на текущую обстановку, а не доедает накопившееся
|
||
прошлое. Число отброшенных кадров публикуется в диагностике: если оно растёт, значит
|
||
машина не тянет, и это видно сразу, а не проявляется скрытой задержкой.
|
||
|
||
Замер по стадиям ведётся всегда и публикуется в `/flyguard/diagnostics`, поэтому
|
||
профилировать решение можно прямо на стенде, не пересобирая его.
|
||
|
||
---
|
||
|
||
## 4. Разделение на пакеты
|
||
|
||
```
|
||
ros2_ws/src/
|
||
├── flyguard_msgs/ ament_cmake — только сообщения
|
||
│ └── msg/ObstacleStatus.msg, msg/DetectedObject.msg
|
||
└── flyguard/ ament_python — конвейер и нода
|
||
├── flyguard/
|
||
│ ├── cdr.py разбор PointCloud2 без ROS (офлайн-режим)
|
||
│ ├── bag.py чтение rosbag2 sqlite3 без ROS
|
||
│ ├── ros_conv.py sensor_msgs → внутреннее представление
|
||
│ ├── retina.py решётка лучей, дальностный образ
|
||
│ ├── geometry.py плоскость пути, ось, координаты (d, u, h)
|
||
│ ├── lamina.py ON/OFF, центр-окружение
|
||
│ ├── medulla.py T4/T5, LPTC, LPLC2, оценка движения
|
||
│ ├── lobula.py кандидаты, связность с учётом глубины
|
||
│ ├── mushroom_body.py новизна
|
||
│ ├── central_complex.py треки
|
||
│ ├── descending.py решение
|
||
│ ├── synth.py синтетические препятствия (для полигона)
|
||
│ ├── brain_view.py схема мозга мухи
|
||
│ ├── pipeline.py сборка
|
||
│ ├── node.py ROS 2-нода
|
||
│ └── data/pandar128_channels.csv поканальная таблица из руководства
|
||
├── launch/detect.launch.py
|
||
├── config/flyguard.yaml, config/flyguard.rviz
|
||
└── test/test_pipeline.py
|
||
```
|
||
|
||
Ключевое решение: **ядро не зависит от ROS**. `rclpy` импортируется только в `node.py`.
|
||
Благодаря этому весь конвейер запускается офлайн прямо по `.db3`, что дало возможность
|
||
отлаживать и мерить качество на Windows без ROS и быстро гонять полигон в несколько
|
||
параллельных сценариев.
|
||
|
||
---
|
||
|
||
## 5. Артефакты
|
||
|
||
| Файл | Что это | Как получен |
|
||
|---|---|---|
|
||
| `artifacts/mushroom_body.npz` | память тоннеля, без учителя | `tools/train_mushroom_body.py` на пустых проездах |
|
||
| `artifacts/mbon_readout.npz` | обученное считывание MBON | `tools/train_mbon.py` на размеченной вставками выборке |
|
||
| `artifacts/mbon_folds/` | по модели на складку, для честной проверки | он же, ключ `--save-folds` |
|
||
| `data/cache/training_set.npz` | размеченная выборка кандидатов | `tools/make_training_set.py` |
|
||
| `artifacts/generalisation.json` | leave-one-bag-out | `tools/evaluate.py` |
|
||
| `artifacts/benchmark.json` | кривые дальности | `tools/make_benchmark.py` |
|
||
| `flyguard/data/pandar128_channels.csv` | 128 каналов лидара | `tools/extract_channel_table.py` из руководства |
|
||
|
||
Память тоннеля копируется в Docker-образ и подхватывается launch-файлом автоматически;
|
||
путь переопределяется параметром `memory_path`.
|
||
|
||
---
|
||
|
||
## 6. Потоки данных в цифрах
|
||
|
||
Для кадра 128 × 3600 × 2 эха (полный круговой скан, 921 600 точек):
|
||
|
||
| Стадия | Объём на входе | Время, мс |
|
||
|---|---|---|
|
||
| приём и разбор сообщения | 24 МБ | ~2 (без копирования) |
|
||
| retina (оконная проекция) | только нужный сектор | 6–9 |
|
||
| стабилизация | ~150 тыс. точек | 3–4 |
|
||
| ось пути | 30 срезов | 4–5 |
|
||
| ламина | 128 × 600 × 3 масштаба | 6–7 |
|
||
| оценка движения | 6000 точек × ~20 проб | 5–8 |
|
||
| лобула | связность по маске | 3–5 |
|
||
| грибовидное тело | единицы кандидатов | 1–2 |
|
||
| центральный комплекс и решение | десятки треков | <0.3 |
|
||
|
||
Оконная проекция — важная оптимизация: при секторе обработки ±30° тяжёлая арифметика
|
||
выполняется только над теми сырыми столбцами, которые в него попадут с учётом скоса
|
||
каналов. На круговом скане это сократило стадию ретины с 29 до 7 мс без изменения
|
||
результата (проверено побитовым сравнением).
|