Разбор: human_tracking

← Все разборы примеров

Источник: репозиторий pioneer-team/pioneer-sdk2-example (GitFlic). Справочник по классам API — на странице «Pioneer SDK 2».

Две версии

  • human_tracking_simple.py — учебный минимум: дрон взлетает, ищет самого крупного человека в кадре, поворачивается к нему, при потере цели зависает. Полёта вперёд/назад нет.
  • human_tracking_rknn.py — расширенный: распознаёт позу, рисует скелет, подтверждает жесты несколькими кадрами подряд, двигает дрон по жестам, делает фото и садится по жесту посадки.

Обе программы используют модель YOLO Pose из pioneer_rknn (про NPU — на странице «Нейросети (RKNN)»):

from pioneer_sdk2 import Pioneer, Camera, ImageViewer, CameraType
from pioneer_rknn import YoloPose

camera = Camera(camera_type=CameraType.MAIN)     # основная камера
model = YoloPose(model_name="yolov8n-pose")      # модель из реестра Pioneer-RKNN

Вход нейросети — 640×640, поэтому кадр масштабируется перед инференсом, а координаты рамок пересчитываются обратно к размеру исходного кадра.

human_tracking_simple.py — сопровождение разворотом

Найдя людей, скрипт выбирает рамку с максимальной площадью и «доворачивает» дрон к её центру. Задача скорости решается простым П-регулятором:

def select_main_box(boxes):                             # функция выбирает самого крупного человека
    if not boxes:                                       # проверяем, есть ли найденные рамки
        return None                                     # возвращаем None, если человека нет

    def box_area(box):                                  # функция считает площадь рамки
        x1, y1, x2, y2 = box                            # получаем координаты рамки
        return (x2 - x1) * (y2 - y1)                    # возвращаем площадь рамки

    return max(boxes, key=box_area)                     # выбираем рамку с максимальной площадью


def get_tracking_speed(box, frame_width):               # функция считает скорость для сопровождения человека
    x1, _, x2, _ = box                                  # получаем горизонтальные координаты рамки человека
    box_center_x = (x1 + x2) / 2                        # центр рамки по X

    yaw_error = frame_width / 2 - box_center_x          # ошибка по горизонтали: человек левее/правее центра

    vx = 0.0                                            # по оси X корпуса в этом примере не двигаемся
    vy = 0.0                                            # вперед/назад не летим: расстояние по размеру рамки ненадежно
    vz = 0.0                                            # высоту удерживает автопилот после выхода на рабочую высоту
    yaw_rate = limit(YAW_KP * yaw_error, -MAX_YAW_RATE, MAX_YAW_RATE) # скорость поворота

    return vx, vy, vz, yaw_rate                         # возвращаем команду скорости
  • yaw_error — ошибка по горизонтали: смещение центра рамки от середины кадра в пикселях.
  • yaw_rate = YAW_KP * yaw_error — классический П-регулятор: чем дальше человек от центра, тем быстрее поворот; limit() ограничивает скорость диапазоном MAX_YAW_RATE.
  • По осям vx, vy, vz — нули намеренно: README подчёркивает, что оценка расстояния по размеру рамки слишком груба для безопасного учебного примера.

Основной цикл типовой: кадр → подготовка → инференс → рамки → команда:

            model_input = resize_for_model(frame)       # подготавливаем кадр для нейросети
            detections = model.run([model_input])       # запускаем распознавание человека
            boxes = get_person_boxes(frame, detections) # получаем рамки найденных людей
            main_box = select_main_box(boxes)           # выбираем самого крупного человека

            if main_box is None:                        # если человек не найден
                speed = ZERO_SPEED                      # задаем нулевую скорость
                send_speed_command(drone, speed)        # отправляем команду зависания
                cv2.putText(frame, "person not found", (20, 40),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)
            else:
                speed = get_tracking_speed(main_box, frame.shape[1]) # считаем скорость
                send_speed_command(drone, speed, immediate=False) # отправляем скорость по таймеру
                draw_box(frame, main_box, speed)         # рисуем рамку и команду

            viewer.imshow(name="human_tracking", frame=frame, fps=30) # публикуем кадр

Три механизма безопасности, повторяющиеся в обоих файлах:

  • Нет кадра (timeout или None) — немедленная нулевая скорость, дрон зависает.
  • Новая скорость отправляется сразу, а повтор той же — не чаще, чем раз в SEND_PERIOD (0,2 с); интервал действия команды COMMAND_INTERVAL — 0,3 с, так что команды перекрываются.
  • В finally по get_fly_state().name решается исход: IN_SKY → нулевая скорость + land(); ARMEDdisarm(); после освобождаются viewer, camera, соединение и ресурсы модели model.release().

human_tracking_rknn.py — жесты вместо клавиш

Скелет человека (17 ключевых точек COCO-17) позволяет распознавать жесты математически: сравниваются координаты плеч, локтей, запястий и бёдер. Соответствие жестов и команд:

ЖестКоманда дрону
Поднятая вверх согнутая левая рукаВперёд, ближе к человеку
Поднятая вверх согнутая правая рукаНазад, дальше от человека
Вытянутая вбок левая рукаВлево
Вытянутая вбок правая рукаВправо
Сведённые или скрещенные руки перед грудьюФото с задержкой 5 с
Две опущенные руки, согнутые в локтяхПосадка

