Глава 10. Применение нейросети
1. Цель занятия и обзор плана
На этом занятии мы научимся применять обученную модель YOLO для автономного управления катамараном. Мы разберём, какие данные возвращает нейросеть, как их фильтровать, как рассчитывать азимут на объект и как интегрировать всё это с регулятором и машиной состояний.
После завершения занятия вы сможете:
-
получать и интерпретировать результаты детекции YOLO;
-
фильтровать объекты по уверенности (confidence) и по классам;
-
выбирать лучший объект на класс;
-
рассчитывать азимут на обнаруженный объект;
-
наводить катамаран на ворота с помощью регулятора;
-
объединять всё в машину состояний для автономного прохождения задания.
2. Теоретический материал
2.1. Данные от YOLO
Модель YOLO возвращает для каждого обнаруженного объекта три вещи:
-
Bounding box (рамка) — координаты прямоугольника, описывающего объект.
-
Confidence (уверенность) — число от 0 до 1, показывающее, насколько модель уверена в обнаружении. Чем ближе к 1 — тем увереннее.
-
Class ID — идентификатор класса объекта. Через словарь
model.namesего можно преобразовать в текстовое имя (например, «red buoy»).
results = model.predict(frame, verbose=False)
boxes = results[0].boxes
for i in range(len(boxes)):
cls_id = int(boxes.cls[i].item())
cls_name = model.names[cls_id] # имя класса, например "red_buoy"
conf = boxes.conf[i].item() # уверенность, например 0.87
xywh = boxes.xywh[i].tolist() # [center_x, center_y, width, height] в пикселях
print(f"{cls_name}: {conf:.2f}, bbox: {xywh}")
2.2. Форматы bounding box
YOLO поддерживает несколько форматов координат рамки:
-
xyxy— координаты верхнего левого (x1, y1) и нижнего правого (x2, y2) углов в пикселях. -
xywh— центр (x, y) и размеры (w, h) в пикселях. -
xywhn— то же, чтоxywh, но нормализовано (от 0 до 1). Нормализованные значения удобнее: они не зависят от разрешения изображения. Чтобы получить нормализованные координаты, достаточно добавитьnв конце:boxes.xywhn.
На практике для расчёта азимута удобнее всего использовать xywhn — нормализованную координату центра по оси X.
2.3. Фильтрация по уверенности (confidence)
Не все обнаруженные объекты достоверны. Модель может «увидеть» буй там, где его нет — просто с низкой уверенностью. Поэтому устанавливается пороговое значение: все обнаружения с confidence ниже порога отбрасываются.
Обычно порог начинают с 0.5 (50%). Это означает: «если модель уверена менее чем наполовину — считаем это ложным обнаружением».
results = model.predict(
frame,
conf=0.5, # пороговое значение уверенности
verbose=False
)
Порог можно регулировать через Dynamic Reconfigure в RQT. Запустите пример:
$ ros2 launch lesson_10 main.launch.py lesson_num:=1
Попробуйте увеличить порог до 0.8 — заметите, что объекты пропадают и появляются только когда модель очень уверена (обычно при приближении). Уменьшите до 0.3 — появится больше обнаружений, но и больше ложных срабатываний.
Чем выше порог, при котором модель стабильно работает — тем лучше она обучена.
2.4. Фильтрация по классам
Если нас интересуют только определённые объекты (например, только буи для прохождения ворот), можно ограничить детекцию нужными классами:
# Список нужных классов
allowed_classes = ['red_buoy', 'yellow_buoy']
# Получаем ID этих классов
allowed_ids = [key for key, value in model.names.items() if value in allowed_classes]
results = model.predict(
frame,
conf=0.5,
classes=allowed_ids, # детектировать только указанные классы
verbose=False
)
Теперь модель будет возвращать только красные и жёлтые буи, игнорируя все остальные объекты — даже если они есть в кадре.
Выбор лучшего объекта на класс
Если модель обнаружила несколько объектов одного класса (например, два красных буя), полезно оставить для каждого класса только тот, в котором модель больше всего уверена:
def filter_best_per_class(results):
boxes = results[0].boxes
best_idx = []
for cls in boxes.cls.unique():
cls_mask = boxes.cls == cls
cls_conf = boxes.conf[cls_mask]
cls_indices = cls_mask.nonzero(as_tuple=True)[0]
best_idx.append(int(cls_indices[cls_conf.argmax()]))
results[0].boxes = boxes[best_idx]
После этой фильтрации у нас остаётся по одному объекту каждого класса — с максимальной уверенностью.
3. Практическое занятие
3.1. Расчёт азимута на объект
Зная горизонтальный угол обзора камеры (FOV) и нормализованную координату центра объекта, можно рассчитать азимут — угол отклонения объекта от оптической оси камеры.
В нашем симуляторе FOV = 90°. Для углов до 90° можно использовать линейную аппроксимацию:
# x_center_n — нормализованная координата центра (0 = левый край, 1 = правый)
# fov — горизонтальный угол обзора камеры в градусах
azimuth_deg = (x_center_n - 0.5) * fov
Результат: 0° — объект в центре кадра. Отрицательные значения — объект левее. Положительные — правее.
В коде это выглядит так:
fov = camera_info.fov # получаем FOV камеры
for box in results[0].boxes:
x_center_n = box.xywhn[0][0].item() # нормализованная X-координата центра
cls_name = model.names[int(box.cls.item())]
azimuth = (x_center_n - 0.5) * fov
data.append((cls_name, azimuth, box.conf.item()))
Запуск примера:
$ ros2 launch lesson_10 main.launch.py lesson_num:=2
На обработанном изображении в левом верхнем углу будет отображаться азимут. Поворачивайте катамаран и наблюдайте, как меняется угол: объект слева — минус, справа — плюс, в центре — около нуля.
3.2. Азимут на ворота
Ворота — это не один объект, а два буя (красный и жёлтый). Чтобы катамаран двигался в центр ворот, нужно рассчитать средний азимут:
def eval_gate_azimuth(data):
if len(data) < 2: # нужны оба буя
return None
azimuth_sum = sum([azimuth for _, azimuth, _ in data])
return azimuth_sum / len(data)
Суммируем азимуты всех найденных буёв и делим на их количество — получаем направление на центр ворот.
Запуск примера:
$ ros2 launch lesson_10 main.launch.py lesson_num:=3
В RQT через Topic Monitor можно наблюдать значение целевого азимута в топике target_azimuth. Когда вы смотрите левее ворот — азимут отрицательный (нужно повернуть влево). Когда правее — положительный. Когда ворота по центру — близок к нулю.
3.3. Регулятор
На основе целевого азимута можно реализовать регулятор: если азимут отрицательный (ворота левее) — поворачиваем влево; если положительный — вправо. Одновременно катамаран движется вперёд.
Это дискретный регулятор, аналогичный тому, что мы использовали в предыдущих занятиях. Он стремится свести ошибку по азимуту к нулю:
$ ros2 launch lesson_10 main.launch.py lesson_num:=4
Через Dynamic Reconfigure в RQT можно настроить параметры регулятора:
-
линейная скорость — скорость движения вперёд. Увеличите — катамаран быстрее поедет к цели.
-
коэффициент реакции — чувствительность к ошибке азимута. Увеличите — катамаран будет резче реагировать на отклонение, вплоть до колебаний (синусоидальная траектория). Уменьшите — реакция будет плавнее, но медленнее.
Там же есть выключатель регулятора. Остановили — катамаран замирает. Включили — продолжает движение.
Подбирайте параметры так, чтобы катамаран двигался к воротам плавно, без резких рывков. Возможно, стоит реализовать ПИД-регулятор из предыдущих работ — он даст более плавное и точное управление.
3.4. Интеграция с машиной состояний
На финальном шаге все компоненты объединяются: YOLO-детекция, расчёт азимута, регулятор и машина состояний из урока 08. Миссия выглядит примерно так:
-
WaitStart — ждём команду на старт.
-
ResetMotion — сбрасываем скорости.
-
MoveForward — двигаемся вперёд некоторое время.
-
SearchGate — начинаем искать ворота (поворачиваемся, пока не обнаружим буи).
-
Autopilot — двигаемся в режиме автопилота в сторону ворот.
Запуск:
$ ros2 launch lesson_10 main.launch.py lesson_num:=5
Загрузите сцену соревнования в симуляторе. Для старта отправьте True в топик start через RQT Message Publisher.
Катамаран начнёт движение, проедет некоторое расстояние вперёд, начнёт поворачиваться в поисках ворот, найдёт их и двинется в их сторону. Его траектория может быть не идеальной — можно настроить параметры регулятора и миссию, чтобы он двигался оптимальнее.
4. Домашнее задание
-
Реализуйте автономное прохождение ворот с использованием вашей нейросети из урока 09.
-
Модифицируйте регулятор для более плавного движения (например, реализуйте ПИД-регулятор).
-
Задача со звёздочкой: реализуйте автономную парковку в гараж с использованием нейросети. По сути, всё выглядит точно так же, но требует небольших модификаций машины состояний.