Дрон-фотограф

Описание проекта

Описание

В данном проекте реализовывается возможность отслеживания действий человека во время автономного полета квадрокоптера «Геоскан Пионер Мини».

С помощью нейронных сетей, которые занимаются распознаванием скелета человека на изображении, программа умеет удерживать человека в кадре ровно по центру и на заданном расстоянии, а также детектировать настраиваемые жесты, которые могут означать всевозможные команды: фотографирование, посадка, подлет ближе к человеку, смещение вбок и так далее.

Возможные проблемы при текущей реализации проекта:

  • Иногда возможно определение скелета с невысокой точностью. Попробуйте изменить фон, чтобы тело было контрастно.

  • Недостаточная скорость реакции на изменение положения человека в пространстве. Исправляется постепенным увеличением коэффициентов регуляторов.

Команды (позы)

  • Поднятая вверх согнутая левая рука - подлет ближе

img\_8.png
  • Поднятая вверх согнутая правая рука - отлет дальше

img\_7.png
  • Вытянутая вбок левая рука - движение влево

img\_10.png
  • Вытянутая вбок правая рука - движение вправо

img\_9.png
  • Скрещенные перед грудью руки - фотография

img\_11.png
  • Две опущенные руки, согнутые в локтях - посадка

img\_12.png

Установка компонентов

Важно

В данном разделе инструкции представлены для графических операционных систем. Во всех операционных системах процесс установки компонентов не отличается, за исключением систем без графического интерфейса в моменте установки интерпретатора Python и среды разработки PyCharm.

Если вы используете в качестве операционной системы не Windows или Mac, то перед установкой пакетов через PIP необходимо установить дополнительные пакеты для самой ОС следующей командой:

sudo apt-get install libxml2-dev libxslt-dev python-dev

Python

Для повторения данного проекта, как и программирования квадрокоптера «Геоскан Пионер Мини» необходимо установить интерпретатор языка программирования Python (версия 3.7 и выше) на ваш компьютер. Это, необходимо, так как все вычисления будут проводиться на компьютере, а на квадрокоптер будут отправляться лишь команды по типу «Лететь в точку».

Рассмотрим установку интерпретатора Python для OS Windows.

Чтобы установить интерпретатор языка Python, необходимо перейти на официальный сайт, открыть вкладку Downloads и выбрать All releases для открытия страницы со всеми релизами Python.

img.png

На 04.07.2021 рекомендуется скачивать версию 3.7.Х или 3.8.Х

img\_1.png

Некоторые релизы не имеют установочных файлов, поэтому поищите версии, в которых будут файлы установки под вашу операционную систему, блок с файлами на странице релиза будет выглядеть примерно так:

img\_13.png

Здесь следует выбрать Windows installer (64 или 32 bit в зависимости от вашей OS)

Запустите скачанный установочный файл и обязательно выберите оба пункта внизу окна, после чего нажмите Install now:

img\_14.png

После окончания установки отключите ограничение на длину переменной Path, нажав на соответствующую кнопку:

img\_15.png

PyCharm

PyCharm – это среда разработки с большим функционалом для удобного написания программ, например, удобные подсказки, или интеграция с системой контроля версий GIT (нуждается в отдельной установке) и др.

Для установки среды разработки PyCharm следует также перейти на официальный сайт и выбрать версию Community, так как она полностью бесплатна.

img\_2.png

После скачивания установочного файла, процесс установки не будет отличаться от установки любой другой программы на ваш ПК.

Скачивание проекта

Есть два варианта скачать проект:

  1. Если у вас установлен Git, то откройте командную строку в директории, куда хотите сохранить папку с проектом и выполните команду:

    git clone https://github.com/DgtalCode/PioneerHumanTracking.git
    

    У вас появится папка PioneerHumanTracking, где будут лежать все исходные файлы.

  2. Если у вас Git не установлен, то перейдите на страницу проекта на GitHub и скачайте zip архив, нажав на зеленую кнопку в верхней части экрана. Распакуйте архив в любую удобную папку.

Пакеты для Python

Сперва нужно установить пакет, который будет использоваться для установки других пакетов:

pip3 install wheel