Функция classify_gesture() (в файле ~60 строк) по индексам COCO-17 проверяет углы и пропорции рук относительно корпуса. Для обработки ключевых точек важна уверенность детекции:

STABLE_FRAMES = 10                                      # сколько кадров подряд нужно видеть жест для подтверждения
LAND_STABLE_FRAMES = 18                                 # посадку подтверждаем дольше, чтобы избежать случайного срабатывания
GESTURE_COOLDOWN = 2.0                                  # пауза между повторным выполнением одного и того же жеста
PHOTO_DELAY = 5.0                                       # задержка перед сохранением фотографии в секундах

Стабилизация жеста

Чтобы случайно поднятая рука не превратилась в команду, жест считается командой только после STABLE_FRAMES кадров подряд; для посадки порог выше — LAND_STABLE_FRAMES. Плюс кулдаун между повторами:

def get_stable_gesture(raw_gesture):                    # функция подтверждает жест несколькими кадрами подряд
    gesture_history.append(raw_gesture)                 # добавляем текущий жест в общую историю

    if raw_gesture == "none":                           # none не нужно подтверждать как команду
        return "none", False                            # возвращаем отсутствие жеста

    required_frames = LAND_STABLE_FRAMES if raw_gesture == "land" else STABLE_FRAMES # для посадки нужен более строгий порог
    recent_gestures = list(gesture_history)[-required_frames:] # берем последние распознанные жесты

    if len(recent_gestures) == required_frames and all(item == raw_gesture for item in recent_gestures): # проверяем стабильность жеста
        return raw_gesture, True                         # возвращаем подтвержденный жест

    return raw_gesture, False                            # жест виден, но еще не подтвержден


def can_execute_action(gesture):                         # функция проверяет задержку между повторными действиями
    now = time.time()                                    # получаем текущее время
    last_time = last_action_time.get(gesture, 0)         # получаем время прошлого выполнения жеста

    if now - last_time < GESTURE_COOLDOWN:               # проверяем, прошла ли пауза
        return False                                     # если пауза не прошла, действие не выполняем

    last_action_time[gesture] = now                      # обновляем время выполнения жеста
    return True                                          # разрешаем выполнить действие

Здесь два независимых «замка»:

  • get_stable_gesture() следит за deque последних распознанных кадров: команда выполняется, только если все последние кадры дают тот же жест.
  • can_execute_action() добавляет паузу GESTURE_COOLDOWN = 2.0 с между повторным выполнением одного и того же жеста.

Основной цикл и приоритеты

Приоритеты решений в главном цикле укладываются в четыре ветки: подтверждённое фото → подтверждённая посадка (выход из цикла) → управляющие жесты; иначе — доворот к человеку:

            if main_pose is not None:                    # проверяем, найден ли человек в кадре
                raw_gesture = classify_gesture(main_pose["keypoints"]) # определяем текущий жест
                stable_gesture, stable = get_stable_gesture(raw_gesture) # проверяем, стабилен ли жест
                draw_pose(frame, main_pose, raw_gesture, stable)       # рисуем скелет и подпись жеста

                if stable and stable_gesture == "photo" and can_execute_action("photo"): # проверяем подтвержденный жест фото
                    update_photo_timer(frame)            # запускаем таймер сохранения фото
                elif photo_timer != -1:                  # если таймер фото уже запущен
                    update_photo_timer(frame)            # продолжаем отсчет до сохранения

                if stable and stable_gesture == "land" and can_execute_action("land"): # проверяем подтвержденный жест посадки
                    print("Распознан подтвержденный жест посадки") # выводим сообщение пользователю
                    break                                # выходим из цикла, посадка выполнится в finally

                elif stable and stable_gesture in ["left", "right", "forward", "backward"]: # проверяем управляющие жесты
                    send_gesture_speed(drone, stable_gesture) # отправляем команду движения по жесту
                else:                                    # если управляющий жест не найден
                    send_tracking_speed(drone, main_pose, frame.shape[1]) # разворачиваем дрон к человеку

            else:                                        # если человек не найден
                cv2.putText(frame, "person not found", (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 255), 2) # пишем сообщение на кадре
                send_hover_speed(drone)                  # отправляем команду зависания

            viewer.imshow(name="human_tracking", frame=frame, fps=30) # отправляем кадр в RTSP-трансляцию

Один случайный жест проблем не вызовет: он считается кандидатом (жёлтая подпись на кадре) и исполняется только после подтверждения на STABLE_FRAMES кадрах (зелёная подпись). Полный разбор визуального интерфейса жестов с картинками — в README директории репозитория.

Запуск и рекомендации

python3 human_tracking_simple.py
python3 human_tracking_rknn.py

Обработанный кадр — rtsp://10.42.0.1:8889/human_tracking/. Рекомендации README:

  • Контрастный фон и хорошее освещение делают детекцию стабильнее.
  • Первый полёт — с небольшими MAX_VX, MAX_VY и MAX_YAW_RATE; коэффициенты и ограничители меняйте постепенно.
  • Не запускайте рядом с людьми, стенами и предметами, которые могут попасть в зону винтов.