Разбор: 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();ARMED→disarm(); после освобождаются 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; коэффициенты и ограничители меняйте постепенно. - Не запускайте рядом с людьми, стенами и предметами, которые могут попасть в зону винтов.