Files
gps-denied-onboard/README.md
T
Yuzviak 1bf8b2a684 docs: record EuRoC MH_01 real-run baseline across all doc surfaces
Updates README, testing/README, next_steps.md, and ADR 0001 with the
first real EuRoC MH_01 e2e run (100 frames, ~30s wall-time, ATE RMSE
~10.9 km → xfail). Places the EuRoC result alongside the prior VPAIR
baseline (~1770 km) so future-reader can see both failure modes at a
glance:

- VPAIR diverges because no raw IMU → ESKF never engages
- EuRoC diverges because indoor scene has no satellite anchor, so
  VO+ESKF drift without an external correction

Also records the branching policy (rename ``euroc_mh01`` →
``euroc_machine_hall``; empty URL due to DSpace UI gate; manual
fetch via DOI 10.3929/ethz-b-000690084).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 17:52:06 +03:00

14 KiB
Raw Blame History

GPS-Denied Onboard

Бортова система GPS-denied навігації для фіксованого крила БПЛА на Jetson Orin Nano Super.

Замінює GPS-сигнал власною оцінкою позиції на основі відеопотоку (cuVSLAM), IMU та супутникових знімків. Позиція подається у польотний контролер ArduPilot у форматі GPS_INPUT через MAVLink при 5–10 Гц.


Архітектура

IMU (MAVLink RAW_IMU) ──────────────────────────────────────────▶ ESKF.predict()
                                                                        │
ADTI 20L V1 ──▶ ImageInputPipeline ──▶ ImageRotationManager            │
                                              │                         │
                              ┌───────────────┼───────────────┐         │
                              ▼               ▼               ▼         │
                   cuVSLAM/ORB VO    GlobalPlaceRecog    SatelliteData  │
                       (F07)            (F08/Faiss)         (F04)       │
                              │               │               │         │
                              ▼               ▼               ▼         │
                         ESKF.update_vo()   GSD norm    MetricRefinement│
                              │                              (F09)       │
                              └──────────────────────▶ ESKF.update_sat()│
                                                                        │
                                                         ESKF state ◀──┘
                                                              │
                                              ┌───────────────┼──────────────┐
                                              ▼               ▼              ▼
                                      MAVLinkBridge    FactorGraph     SSE Stream
                                      GPS_INPUT 5-10Hz  (GTSAM ISAM2)  → Ground Station
                                      → ArduPilot FC

State Machine (process_frame):

NORMAL ──(VO fail)──▶ LOST ──▶ RECOVERY ──(GPR+Metric ok)──▶ NORMAL

Стек

Підсистема Dev/CI Jetson (production)
Visual Odometry ORBVisualOdometry (OpenCV) CuVSLAMVisualOdometry (PyCuVSLAM v15)
AI Inference MockInferenceEngine TRTInferenceEngine (TensorRT FP16)
Place Recognition numpy L2 fallback Faiss GPU index
MAVLink MockMAVConnection pymavlink over UART
ESKF numpy (15-state) numpy (15-state)
Factor Graph Mock poses GTSAM 4.3 ISAM2
API FastAPI + Pydantic v2 + SSE FastAPI + Pydantic v2 + SSE
БД SQLite + SQLAlchemy 2 async SQLite
Тести pytest + pytest-asyncio

Швидкий старт

Вимоги

  • Python ≥ 3.11
  • ~500 MB дискового простору (GTSAM wheel)

Встановлення

git clone https://github.com/azaion/gps-denied-onboard.git
cd gps-denied-onboard
git checkout stage1

python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

Запуск

# Прямий запуск
uvicorn gps_denied.app:app --host 0.0.0.0 --port 8000

# Docker
docker compose up --build

Сервер: http://127.0.0.1:8000

Змінні середовища

# Основні
DB_URL=sqlite+aiosqlite:///./flight_data.db
SATELLITE_TILE_DIR=.satellite_tiles
MAVLINK_CONNECTION=serial:/dev/ttyTHS1:57600  # або tcp:host:port
MAVLINK_OUTPUT_HZ=5.0
MAVLINK_TELEMETRY_HZ=1.0

# ESKF тюнінг (опціонально)
ESKF_VO_POSITION_NOISE=0.3
ESKF_SATELLITE_MAX_AGE=30.0
ESKF_MAHALANOBIS_THRESHOLD=16.27

# API
API_HOST=127.0.0.1
API_PORT=8000

# Моделі
MODEL_WEIGHTS_DIR=weights

