С момента появления сенсора Kinect for Windows я питал тихую страсть к программированию такого рода устройств – ведь это волшебное чувство, когда написанная тобой программа отзывается на движения человека на расстоянии! Сегодня я расскажу вам про программирование ещё одного похожего устройства – Leap Motion.

6tag_060314-114131

По сути дела, Leap Motion – это небольшой Kinect, который может распознавать положение кистей рук. По замыслу создателей (хорошо отраженных в этом видео), его можно использовать для управления компьютером во множестве различных сценариев. В этом посте мы рассмотрим, как программируется Leap Motion (на платформе .NET), и как можно не его основе создать простой музыкальный инструмент – терменвокс. Вот такой:

Простейший терменвокс на базе Leap Motion

Leap Motion для программиста

С точки зрения программиста, Leap Motion предоставляет набор удобных API (документированных тут), которые позволяют:

  • С высокой частотой (более, чем 60-100 кадров в секунду) получать модель пространства, включающую в себя координаты и скорости пальцев рук, нормали ладоней, положения и скорости различных “инструментов” (Leap Motion хорошо распознает карандаши и авторучки)
  • Распознавать основные жесты: окружность, swipe, нажатие
  • Эмулировать touch surface, т.е. касания некоторой виртуальной поверхности перед экраном
  • Распознавать основные движения рук и пальцев (motions), транслируя их в преобразования поворота, перемещения или масштабирования.

В отличие от Kinect, Leap Motion не предоставляет доступа к 3D-данным, полученным с камеры

Программировать Leap Motion можно на различных языках и платформах, в том числе .NET. В этой статье мы рассмотрим использование первой возможности – доступа к модели руки.

Отслеживаем простейшие движения

Для наших экспериментов создадим пустой WPF-проект, и добавим на основной экран цветную окружность. Нашим первым шагом будет научиться перемещать эту окружность по экрану движениями пальца. Итак, разметка основной странички (XAML) будет выглядеть так:

<Window x:Class="LeapVoxSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="768" Width="1024" Background="Black">
    <Canvas>
        <Ellipse x:Name="ptr" Fill="Yellow" Height="30" Width="30"/>
    </Canvas>
</Window>

Для использования Leap Motion необходимо скачать Leap Motion SDK, и подключить к проекту ссылку на LeapCSharp.NET4.0.dll. Однако это еще не всё – Leap SDK требует также библиотек Leap.dll и LeapCSharp.dll, которые должны находится в одной директории с выполняемым файлом проекта. Поэтому найдите эти файлы в директории LeapSDK\lib\x86 (в той директории, где установлен Leap SDK), перетащите их в проект Visual Studio и установите свойства “действия при компиляции = контент, копировать в выходную директорию = копировать, если новее”.

Все общение с контроллером Leap ведется через объект Controller. Создадим такой объект в нашем основном файле MainWindows.xaml.cs:

Controller Leap = new Controller();

Теперь Leap Motion готов предоставлять нам кадры с данными о положении рук. Получать кадры можно двумя способами:

  • Передать в объект Controller экземпляр класса Listener, в котором будет перегружен метод OnFrame. Этот метод будет автоматически вызываться Leap Motion SDK с максимально возможной частотой кадров.
  • Создать свой метод, который будет периодически опрашивать класс Controller и получать с него текущий кадр. Так следует поступать, есть в программе уже есть естественный цикл, или если мы хотим выполнять код в UI-потоке выполнения.

В нашем случае для создания цикла опроса конроллера используем DispatcherTimer, который будет срабатывать 30 раз в секунду:

DispatcherTimer dt = new DispatcherTimer() 
    { 
        Interval = TimeSpan.FromSeconds(1 / 30)
    };

public MainWindow()
{
    InitializeComponent();
    dt.Tick += dt_Tick;
    dt.Start();
}

В методе dt_Tick будет сосредоточена основная логика отслеживания движений. Мы получаем текущий кадр (Frame), и если в этом кадре видны пальцы – берем первый попавшийся их них и используем его координаты, чтобы изменить положение окружности:

void dt_Tick(object sender, EventArgs e)
{
    var fr = Leap.Frame();
    if (fr!=null && fr.Fingers.Count>0)
    {
        var f = fr.Fingers[0];
        Canvas.SetLeft(ptr, 512 + f.TipPosition.x);
        Canvas.SetTop(ptr, 768-f.TipPosition.y);
    }
}

Мы добились того, что можем перемещать кружок по экрану, передвигая палец над контроллером Leap Motion. Пример кода на данном этапе можно посмотреть на GitHub.

Программная генерация звука различной высоты

Для программной генерации звука различной высоты мы используем библиотеку NAudio. Простейший способ добавить поддержку NAudio в проект – использовать Nuget, набрав в консоли диспетчера пакетов:

install-package NAudio

Процесс генерации звука переменной высоты подробно описан в статье Чарльза Петцольда на MSDN. Если коротко – мы создаем класс, который генерирует синусоидальную волну звука, и передаем её на вход методу NAudio, который эту волну воспроизводит. При этом есть ряд тонкостей: чтобы звук менялся более плавно, необходимо плавно менять частоту звука, и при этом учитывать задержку буферизации аудио. Все эти тонкости учтены в классе PortamentoSineWaveOscillator.cs, который необходимо поместить в проект.

Для генерации звука мы создаем объект типа WaveOut и инициализируем его, передавая экземпляр созданного нами осциллятора:

WaveOut WaveGen = new WaveOut();
PortamentoSineWaveOscillator Osc = new PortamentoSineWaveOscillator(44100,120);

Инициалиацию мы проводим в конструкторе страницы:

WaveGen.Init(Osc);
WaveGen.Play();

Теперь нам достаточно изменять свойство Osc.Pitch, чтобы соответствующим образом менялась высота звука, и Osc.Amplitude для изменения громкости. Пускай горизонтальные движения пальцев отвечают за громкость, а вертикальные – за высоту звука. Коэффициенты подбираются опытным путем для достижения кофортного звучания:

void dt_Tick(object sender, EventArgs e)
{
    var fr = Leap.Frame();
    if (fr!=null && fr.Fingers.Count>0)
    {
        var f = fr.Fingers[0];
        ...
        // Меняем высоту звука
        var p = Math.Abs(f.TipPosition.y / 2);
        var a = 255 - Math.Abs(f.TipPosition.x);
        if (p >= 0 && p <= 150) Osc.Pitch = p;
        if (a >= 0 && a <= 255) Osc.Amplitude = (short)a;
    }
}

Версию проекта на этой стадии можно получить тут.

Последние штрихи

Чтобы сделать проект чуть более красивым, вместо обычного круга, перемещаемого по экрану, можно добавить спец.эффекты на основе системы частиц (Particle Systems). Я взял за основу вот эту статью на CodeProject, где приводится исходный код WPF-контрола, представляющего собой источник частиц. Заменив им окружность, добавленную на первом шаге, мы получим финальный проект, исходный код которого доступен на GitHub.

Домашнее задание

Если вы захотите продолжить эксперименты, то простор для творчества практически неограничен! В частности, можно:

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

Если вас будет интерес к этой теме (пишите в комментариях), то я продолжу серию статей, добавляя различные дополнительные интересности, связанные с Leap Motion, Kinect и неожиданным использованием мобильных устройств. О чем вам бы хотелось услышать? Например, в этом видео показано, что ещё можно сделать с помощью Leap Motion за пару часов: