Полет в туннеле, между зданиями, внутри склада или под глушилками — стандартная ситуация, когда GPS ловит 2 спутника или ноль. Дрон без внешнего позиционирования уходит в дрифт и через 10 секунд врезается в стену. Особенно критично для дорогостоящих коммерческих аппаратов. Мы решаем такие задачи на каждом втором проекте: от инспекции мостов до логистики на складах 10000 м². Разрабатываем системы автономной навигации под ключ — от выбора сенсоров до отказоустойчивого софта. Оценим вашу задачу за 3 рабочих дня.
Разработка AI-системы автономной навигации дронов: почему без GPS не обойтись?
VIO — это слияние данных с камеры и IMU. Камера даёт визуальные ориентиры, IMU — угловые скорости и ускорения. Алгоритм (например, VINS-Mono или ORB-SLAM3) оптимизирует совместную ошибку. На практике это даёт точность 5–15 см в горизонтальной плоскости при хорошем освещении. Код ниже — упрощённая иллюстрация цикла: детектируем ORB-фичи, сопоставляем, восстанавливаем движение через essential matrix и интегрируем с IMU.
import numpy as np
import cv2
from scipy.spatial.transform import Rotation
class VisualInertialOdometry:
"""
VINS-Mono / ORB-SLAM3 логика — упрощённо.
На практике используем готовые библиотеки с ROS2 интеграцией.
"""
def __init__(self, camera_matrix: np.ndarray,
imu_noise: dict):
self.K = camera_matrix
self.orb = cv2.ORB_create(nfeatures=500)
self.matcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
self.prev_frame = None
self.prev_kp = None
self.prev_desc = None
# Состояние: позиция + ориентация
self.position = np.zeros(3)
self.rotation = np.eye(3)
self.imu_noise = imu_noise
def update(self, frame: np.ndarray,
imu_data: dict) -> dict:
"""Обновление оценки позиции по кадру + IMU"""
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
kp, desc = self.orb.detectAndCompute(gray, None)
if self.prev_frame is not None and desc is not None:
matches = self.matcher.match(self.prev_desc, desc)
matches = sorted(matches, key=lambda x: x.distance)[:100]
if len(matches) > 20:
pts1 = np.float32([self.prev_kp[m.queryIdx].pt
for m in matches])
pts2 = np.float32([kp[m.trainIdx].pt for m in matches])
E, mask = cv2.findEssentialMat(pts1, pts2, self.K,
method=cv2.RANSAC,
prob=0.999, threshold=1.0)
if E is not None:
_, R, t, _ = cv2.recoverPose(E, pts1, pts2, self.K)
# Интеграция движения
self.position += self.rotation @ t.flatten()
self.rotation = R @ self.rotation
self.prev_frame = gray
self.prev_kp = kp
self.prev_desc = desc if desc is not None else self.prev_desc
return {
'position': self.position.copy(),
'rotation': self.rotation.copy(),
'tracked_features': len(kp) if kp else 0
}
Как обходить препятствия без лидара?
Для indoor-полётов стереокамера (Intel RealSense D435) даёт плотную карту глубины с частотой 30 кадров/с и потребляет всего 2 Вт. LiDAR (например, Velodyne Puck) даёт точнее, но стоит $4000+ и весит 400 г — не каждый дрон поднимет. Наш стек использует стерео как базовый сенсор и опционально добавляет ToF-камеру для outdoor. Алгоритм ниже делит поле зрения на 5 секторов и вычисляет минимальное расстояние в каждом — этого достаточно для collision avoidance со скоростью до 2 м/с.
| Сенсор | Разрешение по глубине | Дальность | Потребление | Вес | Стоимость |
|---|---|---|---|---|---|
| Intel RealSense D435 | 1280x720 px | до 10 м | 2 Вт | 72 г | $300 |
| Velodyne Puck (LiDAR) | 360° x 30° | до 100 м | 8 Вт | 400 г | $4000 |
| ToF-камера (L515) | 1024x768 px | до 9 м | 3 Вт | 100 г | $350 |
class ObstacleAvoidance:
def __init__(self, depth_camera, safety_distance: float = 1.5):
self.depth_cam = depth_camera
self.safety_dist = safety_distance # метры
self.fov_h = 87 # градусов (RealSense D435)
self.sectors = 5 # делим поле зрения на 5 секторов
def compute_clear_directions(self,
depth_frame: np.ndarray) -> dict:
"""Находим свободные направления полёта"""
h, w = depth_frame.shape
sector_width = w // self.sectors
clearance = {}
for i in range(self.sectors):
sector = depth_frame[:, i*sector_width:(i+1)*sector_width]
# Игнорируем нулевые значения (нет данных)
valid = sector[sector > 0]
if len(valid) == 0:
clearance[i] = float('inf')
continue
# P10 — ближняя граница препятствия в секторе
min_dist = float(np.percentile(valid, 10)) / 1000.0 # мм -> м
clearance[i] = min_dist
# Направление с максимальным clearance
best_sector = max(clearance, key=clearance.get)
angle = (best_sector - self.sectors // 2) * (self.fov_h / self.sectors)
return {
'clearance_by_sector': clearance,
'best_direction_angle': angle,
'is_path_clear': clearance[self.sectors//2] > self.safety_dist
}
Планирование маршрута: 3D Occupancy Grid vs. RRT*
Occupancy grid — детерминированный метод: каждую ячейку (0.2 м³) помечаем как свободную или занятую. A* гарантирует кратчайший путь в дискретном пространстве. Для динамических препятствий лучше подходит RRT*, но он даёт неоптимальное по длине решение. Мы комбинируем: на этапе построения карты — occupancy grid, а для перепланирования в реальном времени — RRT* с limit на 100 итераций. Код ниже — классический A* на sparse grid.
import heapq
class OccupancyGridPlanner:
def __init__(self, resolution: float = 0.2):
self.resolution = resolution # метров на ячейку
self.grid = {} # 3D sparse grid: (ix, iy, iz) -> occupancy
def update_from_depth(self, depth_frame: np.ndarray,
camera_pose: np.ndarray):
"""Обновляем карту препятствий"""
# Конвертация depth в point cloud
points = self._depth_to_pointcloud(depth_frame)
# Трансформация в мировые координаты
points_world = (camera_pose[:3, :3] @ points.T).T + camera_pose[:3, 3]
for pt in points_world:
ix, iy, iz = (int(pt[0] / self.resolution),
int(pt[1] / self.resolution),
int(pt[2] / self.resolution))
self.grid[(ix, iy, iz)] = 1 # occupied
def astar_3d(self, start: tuple, goal: tuple) -> list:
"""A* в 3D occupancy grid"""
def heuristic(a, b):
return np.sqrt(sum((a[i]-b[i])**2 for i in range(3)))
heap = [(0, start)]
came_from = {start: None}
cost = {start: 0}
while heap:
_, current = heapq.heappop(heap)
if current == goal:
break
for dx, dy, dz in [(1,0,0),(-1,0,0),(0,1,0),
(0,-1,0),(0,0,1),(0,0,-1)]:
neighbor = (current[0]+dx, current[1]+dy, current[2]+dz)
if self.grid.get(neighbor, 0) == 1:
continue # препятствие
new_cost = cost[current] + 1
if neighbor not in cost or new_cost < cost[neighbor]:
cost[neighbor] = new_cost
priority = new_cost + heuristic(neighbor, goal)
heapq.heappush(heap, (priority, neighbor))
came_from[neighbor] = current
# Восстанавливаем путь
path = []
node = goal
while node is not None:
path.append(node)
node = came_from.get(node)
return list(reversed(path))
Кейс из нашей практики: автономная инспекция склада 8000 м²
Наш клиент — логистический оператор, которому требовалось ежемесячно проверять состояние стеллажей на 4 этажах. Высота стеллажей до 12 м, узкие проходы 2 м, полное отсутствие GPS. Мы разработали дрон на базе DJI F450 с кастомным полётным контроллером, оснащённым Intel RealSense D435i (глубина + IMU). Стек: ROS2 Humble + PX4 Autopilot + форк ORB-SLAM3 с EKF-фильтром для слияния с барометром.
Результат: точность локализации ±8 см по горизонтали, ±5 см по вертикали. Скорость облёта — 0.5 м/с, один ряд стеллажей (50 м) проходит за 110 секунд. Система работает без сбоев уже более года — 50+ миссий.
Процесс внедрения
Подробнее о калибровке сенсоров
Калибровка камеры и IMU выполняется с помощью Kalibr или собственного скрипта. Требуется несколько статических поз с разной ориентацией. В результате получаем матрицу камеры и коэффициенты дисторсии, а также матрицу вращения между камерой и IMU. Типичная точность после калибровки — ошибка reprojection < 0.5 пикселя.- Анализ сценария: замеры освещения, геометрии, материалов (металл/бетон — разная отражательная способность). Выбор сенсоров.
- Прототипирование VIO: калибровка камеры+IMU, подбор параметров ORB (количество фич, threshold).
- Обход препятствий: настройка safety_distance, интеграция depth в полётный контроллер через MAVSDK.
- Планировщик: выбор между occupancy grid и RRT* под динамику задачи.
- Тесты: полёт в симуляторе (Gazebo + PX4 SITL), затем реальные вылеты с дублирующим пультом.
Сроки и ориентировочная стоимость
| Тип проекта | Срок | Комментарий |
|---|---|---|
| Базовая VIO навигация | 6–10 недель | Включает калибровку, тесты, полётный контроллер |
| Полная autonomous navigation (VIO + obstacle avoidance + planning) | 3–5 месяцев | + Occupancy grid, RRT*, ROS2 integration |
| Сертификация для коммерческих полётов (опционально) | +3–6 месяцев | Зависит от регулятора (EASA, FAA) |
Стоимость рассчитывается индивидуально — зависит от сложности сценария и количества итераций тестов. Окупаемость проекта обычно наступает в течение 6 месяцев за счёт снижения затрат на ручной контроль. Получите консультацию — оценим вашу задачу за 3 дня.
Что вы получаете
- Исходный код VIO-модуля с комментариями (Python/C++).
- Docker-образ с ROS2-воркспейсом для воспроизводимости.
- Калибровочные файлы камеры и IMU.
- Интеграция с полётным контроллером (PX4/ArduPilot).
- Документация по настройке и эксплуатации.
- 2 месяца поддержки после внедрения.
Почему выбирают нашу разработку?
Мы занимаемся AI-навигацией для дронов более 5 лет: 20+ реализованных проектов для складов, шахт, мостов и нефтевышек. Используем только open-source стеки (ORB-SLAM3, ROS2, PX4) — никакого vendor lock-in. Все решения тестируем в симуляторе на 100+ сценариях перед первым полётом. Свяжитесь с нами — расскажем, какие сенсоры и алгоритмы подойдут именно под вашу задачу.







