Управление дронами с помощью приложений для распознавания речи на основе 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* при тестировании системы.

Заключение

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

ссылка на оригинал статьи http://habrahabr.ru/post/273083/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *