company_banner

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

    Заключение


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

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

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

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

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

        Самое читаемое