Управление дронами с помощью приложений для распознавания речи на основе Intel® RealSense™ SDK
В новостях рассказывают о дронах — беспилотных летательных аппаратах — буквально каждый день. Области применения у них самые разные: разведка и боевые операции, фото- и видеосъемка, да и просто развлечения. Технология дронов достаточно новая и заслуживает интереса.
Разработчики могут создавать приложения для управления дронами. Дрон в конечном итоге является обычным программируемым устройством, поэтому к нему можно подключаться и отдавать команды для выполнения нужных действий с помощью обычных приложений для ПК и смартфонов. Для этой статьи я выбрал один из дронов с самыми мощными возможностями программирования — AR.Drone* 2.0 компании Parrot.
Мы узнаем, как взаимодействовать с таким дроном и управлять им с помощью библиотеки, написанной на C#. Опираясь на эту основу, мы добавим речевые команды для управления дроном с помощью Intel® RealSense™ SDK.
PARROT AR.DRONE 2.0
Модель 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* при тестировании системы.
Заключение
В этой статье мы увидели, как взаимодействовать с устройством (таким сложным, как дрон) с помощью интерфейса взаимодействия на естественном языке. Мы увидели, как можно создать простой словарь команд, научить систему понимать его и соответственным образом управлять сложным устройством — дроном в полете. Показанное в этой статье — лишь малая доля доступных возможностей по управлению дроном. Возможности поистине безграничны.
Демонстрация полета на конференции .NET Campus в 2014 году
Об авторе
Марко Даль Пино (Marco Dal Pino) работает в области информационных технологий уже свыше 20 лет, сейчас он фрилансер-консультант по платформе .NET. Марко входит в состав DotNetToscana (это сообщество по технологиям Майкрософт) и обладает званием Microsoft MVP в области разработки платформ Windows. Марко разрабатывает мобильные и встроенные приложения для розничной торговли и корпоративного сектора, а также принимает участие в разработке приложений для Windows 8 и Windows Phone для сторонней компании.
С 2013 года Марко обладает квалификацией Nokia Developer Champion for Windows Phone. В том же году он получил уровень Intel Developer Zone Green Belt корпорации Intel за деятельность по поддержке разработок и популяризации технологии Intel RealSense и управления компьютером без помощи контроллеров. Кроме того, Марко — лауреат сертификатов Intel Software Innovator по технологии Intel RealSense и Интернету вещей.
Он занимается обучением и выступает на крупных технических конференциях.
Марко Минерва (Marco Minerva) работает с платформой .NET с момента появления ее первой версии. Сейчас он главным образом занимается проектированием и разработкой приложений для Магазина Windows и Windows Phone, используя Windows Azure в качестве внутреннего механизма. Марко — один из основателей и руководитель DotNetToscana, находящейся в Тоскане пользовательской группы, работающей с .NET. Марко выступает на технических конференциях и пишет статьи в журналах.