А далее, для удобства установки всех необходимых пакетов, в корне проекта существует специальный файл, хранящий названия нужных библиотек – requirements.txt. Чтобы установить все библиотеки, которые прописаны внутри него, достаточно вызвать следующую команду:

pip3 install -r requirements.txt

После чего нужно всего лишь дождаться окончания установки всех модулей.

Открытие проекта в PyCharm

Запустите PyCharm, щелкнув два раза по иконке программы:

img\_16.png

У вас откроется приветственное окно программы, где будут отображаться ранее открытые проекты (если они есть):

img\_17.png

Важно

Если вы вышли из программы, не закрыв проект, то при следующем запуске у вас откроется сразу последний проект.

Чтобы открыть скачанный проект нажмите на Open:

img\_18.png

Укажите путь до папки PioneerHumanTracking, которую вы скачали ранее и нажмите Ok:

img\_19.png

Важной частью является вот это небольшое меню, в котором происходит настройка используемого интерпретатора и выбор исполняемого файла:

img\_20.png

Нажмите на «+», чтобы добавить новую конфигурацию:

img\_21.png

img\_22.png

Выберите интерпретатор Python из предложенного списка, который вы установили ранее, а также путь до файла, который нужно запускать, в данном случае, main.py, после чего нажмите Ok.

Теперь панель инструментов будет выглядеть вот так:

img\_23.png

Важно

Средства работы с Git не важны для запуска программы, если их нет - ничего страшного.

Программное обеспечение квадрокоптера

Удостоверьтесь, что на квадрокоптере установлены последние версии прошивок, так как это влияет на стабильность работы всех систем.

Подробнее о прошивках написано в этом разделе

Описание исходного кода

Важно

Внимание! В данном проекте используется старая версия библиотеки Pioneer_SDK. Код нуждается в небольших корректировках! См. описание методов Pioneer_SDK в разделе Программирование.

Модули

Импорт необходимых модулей:

import cv2
import mediapipe as mp
import numpy as np
from piosdk import Pioneer
from collections import namedtuple
import time

Пройдемся по библиотекам:

  • cv2 - это библиотека OpenCV. Она предназначена для работы с компьютерным зрением.

  • mediapipe - это библиотека, разработанная компанией Google для определения на изображении различных частей тела, а если быть точнее, то различных точек на теле человека, из которых уже и стоится его скелет, руки, лицо и прочее. Подробное описание библиотеки mediapipe находится на официальном сайте.

  • numpy - библиотека, предназначенная для удобной и оптимизированной работы с большими числовыми массивами и просто различными математическими функциями.

  • collections - стандартная библиотека, которая дает дополнительные возможности по работе с массивами данных. В данном случае из этой библиотеки используется лишь ее часть под названием namedtuple. С помощью этого класса в дальнейшей работе можно легко создавать массивы, где у каждого элемента будет своё имя.

  • time - библиотека для работы со временем.

  • piosdk - эта библиотека для непосредственного взаимодействия с квадрокоптером. Она не нуждается в установке, так как находится внутри проекта.


Переменные

Далее идет блок кода относящийся уже к конфигурированию нейросети для определения скелета человека:

# создание объектов для работы нейросети:
# для рисования
mpDrawings = mp.solutions.drawing_utils
# предварительный конфигуратор детектора
skeletonDetectorConfigurator = mp.solutions.pose
# создание детектора с некоторыми настройками
skDetector = skeletonDetectorConfigurator.Pose(static_image_mode=False,
                                               min_tracking_confidence=0.8,
                                               min_detection_confidence=0.8,
                                               model_complexity=2)

Здесь нужно пояснить, что Mediapipe - весьма обширная библиотека, включающая в себя множество модулей и дающая множество возможностей. Так, она включает в себя как модули для обнаружения различных точек на теле, так и модули для отрисовки этих точек на изображении. Именно поэтому мы видим, как из библиотеки mediapipe берутся модули drawing_utils (отвечает за рисование) и pose (отвечает за детект скелета на изображении).

Однако в случае с pose мы берем просто сырой класс, из которого нужно еще правильно создать уже полноценный объект, который и будет определять скелет на изображении с заданными нами настройками. Это делается в последней строке данного блока кода. В этой строке можно заметить 4 параметра:

  • static_image_mode - параметр. отвечающий за то, в каком режиме будет работать программа: со статичным изображением или же с видеопотоком.

  • min_tracking_confidence - параметр, отвечающий за минимальный предел трекинга точки. Дело в том, что данная библиотека для значительного ускорения своей работы использует нейросети не для каждого кадра. В начальный момент нейросеть определяет точки скелета на изображении, а затем эти точки с помощью алгоритмов компьютерного зрения просто отслеживаются, и когда точность слежения падает ниже значения, заданного этим параметром, то происходит новое включение нейросети.

  • min_detection_confidence - параметр, который отвечает так же за нижний порог, только на этот раз за порог точности детектирования нейросетью точек, то есть если нейросеть определяет точки скелета с точностью меньше, чем указано данным параметром, то предсказания сбрасываются, то есть ничего не возвращается в программу, пока определение не пройдет качественно.

  • model_complexity - «тяжеловесность» модели. Данный параметр отвечает за то, с какой точностью и с какими затратами ресурсов в принципе нейросеть будет пытаться выполнять свою работу

    • 0 - для устройств, которые не обладают большой вычислительной мощностью, результаты не очень точны.

    • 1 - для устройств, обладающих средней вычислительной мощностью, определение имеет ошибки в относительно сложных условиях, например, при небольших засветах.

    • 2 - для устройств с хорошей вычислительно мощностью, определение будет очень хорошим даже в сложных условиях.


Далее идет строка настройки, откуда будет идти видео: с квадрокоптера или с вашей веб-камеры:

# использование встроенной камеры или камеры квадрокоптера
useIntegratedCam = False

Дело в том, что такое решение позволяет очень удобно и хорошо отлаживать те части программы, в которых напрямую не задействован квадрокоптер, то есть, например, при тестировании определения скелета человека на изображении.


Следующим блоком идет создание объекта, через который в дальнейшем будет происходить считывание видеопотока, то есть в зависимости от выбранной ранее настройки будет создан либо объект, через который будет происходить взаимодействие с квадрокоптером, либо объект, через который будет только приходить видео с веб-камеры компьютера:

# создание источников видео в зависимости от переменной
if not useIntegratedCam:
    pioneer = Pioneer(logger=True,log_connection=True)
    pioneer_cam = Camera()
else:
    cap = cv2.VideoCapture(0)

Затем идет большой блок с объявлением различных переменных, много о которых не скажешь:

# объявление переменных, хранящих ширину и высоту изображения
IMGW, IMGH = None, None

# объявление переменной, хранящей значение нажатой кнопки на клавиатуре
key = -1

# объявление переменной, хранящей время начала отсчета таймера для фотографирования
# то есть текущее время на момент прихода команды на создание фотографии
take_photo_time = -1

# объявление переменной для хранения времени последней отправки команды движения
# чтобы не спамить командами
async_waiter = -1

# объявление переменной, хранящей время начала отсчета таймера для следующего детектирования жестов
# то есть текущее время на момент детектирования жеста, для создание небольшой задержки до след. срабатывания
pose_detected = -1

Так же этот блок продолжается объявлением переменных для работы Пропорционально-Дифференциальных регуляторов, которые занимаются удержанием человека в центре изображения и заданного до него расстояния:

# переменные, хранящие линейные скорости квадрокоптера
vx = .0
vy = .0
vz = .0
vr = np.radians(0)

# переменные для работы ПД регулятора при повороте
yaw_err = 0
yaw_errold = 0
yaw_kp = .04
yaw_kd = .02
yaw_k = 0.1

# переменные для работы ПД регулятора при движении вверх/вниз
z_err = 0
z_errold = 0
z_kp = 0.008
z_kd = 0.001

# переменные для работы ПД регулятора при движении вперед/назад
y_err = 0
y_errold = 0
y_kp = 10
y_kd = 2

Далее объявляется словарь (переменная типа dict), в котором описываются используемые в работе части тела человека (шея, плечо, предплечье и т.д.) и точки, эти части тела образующие, а точнее, то индексы этих точек:

# имена частей тела с индексами точек, образующих их
JOINTS_LIST = {"neck": [33, 0],
               "left_clavicle": [33, 12],
               "left_arm": [12, 14],
               "left_forearm": [14, 16],
               "right_clavicle": [33, 11],
               "right_arm": [11, 13],
               "right_forearm": [13, 15]}