Повний список: src/gps_denied/config.py (40+ параметрів з prefix DB_, API_, TILES_, MODEL_, MAVLINK_, SATELLITE_, ESKF_, RECOVERY_, ROTATION_).


API

Endpoint Метод Опис
/health GET Health check
/flights POST Створити політ
/flights/{id} GET Деталі польоту
/flights/{flight_id} DELETE Видалити політ
/flights/{flight_id}/images/batch POST Батч зображень
/flights/{flight_id}/user-fix POST GPS-якір від оператора → ESKF update
/flights/{flight_id}/status GET Статус обробки
/flights/{flight_id}/stream GET SSE стрім (позиція + confidence)
/flights/{flight_id}/frames/{frame_id}/object-to-gps POST Pixel → GPS (ray-ground проекція)
/flights/{flight_id}/waypoints/{waypoint_id} PUT Оновити waypoint
/flights/{flight_id}/waypoints/batch PUT Batch update waypoints

Тести

# Всі тести
python -m pytest -q

# Конкретний модуль
python -m pytest tests/test_eskf.py -v
python -m pytest tests/test_mavlink.py -v
python -m pytest tests/test_accuracy.py -v

# SITL (потребує ArduPilot SITL)
docker compose -f docker-compose.sitl.yml up -d
ARDUPILOT_SITL_HOST=localhost pytest tests/test_sitl_integration.py -v

# E2E пайплайн на публічних UAV-датасетах (EuRoC / VPAIR / MARS-LVIG)
pytest tests/e2e/ -q                    # unit + skip-when-absent (швидко)
pytest tests/e2e/ -m "e2e and not e2e_slow" -v   # CI-tier з завантаженим датасетом
pytest tests/e2e/ -m e2e_slow -v        # nightly-tier (VPAIR sample, MARS-LVIG stress)

# EuRoC Machine Hall bundle — 12.6 GB, DOI 10.3929/ethz-b-000690084
# Завантажити вручну (DSpace UI без прямого URL), розпакувати внутрішній
# MH_0N_easy.zip у datasets/euroc/MH_0N/, щоб існував mav0/
# SHA256 зашитий у DATASET_REGISTRY ("euroc_machine_hall") для верифікації

# VPAIR sample (fixed-wing, downward, 300-400 м) — form-gated на Zenodo
# Розпакувати так, щоб datasets/vpair/sample/poses_query.txt існував
# SHA256 зашитий у DATASET_REGISTRY ("vpair_sample") для верифікації

# Для автоматизованих entry (коли з'являться) — той самий CLI:
python scripts/download_dataset.py <dataset_name>

E2E-харнес гонить FlightProcessor як black-box через спільний DatasetAdapter (src/gps_denied/testing/). Датасети лежать у ./datasets/ (gitignored), тести пропускаються (не фейляться) коли датасету немає. Детально — у локальному design doc .planning/brainstorms/2026-04-16-e2e-datasets-design.md та плані 2026-04-16-e2e-datasets-plan.md.

Поточний статус реальних прогонів:

  • VPAIR sample (200 кадрів fixed-wing 300-400 м над Bonn/Eifel): пайплайн завершується, ATE RMSE ~1770 км → xfail. Причина: VPAIR не ship raw IMU → ESKF-шлях не активний; VO-самостійно розходиться.
  • EuRoC MH_01 (перші 100 кадрів indoor MAV, ASL формат): пайплайн завершується за ~30 с, ATE RMSE ~10.9 км → xfail. Raw IMU є і ESKF активний, але satellite-anchoring для indoor сцени не релевантний, тому VO+ESKF без якоря дрейфує. Експерименти з більшою довжиною/тюнінгом — після стабілізації GPR+ESKF.
  • MARS-LVIG — тести скіпаються (датасет не завантажений локально).

Покриття тестами (195 passed / 8 skipped — unit/component; e2e — окремо)

Файл тесту Компонент К-сть
test_schemas.py Pydantic схеми 12
test_database.py SQLAlchemy CRUD 9
test_api_flights.py REST endpoints 5
test_health.py Health check 1
test_eskf.py ESKF 15-state 17
test_coordinates.py ENU/GPS/pixel 4
test_satellite.py Тайли + Mercator 8
test_pipeline.py Image queue 5
test_rotation.py 360° ротації 4
test_models.py Model Manager + TRT 6
test_vo.py VO (ORB + cuVSLAM) 8
test_gpr.py Place Recognition (Faiss) 7
test_metric.py Metric Refinement + GSD 6
test_graph.py Factor Graph (GTSAM) 4
test_chunk_manager.py Chunk lifecycle 3
test_recovery.py Recovery coordinator 2
test_processor_full.py State Machine 4
test_processor_pipe.py PIPE wiring (Phase 5) 13
test_mavlink.py MAVLink I/O bridge 19
test_acceptance.py AC сценарії + perf 6
test_accuracy.py Accuracy validation 23
test_sitl_integration.py SITL (skip без ArduPilot) 8
Всього 195+8

