Компания
166,93
рейтинг
15 декабря 2015 в 09:32

Разработка → Управление дронами с помощью приложений для распознавания речи на основе Intel RealSense SDK



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

Разработчики могут создавать приложения для управления дронами. Дрон в конечном итоге является обычным программируемым устройством, поэтому к нему можно подключаться и отдавать команды для выполнения нужных действий с помощью обычных приложений для ПК и смартфонов. Для этой статьи я выбрал один из дронов с самыми мощными возможностями программирования — AR.Drone 2.0 компании Parrot.

Мы узнаем, как взаимодействовать с таким дроном и управлять им с помощью библиотеки, написанной на C#. Опираясь на эту основу, мы добавим речевые команды для управления дроном с помощью Intel RealSense SDK.

Модель AR.Drone 2.0 компании Parrot — один из наиболее интересных дронов, предлагаемых на рынке для энтузиастов. Этот дрон обладает множеством функций и включает встроенную систему помощи с интерфейсами стабилизации и калибровки. Дрон оснащен защитным каркасом из прочного пенополистирола, предохраняющим лопасти винтов и движущиеся части в случае падения или столкновения с неподвижными препятствиями.


AR.Drone* 2.0 компании Parrot

Оборудование дрона обеспечивает его подключение по собственной сети Wi-Fi* к внешним устройствам (смартфонам, планшетам, ПК). Протокол связи основан на АТ-подобных сообщениях (подобные команды несколько лет назад использовались для программирования модемов для связи по телефонной сети).

С помощью этого простого протокола можно отправлять дрону все команды, необходимые для взлета, подъема или спуска, полета в разных направлениях. Также можно считывать поток изображений, снятых камерами (в формате высокой четкости), установленными на дроне (одна камера направлена вперед, другая — вниз), чтобы сохранять отснятые в полете фотографии или записывать видео.
Компания-производитель предоставляет несколько приложений для пилотирования дрона вручную, но намного интереснее узнать, как добиться автономного управления полетом. Для этого я решил (при содействии моего коллеги Марко Минерва) создать интерфейс, который позволил бы управлять дроном с разных устройств.

Программное управление дроном


У дрона есть собственная сеть Wi-Fi, поэтому подключимся к ней для передачи команд управления. Всю нужную информацию мы нашли в руководстве для разработчиков AR.Drone 2.0. Например, в руководстве сказано, что нужно отправлять команды по протоколу UDP на IP-адрес 192.168.1.1, порт 5556. Это простые строки в формате AT:

  • AT * REF — управление взлетом и посадкой;
  • AT * PCMD — движение дрона (направление, скорость, высота).

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

Сначала нужно подключиться к устройству.

public static async Task ConnectAsync(string hostName = HOST_NAME, string port = REMOTE_PORT)
        {
             // Set up the UDP connection.
             var droneIP = new HostName(hostName);

             udpSocket = new DatagramSocket();
             await udpSocket.BindServiceNameAsync(port);
             await udpSocket.ConnectAsync(droneIP, port);
             udpWriter = new DataWriter(udpSocket.OutputStream);

             udpWriter.WriteByte(1);
             await udpWriter.StoreAsync();

             var loop = Task.Run(() => DroneLoop());
        }

Как уже было сказано ранее, нужно использовать протокол UDP, следовательно, нужен объект DatagramSocket. После подключения с помощью метода ConnectAsync мы создаем DataWriter в выходном потоке для отправки команд. И наконец, мы отправляем первый байт по Wi-Fi. Он служит только для инициализации системы и будет отброшен дроном.

Проверим команду, отправленную дрону.

        private static async Task DroneLoop()
        {
            while (true)
            {

                var commandToSend = DroneState.GetNextCommand(sequenceNumber);
                await SendCommandAsync(commandToSend);

                sequenceNumber++;
                await Task.Delay(30);
            }
        }

Тег DroneState.GetNextCommand форматирует строковую АТ-команду, которую нужно отправить устройству. Для этого нужен порядковый номер: дрон ожидает, что каждая команда сопровождается порядковым номером, и игнорирует все команды, номера которых меньше или равны номерам уже полученных команд.

После этого мы используем WriteString для отправки в поток команд через StreamSocket, при этом StoreAsync записывает команды в буфер и отправляет их. И наконец, мы увеличиваем порядковый номер и используем параметр Task Delay, чтобы ввести задержку в 30 миллисекунд перед следующей итерацией.
Класс DroneState определяет, какую команду отправить.

    public static class DroneState
    {
       public static double StrafeX { get; set; }
       public static double StrafeY { get; set; }
       public static double AscendY { get; set; }
       public static double RollX { get; set; }
       public static bool Flying { get; set; }
       public static bool isFlying { get; set; }

        internal static string GetNextCommand(uint sequenceNumber)
        {
            // Determine if the drone needs to take off or land
            if (Flying && !isFlying)
            {
                isFlying = true;
                return DroneMovement.GetDroneTakeoff(sequenceNumber);
            }
            else if (!Flying && isFlying)
            {
                isFlying = false;
                return DroneMovement.GetDroneLand(sequenceNumber);
            }

            // If the drone is flying, sends movement commands to it.
            if (isFlying && (StrafeX != 0 || StrafeY != 0 || AscendY != 0 || RollX != 0))
                return DroneMovement.GetDroneMove(sequenceNumber, StrafeX, StrafeY, AscendY, RollX);

            return DroneMovement.GetHoveringCommand(sequenceNumber);
        }
    }


Свойства StrafeX, StrafeY, AscendY и RollX определяют соответственно скорость движения влево и вправо, вперед и назад, высоту и угол вращения дрона. Эти свойства имеют тип данных Double, допустимые значения — от 1 до -1. Например, если задать для свойства StrafeX значение -0,5, то дрон будет перемещаться влево с половиной максимальной скорости; если задать 1, то дрон полетит вправо с максимальной скоростью.

Переменная Flying определяет взлет и посадку. В методе GetNextCommand мы проверяем значения этих полей, чтобы определить, какую команду отправить дрону. Эти команды, в свою очередь, находятся под управлением класса DroneMovement.
Обратите внимание, что, если команды не заданы, последняя инструкция создают так называемую команду Hovering. Это пустая команда, поддерживающая открытый канал связи между дроном и устройством. Дрон должен постоянно получать сообщения от управляющего им приложения, даже если не нужно выполнять никаких действий и ничего не изменилось.

Самый интересный метод класса DroneMovement — метод GetDroneMove, который фактически и занимается составлением и отправкой команд дрону. Другие методы, связанные с движением, см. в этом примере.

public static string GetDroneMove(uint sequenceNumber, double velocityX, double velocityY, double velocityAscend, double velocityRoll)
    {
        var valueX = FloatConversion(velocityX);
        var valueY = FloatConversion(velocityY);
        var valueAscend = FloatConversion(velocityAscend);
        var valueRoll = FloatConversion(velocityRoll);

        var command = string.Format("{0},{1},{2},{3}", valueX, valueY, valueAscend, valueRoll);
        return CreateATPCMDCommand(sequenceNumber, command);
    }
private static string CreateATPCMDCommand(uint sequenceNumber, string command, int mode = 1)
    {
        return string.Format("AT*PCMD={0},{1},{2}{3}", sequenceNumber, mode, command, Environment.NewLine);
    }

Метод FloatConversion не указан здесь, но он преобразует значение типа Double диапазона от -1 до 1 в целочисленное значение со знаком, которое может быть использовано АТ-командами, например строкой PCMD для управления движением.

Показанный здесь код доступен в виде бесплатной библиотеки на сайте NuGet (AR.Drone 2.0 Interaction Library). Эта библиотека предоставляет все необходимое для управления — от взлета до посадки.


Пользовательский интерфейс AR.Drone UI на сайте NuGet

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

Intel RealSense SDK


Теперь посмотрим на одну из самых интересных и удобных в использовании (для меня) возможностей Intel RealSense SDK — распознавание речи.

В SDK поддерживается два подхода к распознаванию речи.

  • Распознавание команд (по заданному словарю).
  • Распознавание свободного текста (диктовка).

Первый подход представляет собой своего рода список команд, заданный приложением, на указанном языке, который обрабатывается «распознавателем». Все слова, которых нет в списке, игнорируются.

Второй подход — что-то типа диктофона, «понимающего» любой текст в свободной форме. Этот подход идеален для стенографирования, автоматического создания субтитров и т. п.

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

        private PXCMSession Session;
        private PXCMSpeechRecognition SpeechRecognition;
        private PXCMAudioSource AudioSource;
        private PXCMSpeechRecognition.Handler RecognitionHandler;

Session — тег, необходимый для доступа к вводу-выводу и к алгоритмам SDK, поскольку все последующие действия унаследованы от этого экземпляра.
SpeechRecognition — экземпляр модуля распознавания, созданного функцией CreateImpl в среде Session.
AudioSource — интерфейс устройства, позволяющий установить и выбрать входное аудиоустройство (в нашем примере кода мы для простоты выбираем первое доступное аудиоустройство).
RecognitionHandler — фактический обработчик, назначающий обработчик событий для события OnRecognition.

Теперь инициализируем сеанс, AudioSource и экземпляр SpeechRecognition.

            Session = PXCMSession.CreateInstance();
            if (Session != null)
            {
                // session is a PXCMSession instance.
                AudioSource = Session.CreateAudioSource();
                // Scan and Enumerate audio devices
                AudioSource.ScanDevices();

                PXCMAudioSource.DeviceInfo dinfo = null;

                for (int d = AudioSource.QueryDeviceNum() - 1; d >= 0; d--)
                {
                    AudioSource.QueryDeviceInfo(d, out dinfo);
                }
                AudioSource.SetDevice(dinfo);

                Session.CreateImpl<PXCMSpeechRecognition>(out SpeechRecognition);

Как было отмечено ранее, для простоты кода мы выбираем первое доступное аудиоустройство.

PXCMSpeechRecognition.ProfileInfo pinfo;
              SpeechRecognition.QueryProfile(0, out pinfo);
              SpeechRecognition.SetProfile(pinfo);

Затем нужно опросить систему, узнать фактический параметр конфигурации и назначить его переменной (pinfo).

Также нужно настроить ряд параметров в профиле, чтобы изменить язык распознавания. Задайте уровень достоверности распознавания (при более высоком значении требуется более уверенное распознавание), интервал окончания распознавания и т. д.

В нашем случае параметр по умолчанию устанавливается как в профиле 0 (полученном из Queryprofile).

                String[] cmds = new String[] { "Takeoff", "Land", "Rotate Left", "Rotate Right", "Advance",
                    "Back", "Up", "Down", "Left", "Right", "Stop" , "Dance"};
                int[] labels = new int[] { 1, 2, 4, 5, 8, 16, 32, 64, 128, 256, 512, 1024 };
                // Build the grammar.
                SpeechRecognition.BuildGrammarFromStringList(1, cmds, labels);
                // Set the active grammar.
                SpeechRecognition.SetGrammar(1);

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

Можно задать несколько грамматик для использования в приложении и включать одну из них при необходимости, поэтому можно создать разные словари команд для всех поддерживаемых языков и предоставить пользователю возможность переключаться между языками, распознаваемыми в SDK. В этом случае нужно установить соответствующие DLL-файлы поддержки языка, поскольку при установке SDK по умолчанию устанавливается поддержка только для языка «Английский (США)». В этом примере мы используем только грамматику, установленную по умолчанию вместе с языком «Английский (США)».

Затем выбираем, какую грамматику следует назначить активной в экземпляре SpeechRecognition.

                RecognitionHandler = new PXCMSpeechRecognition.Handler();

                RecognitionHandler.onRecognition = OnRecognition;

Эти инструкции определяют новый обработчик событий для события OnRecognition и назначают его методу, описанному ниже.

        public void OnRecognition(PXCMSpeechRecognition.RecognitionData data)
        {
            var RecognizedValue = data.scores[0].label;
            double movement = 0.3;
            TimeSpan duration = new TimeSpan(0, 0, 0, 500);
            switch (RecognizedValue)
            {
                case 1:
                    DroneState.TakeOff();
                    WriteInList("Takeoff");
                    break;
                case 2:
                    DroneState.Land();
                    WriteInList("Land");
                    break;
                case 4:
                    DroneState.RotateLeftForAsync(movement, duration);
                    WriteInList("Rotate Left");
                    break;
                case 5:
                    DroneState.RotateRightForAsync(movement, duration);
                    WriteInList("Rotate Right");
                    break;
                case 8:
                    DroneState.GoForward(movement);
                    Thread.Sleep(500);
                    DroneState.Stop();
                    WriteInList("Advance");
                    break;
                case 16:
                    DroneState.GoBackward(movement);
                    Thread.Sleep(500);
                    DroneState.Stop();
                    WriteInList("Back");
                    break;
                case 32:
                    DroneState.GoUp(movement);
                    Thread.Sleep(500);
                    DroneState.Stop();
                    WriteInList("Up");
                    break;
                case 64:
                    DroneState.GoDown(movement);
                    Thread.Sleep(500);
                    DroneState.Stop();
                    WriteInList("Down");
                    break;
                case 128:
                    DroneState.StrafeX = .5;
                    Thread.Sleep(500);
                    DroneState.StrafeX = 0;
                    WriteInList("Left");
                    break;
                case 256:
                    DroneState.StrafeX = -.5;
                    Thread.Sleep(500);
                    DroneState.StrafeX = 0;
                    WriteInList("Right");
                    break;
                case 512:
                    DroneState.Stop();
                    WriteInList("Stop");
                    break;
                case 1024:
                    WriteInList("Dance");
                    DroneState.RotateLeft(movement);
                    Thread.Sleep(500);
                    DroneState.RotateRight(movement);
                    Thread.Sleep(500);
                    DroneState.RotateRight(movement);
                    Thread.Sleep(500);
                    DroneState.RotateLeft(movement);
                    Thread.Sleep(500);
                    DroneState.GoForward(movement);
                    Thread.Sleep(500);
                    DroneState.GoBackward(movement);
                    Thread.Sleep(500);
                    DroneState.Stop();
                    break;
                default:
                    break;

            }
            Debug.WriteLine(data.grammar.ToString());
            Debug.WriteLine(data.scores[0].label.ToString());
            Debug.WriteLine(data.scores[0].sentence);
            // Process Recognition Data
        }

Это метод получения значения, возвращенного из данных распознавания, и выполнения соответствующей команды (в нашем случае — соответствующей команды управления полетом дрона).

Каждая команда дрона относится к вызову DroneState с определенным методом (TakeOff, GoUp, DoDown и т. д.) и с определенным параметром движения или длительности, который в каждом случае касается определенного количества или длительности движения.

Некоторым командам требуется явный вызов метода Stop для остановки текущего действия, иначе дрон продолжит двигаться согласно полученной команде (команды см. в предыдущем фрагменте кода).

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

Для проверки распознавания, даже если нет доступного дрона, я вставил переменную (она управляется флажком в главном окне), которая включает функциональный режим Drone Stub (в этом режиме команды создаются, но не отправляются).

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

В коде содержатся некоторые команды отладки, выводящие полезную информацию в окнах отладки Visual Studio* при тестировании системы.

Заключение


В этой статье мы увидели, как взаимодействовать с устройством (таким сложным, как дрон) с помощью интерфейса взаимодействия на естественном языке. Мы увидели, как можно создать простой словарь команд, научить систему понимать его и соответственным образом управлять сложным устройством — дроном в полете. Показанное в этой статье — лишь малая доля доступных возможностей по управлению дроном. Возможности поистине безграничны.

Оригинал статьи
Автор: @saul
Intel
рейтинг 166,93

Комментарии (3)

  • 0
    Самое интересное не показали, failsafe поведение.
    Представил, как оператор орёт «Stop! Halt! F***!», а дрон летит в стену. Или мимокрокодил говорит «shut up and take my money!», увидев такое прекрасное творение, оно уходит в потолок, и будущий покупатель уже передумал.

    не мог не вспомнить

    • 0
      А я не мог не вспомнить
  • +2
    «Модель AR.Drone 2.0 компании Parrot — один из наиболее интересных дронов, предлагаемых на рынке для энтузиастов.» Интересно, как эта свистулька по тормозному wifi с радиусом действия 30 метров стала наиболее интересной для энтузиастов?
    Ну и да, голосовое управление для квадрика это неминуемы креш. В аналоговом управлении помимо команды «верх» или «влево» есть еще сила с которой эту команду следует совершать. Как вы голосом будете передавать насколько градусов квадру нужно развернуться, насколько быстро опуститься или ускориться?

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

Самое читаемое Разработка