Имена частей тела выбираются и задаются произвольно, ведь это как раз тот блок, который может активно изменяться: если вы собираетесь показывать какие-то жесты ногами, то их спокойно можно сюда добавить по аналогии с представленными частями тела.

Индексы точек берутся основываясь на картинке, которая взята с официального сайта проекта Mediapipe из раздела Pose:

img\_3.png

Важно

Как можно заметить из представленной схемы выше - в скелете нет 33-й точки, которая как-то используется в словаре. Это точка будет рассчитываться в программе самостоятельно и добавляться в массив с оригинальными точками.

33-я точка - это центральная точка между лопатками.


Последним блоком объявления переменных идет создание уникальных именованных массивов:

# массив Точка имеет 4 именованных параметра, описывающих точку скелета
Point = namedtuple('Point', 'x y z visibility')

# массив, содержащий сгенерированные части тела в виде векторов,
# в котором к элементам можно обратиться через точку
Parts = namedtuple("Parts", JOINTS_LIST.keys())

# массив, описывающий конкретную часть тела в виде вектора
Part = namedtuple("Part", 'x y angle')

Объявление массивов таких типов случит исключительно для удобства, ведь хранить данные можно было бы и в обычных кортежах, но обращение к элементам по числовому индексу вносило бы сильную путаницу.

Так, массив Point позволят удобно описать точку скелета, имеющую 4 параметра: 3 пространственные координаты и видимость.

Массив Part позволяет уже описать конкретную часть тела в виде вектора, например, предплечье. У вектора в данном случае есть 3 параметра: координаты на плоскости и направление. Направление у всех векторов является глобальным, то есть не зависит от положения предыдущей части тела. Угол измеряется в градусах и отсчитывается от положительной оси X на координатной плоскости:

img\_4.png

Массив Parts в свою очередь хранит в себе предыдущие массивы типа Part. Обращение к элементам происходит по именам, прописанным в массиве JOINTS_LIST. Служит опять же для упрощения написания и читабельности кода.


Функции

remap(oldValue, oldMin, oldMax, newMin, newMax)

Первой идет функция для преобразования числа из одного диапазона в другой, под названием remap:

def remap(oldValue, oldMin, oldMax, newMin, newMax):
    """
    Функция для преобразования числовых значений из одного диапазона в другой
    """
    oldRange = (oldMax - oldMin)
    if (oldRange == 0):
        newValue = newMin
    else:
        newRange = (newMax - newMin)
        newValue = (((oldValue - oldMin) * newRange) / oldRange) + newMin
    return newValue

convert_points(points)

Далее определяется функция convert_points, которая преобразует относительные координаты всех точек в глобальные. Изначально, после определения нейросетью точек на теле человека, их координаты имеют дробные значения в диапазоне от 0 до 1, данная функция занимается преобразованием дробного числа в привычные координаты в пикселях. Для преобразования обязательно должны быть объявлены переменные IMGW и IMGH, хранящие ширину и высоту изображения в пикселях соответственно.

Также в этой функции производится вычисление 33й, базовой точки, которая находится между лопатками.