Benchmark валідації (Phase 7)

python scripts/benchmark_accuracy.py --frames 50

Результати на синтетичній траєкторії (20 м/с, 0.7 fps, шум VO 0.3 м, супутник кожні 5 кадрів):

Критерій Результат Ліміт
80% кадрів ≤ 50 м 100% ≥ 80%
60% кадрів ≤ 20 м 100% ≥ 60%
p95 затримка ~9 мс < 400 мс
VO дрейф за 1 км ~11 м < 100 м

Структура проєкту

gps-denied-onboard/
├── src/gps_denied/
│   ├── app.py                     # FastAPI factory + lifespan
│   ├── config.py                  # Pydantic Settings
│   ├── api/routers/flights.py     # REST + SSE endpoints
│   ├── core/
│   │   ├── eskf.py                # 15-state ESKF (IMU+VO+satellite fusion)
│   │   ├── processor.py           # FlightProcessor + process_frame
│   │   ├── vo.py                  # ORBVisualOdometry / CuVSLAMVisualOdometry
│   │   ├── mavlink.py             # MAVLinkBridge → GPS_INPUT → ArduPilot
│   │   ├── satellite.py           # SatelliteDataManager (local z/x/y tiles)
│   │   ├── gpr.py                 # GlobalPlaceRecognition (Faiss/numpy)
│   │   ├── metric.py              # MetricRefinement (LiteSAM/XFeat + GSD)
│   │   ├── graph.py               # FactorGraphOptimizer (GTSAM ISAM2)
│   │   ├── coordinates.py         # CoordinateTransformer (ENU↔GPS↔pixel)
│   │   ├── models.py              # ModelManager + TRTInferenceEngine
│   │   ├── benchmark.py           # AccuracyBenchmark + SyntheticTrajectory
│   │   ├── pipeline.py            # ImageInputPipeline
│   │   ├── rotation.py            # ImageRotationManager
│   │   ├── recovery.py            # FailureRecoveryCoordinator
│   │   └── chunk_manager.py       # RouteChunkManager
│   ├── schemas/                   # Pydantic схеми (eskf, mavlink, vo, ...)
│   ├── db/                        # SQLAlchemy ORM + async repository
│   └── utils/mercator.py          # Web Mercator tile utilities
├── tests/                         # 22 test модулі
├── scripts/
│   └── benchmark_accuracy.py      # CLI валідація точності
├── Dockerfile                     # Multi-stage Python 3.11 image
├── docker-compose.yml             # Local dev
├── docker-compose.sitl.yml        # ArduPilot SITL harness
├── .github/workflows/
│   ├── ci.yml                     # lint + pytest + docker smoke (кожен push)
│   └── sitl.yml                   # SITL integration (нічний / ручний)
└── pyproject.toml

Компоненти

ID Назва Файл Dev Jetson
F04 Satellite Data Manager core/satellite.py local tiles local tiles
F05 Image Input Pipeline core/pipeline.py
F06 Image Rotation Manager core/rotation.py
F07 Sequential Visual Odometry core/vo.py ORB cuVSLAM
F08 Global Place Recognition core/gpr.py numpy Faiss GPU
F09 Metric Refinement core/metric.py Mock LiteSAM/XFeat TRT
F10 Factor Graph Optimizer core/graph.py Mock GTSAM ISAM2
F11 Failure Recovery core/recovery.py
F12 Route Chunk Manager core/chunk_manager.py
F13 Coordinate Transformer core/coordinates.py
F16 Model Manager core/models.py Mock TRT engines
F17 ESKF Sensor Fusion core/eskf.py
F18 MAVLink I/O Bridge core/mavlink.py Mock pymavlink

Що залишилось (on-device)

  1. Офлайн завантаження тайлів для зони місії → {tile_dir}/z/x/y.png
  2. Конвертація моделей: LiteSAM/XFeat PyTorch → ONNX → TRT FP16
  3. Запуск SITL: docker compose -f docker-compose.sitl.yml up
  4. Польотні дані: записати GPS + відео → порівняти ESKF-траєкторію з ground truth
  5. Калібрування: camera intrinsics + IMU noise density для конкретного апарату

Ліцензія

Приватний репозиторій. Усі права захищено.