def convert_points(points):
    """
    Функция для конвертации определенных нейросетью точек скелета
    из относительных координат (0-1.0) в абсолютные координаты
    """
    # массив, в котором будут храниться конвертированные точки
    converted_points = []

    # генерация базовой точки (между лопатками)
    # х - (х левой лопатки + х правой лопатки) / 2
    # у - (у левой лопатки + у правой лопатки) / 2
    # остальное по аналогии
    base_point = Point(x=round(IMGW * (points[12].x + points[11].x) // 2),
                       y=round(IMGH * (points[12].y + points[11].y) // 2),
                       z=(points[12].z + points[11].z) / 2,
                       visibility=(points[12].visibility + points[11].visibility) / 2)
    # непосредственная конвертация координат точек путем умножения
    # относительной координаты на ширину(высоту) изображения
    # (z и видимость остаются без изменений)
    for p in points:
        converted_points.append(Point(x=round(IMGW * p.x),
                                      y=round(IMGH * p.y),
                                      z=p.z,
                                      visibility=p.visibility))
    converted_points.append(base_point)
    return converted_points

ang(v1)

Затем идет небольшая функция ang, которая возвращает направление вектора:

def ang(v1):
    """
    Функция рассчитывает направление вектора на плоскости и возвращает угол от 0 до 359
    """
    angle = round(np.degrees(np.arctan2(v1[1], -v1[0])))
    angle = remap(angle, -180, 179, 0, 359)
    return round(angle)

Здесь используется функция arctan2, которая позволяет правильно определить угол, на который повернут вектор. Используя только одну известную всем со школы тригонометрическую функцию (sin, cos, tan) добиться правильного определения угла сложно, так как все они имеют некоторые ограничения, например, синус может выдавать одинаковые значения в разных точках, что недопустимо. Arctan2 исправляет все эти недостатки и позволяет получать абсолютно верные углы:

img\_5.png

Как видно из иллюстрации, углы вычисляются в радианах, поэтому их надо конвертировать в градусы используя функцию degrees, после чего округлить функцией round, а в конце для удобства представления происходит изменение диапазона значений с [-179, 180] на [0, 359] функцией remap.

Функция arctan2 принимает 2 аргумента: У-координата вектора, Х-координата вектора.


generate_parts_vectors(pts)

Далее идет функция generate_parts_vectors, которая выражает все части тела через векторы:

def generate_parts_vectors(pts):
    """
    Функция для представления частей тела в виде векторов.
    Принимает набор точек, а возвращает вектора.
    """
    j = {}
    # проход по элементам словаря с именами и точками частей тела
    for joint in JOINTS_LIST.items():
        # 2 точки, образующие часть тела
        pos = joint[1]
        # из переданного набора найденных точек скелета выбираются эти 2 и считается вектор
        vec_x = pts[pos[1]].x - pts[pos[0]].x
        vec_y = pts[pos[1]].y - pts[pos[0]].y
        # сохранение вектора с именем части тела
        j.update({
            joint[0]: Part(vec_x, vec_y, ang([vec_x, vec_y]))
        })
    # конвертация в массив, к элементам которого можно обращаться через точку
    j = Parts(**j)
    return j

Расчет вектора происходит по следующей формуле:

img\_6.png

eq(num1, num2, err=10)

Затем идет функция eq, которая позволяет сравнить 2 не отрицательных числа, но с небольшой погрешностью, то есть ее можно назвать примерным сравнением:

def eq(num1, num2, err=15):
    """
    функция для сравнивания двух чисел с погрешностью
    """
    if num1 < 0 or num2 < 0:
        return True
    if abs(num1-num2) <= err:
        return True
    elif (360-num1) <= err and abs(360 - num1 - num2) <= err:
        return True
    else:
        return False

Если одно из сравниваемых чисел будет отрицательным, то функция обязательно вернет True.

Она используется при определении поз, когда нужно сравнить идеальное значение направления вектора и его текущее значение. Вот наглядный пример:

Обратимся к рисунку, который уже был показан в самом начале. Нам нужно определить, что вектор, описывающий плечо направлен влево (180 град.), а предплечье - вверх (90 град.). Можно сравнивать и просто с конкретным числом встроенными в язык средствами, но тогда распознавание жестов будет очень плохим, так как в реальности согнуть руку на такие углы очень сложно. Поэтому и используется эта функция - чтобы жест было показать намного легче.

img\_7.png

eq_all(lside=[], rside=[], neck=[])

И последней идет функция eq_all, которая позволяет в удобной форме проверять направления всех описанных ранее векторов:

def eq_all(lside=[], rside=[], neck=[]):
    """
    функция для быстрого сравнения всех прописанных векторов
    """
    ans = True
    if lside:
        # print('Left: ', parts.left_clavicle.angle, parts.left_arm.angle, parts.left_forearm.angle)
        ans = eq(parts.left_clavicle.angle, lside[0]) and \
              eq(parts.left_arm.angle, lside[1]) and \
              eq(parts.left_forearm.angle, lside[2]) and \
              ans
    if rside:
        # print("Right: ", parts.right_clavicle.angle, parts.right_arm.angle, parts.right_forearm.angle)
        ans = eq(parts.right_clavicle.angle, rside[0]) and \
              eq(parts.right_arm.angle, rside[1]) and \
              eq(parts.right_forearm.angle, rside[2]) and \
              ans
    if neck:
        ans = eq(parts.neck.angle) and ans
    # print('\n')
    return ans

Функция не является адаптивной! Это значит, что она рассчитана только на текущую конфигурацию частей тела (две руки и шея). При добавлении новых частей тела данную функцию нужно будет редактировать.

В данную функцию передаются 3 массива, в которых последовательно передаются «идеальные» направления векторов (в град.).

Так, в массивах lside и rside обязательно должны быть 3 значения: 1. направление вектора «лопатка» 2. направление вектора «плечо» 3. направление вектора «предплечье»

В массиве neck должно быть 1 значение: направление вектора «шея»

Перечисленные массивы могут не указываться, тогда определение позы не будет зависеть от не указанных частей тела. Например, если НЕ указывать массивы rside и lside, то определение будет зависеть только от направления шеи, а руки могут располагаться как угодно.

Основной блок

Основная часть программы с комментариями представлена ниже:

# объявление массива для хранения конвертированных точек
converted_points = []

# если используется не интегрированная камера
if not useIntegratedCam:
    # включение двигателей коптера
    pioneer.arm()
    # взлет
    pioneer.takeoff()

while True:
    vx, vy, vz, vr = 0, 0, 0, 0
    # считывание кадра либо с веб-камеры, либо с коптера
    if useIntegratedCam:
        ret, frame = cap.read()
        if not ret:
            continue
    else:
        frame = pioneer_cam.get_cv_frame()

    # отзеркаливание изображения
    frame = cv2.flip(frame, 1)

    # получение размеров изображения
    IMGW = np.shape(frame)[1]
    IMGH = np.shape(frame)[0]

    # определение точек скелета
    detected_skeletons = skDetector.process(frame)

    # проверка, найдены ли точки
    if detected_skeletons and detected_skeletons.pose_landmarks is not None:
        # запись всех точек в переменную с более коротким именем
        points = detected_skeletons.pose_landmarks.landmark

        # конвертация координат точек из относительных в абсолютные
        # (из диапазона от 0 до 1 в диапазон от 0 до ширины/высоты)
        converted_points = convert_points(points)
        # представление частей тела в виде векторов
        parts = generate_parts_vectors(converted_points)

        # отрисовка базовой точки (между лопатками)
        cv2.circle(frame, (converted_points[33].x, converted_points[33].y), 4, (255, 0, 0), 3)

        # проверка, был ли найден жест
        # необходима для создания задержки (промежутка) между детектированием
        if pose_detected == -1:
            # проверка направлений векторов и выявление поз
            # крещенные руки на груди
            if eq_all(lside=[180, 270, 45], rside=[0, 270, 135]):
                print("POSE 1")
                if take_photo_time == -1:
                    print("Picture will be saved in 5 seconds")
                    take_photo_time = time.time()
                pose_detected = time.time()

            # левая рука вбок
            elif eq_all(lside=[180, 180, 180]):
                print("POSE 2")
                vx = 0.7
                pose_detected = time.time()

            # правая рука вбок
            elif eq_all(rside=[0, 0, 0]):
                print("POSE 3")
                vx = -0.7
                pose_detected = time.time()

            # левая рука вбок и согнута в локте вверх
            elif eq_all(lside=[180, 180, 90]):
                print("POSE 4")
                vy = 1
                pose_detected = time.time()

            # правая рука вбок и согнута в локте вверх
            elif eq_all(rside=[0, 0, 90]):
                print("POSE 5")
                vy = -1
                pose_detected = time.time()

            if eq_all(lside=[180, 180, 270], rside=[0, 0, 270]):
                print("POSE 6")
                pose_detected = time.time()
                # если используется НЕ встроенная в компьютер камера
                if not useIntegratedCam:
                    pioneer.land()

        # если с момента обнаружения жеста прошло 10мс - сбросить переменную, блокирующую детектирование
        if pose_detected != -1 and time.time() - pose_detected > 0.1:
            pose_detected = -1

        # если время вызова таймера существует и уже прошло больше 5 секунд, то сохранить фото
        if take_photo_time != -1 and time.time() - take_photo_time > 5:
            cv2.imwrite("image.png", frame)
            take_photo_time = -1
            pose_detected = -1

        # проверяем, что с последней отправки команды задачи скорости прошло больше 10мс
        if async_waiter != -1 and time.time() - async_waiter > 0.1:
            async_waiter = -1

        # если используется НЕ встроенная в компьютер камера
        if not useIntegratedCam and async_waiter == -1:
            # если сконвертированные точки существуют, то работают регуляторы:
            if converted_points:
                # регулятор для удержания человека в центре изображения по рысканью (вращение вокруг своей оси)
                yaw_err = -(IMGW // 2 - converted_points[33].x) * yaw_k
                yaw_u = yaw_kp * yaw_err - yaw_kd * (yaw_err - yaw_errold)
                yaw_errold = yaw_err

                # регулятор определенного расстояния до человека по оси Y (вперед/назад)
                y_err = -(-0.15 - converted_points[33].z)
                y_u = y_kp * y_err - y_kd * (y_err - y_errold)
                y_errold = y_err

                # регулятор для удержания человека в центре изображения по оси Z (вверх/вниз)
                z_err = ((IMGH * 1) // 2 - converted_points[33].y)
                z_u = z_kp * z_err - z_kd * (z_err - z_errold)
                z_errold = z_err

                # обновление переменных, содержащих значения (координаты) для удержания коптером
                vr += -yaw_u
                vx += vx
                vy += 0
                vz += z_u
                pioneer.set_manual_speed_body_fixed(vx, vy, vz, vr)

                async_waiter = time.time()

        # отрисовка всех точек и линий средствами используемой библиотеки
        mpDrawings.draw_landmarks(frame, detected_skeletons.pose_landmarks, skeletonDetectorConfigurator.POSE_CONNECTIONS)

    frame = cv2.resize(frame, (1920, 1080))

    # создание окна с изображением
    cv2.imshow("frame", frame)

    # считывание идентификатора нажатой кнопки с клавиатуры
    key = cv2.waitKey(1)

    # выход из программы при нажатии кнопки q
    if key == ord('q'):
        # если используется НЕ встроенная в компьютер камера
        if not useIntegratedCam:
            pioneer.disarm()
        break

    # посадка при нажатии кнопки l
    if key == ord('l'):
        # если используется НЕ встроенная в компьютер камера
        if not useIntegratedCam:
            pioneer.land()
            cordX, cordY = 0, 0
            cordZ = 1.5

    # взлет при нажатии кнопки j
    if key == ord('j'):
        # если используется НЕ встроенная в компьютер камера
        if not useIntegratedCam:
            pioneer.arm()
            pioneer.takeoff()
            pioneer.go_to_local_point(0, 0, 1.5, 0)

    if key == ord('u'):
        if not useIntegratedCam:
            print('Going up....')
            pioneer.go_to_local_point_body_fixed(x=0, y=0, z=0.5, yaw=0)

# завершение работы захвата изображений с камеры
if useIntegratedCam:
    cap.release()
# закрытие всех окон
cv2.destroyAllWindows()

Запуск программы

При первом запуске:

  1. Подключитесь к интернету

  2. Запустите файл main.py любым способом (в PyCharm, через IDLE, двойным кликом по нему).

  3. Начнется скачивание модели нейросети

  4. Через некоторое время программа остановится с предупреждением, что невозможно подключиться к квадрокоптеру.

  5. Можно переходить к дальнейшим запускам

Дело в том, что библиотека mediapipe изначально не хранит в себе модели всех нейросетей для экономии места при скачивании, поэтому при первом запуске программы автоматически происходит скачивание нужных в работе моделей.

При дальнейших запусках:

  1. Подключитесь к квадрокоптеру по WI-FI, как рассказывается в примерах работы с Пионер Мини.

  2. Запустите файл main.py любым способом (в PyCharm, через IDLE, двойным кликом по нему).

  3. На экране появится окно с изображением с камеры квадрокоптера, а сам он начнет взлетать.

  4. Встаньте перед квадрокоптером, чтобы он выровнялся.

  5. Для завершения работы программы нажимайте кнопку l до начала процесса посадки, а после посадки нажмите q для выключения двигателей.