Закрыть
Первый скетч для Kinect

Первый скетч для Kinect

Я уже установил требуемые драйвера и фреймворки, а также библиотеки Processing для работы с Kinect. Теперь все готово для написания первой простенькой программки для Kinect в стиле «Привет, мир!» Эта программа получит доступ к сенсору Kinect, захватит изображения с цветной камеры и камеры глубины и отобразит их на экране рядом друг с другом.

Итак, наш первый скетч на Processing для Kinect выглядит следующим образом:

import SimpleOpenNI.*; // импортируем библиотеку SimpleOpenNI
SimpleOpenNI kinect; // декларируем объект kinect класса SimpleOpenNI

void setup()
{
 size(640*2, 480); // размер окна приложения
 kinect = new SimpleOpenNI(this); // создаем экземпляр объекта SimpleOpenNI

 kinect.enableDepth(); // получаем доступ к изображению глубины
 kinect.enableRGB(); // получаем доступ к RGB-изображению
}

void draw()
{

 kinect.update(); // получаем новые данные
 image(kinect.depthImage(), 0, 0); // выводим изображение глубины
 image(kinect.rgbImage(), 640, 0); // выводим RGB-изображение
}

Запустив на выполнение этот скетч, увидим примерно следующую картинку:

Изображения RGB и глубины с Kinect

Точки изображения, которые расположены ближе к сенсору на изображении глубины (слева) отображаются как более светлые, соответственно, дальние точки сцены — более темные.

 

Рабочий диапазон

Камера глубины сцены имеет некоторые ограничения. Минимальное расстояние, на котором фиксируется глубина составляет около 40 см. На более близком расстоянии, Kinect не может вычислить расстояние на основе смещения точек на инфракрасном изображении. Такие точки изображения, сенсор просто выкидывает, делая их черными. Это хорошо видно, если чуть ближе придвинуть ладонь — на изображении глубины она становится черной:

Минимальное и максимальное расстояние при работе с Kinect

Верхняя граница диапазона чувствительности камеры лежит на расстоянии около 3 метров (на изображении это черная область в углу между стенами и потолком). Таким образом, все точки на изображении глубины в диапазоне 40-300 см являются черными.

 

Шум на гранях

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

 

Отражение вызывает искажения

Проведем эксперимент. Разместим на стене зеркало.

Влияние отражений на карту глубины Kinect

Мы видим, что достаточно плоское зеркало выглядит на изображение темным на фоне светлой стены — гораздо темнее, чем должен быть объект такой толщины.

Причиной этому является отраженное зеркалом инфракрасное излучение проектора сенсора. Здесь есть один нюанс.

Так как инфракрасные точки, излучаемые проектором Kinect были отражены зеркалом на другие объекты, находящиеся в помещении, то вычисленная сенсором глубина зеркала будет расстоянием между Kinect и зеркалом плюс расстоянием между зеркалом и частью комнаты, отраженной в нем.

Этот эффект может вызвать дополнительные сложности, когда отражающие объекты будут случайно попадать в область видимости Kinect, например, когда при помощи сенсора, установленного на мобильном роботе строится карта помещения и в поле зрения попадает стеклянная дверь. Однако, если учесть этот эффект отражения, использованием угла между сенсором и любой частично отражающей поверхностью, то эту проблему можно частично избежать.

Эффект отражения можно использовать и в своих интересах. Например, окружив сканируемый объект системой зеркал. Таким образом, можно получить круговой скан объекта, не перемещая сенсор. В качестве примера приведу фото, сделанное художником Кайлом Макдональдом:

3D-сканирование при помощи Kinect и системы зеркал

 

Перекрытие и затенение

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

Как я уже рассказывал в статье Структурированный свет в Kinect, ИК-проектор сенсора формирует некоторый шаблон из точек. Каждая точка движется, пока не достигнет объекта, а затем, отразившись, возвращается и фиксируется инфракрасной камерой сенсора, а затем используется для расчета глубины.

Если же на пути ИК-луча находится какое-либо препятствие, то все, что позади этого препятствия, становится для сенсора невидимым. Такие объекты отображаются на изображении глубины в виде черных областей. Эта проблема называется перекрытием. С Kinect вы не можете видеть сквозь или вокруг объектов. Всегда будут части сцены, которые будут скрыты из поля зрения и у нас не будет данных об их глубине. Какие именно части сцены будут перекрыты определяется положением и углом сенсора Kinect по отношению к различным объектам.

 

Перекос между цветным изображением и глубиной

Если внимательно присмотреться к цветоному изображению и изображению глубины, то можно заметить, что они получены с различных точек зрения. Также, можно заметить, что на RGB-изображении показана несколько большая область.

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

 

Разбор кода

Немного разобравшись с тем, что же мы получаем в результате работы программы, давайте разберем сам программный код.

Объект kinect, который мы задекларировали в самом начале программы, используется для того, чтобы получить доступ ко всем данным Kinect. Мы будем использовать методы этого объекта, чтобы получить цветное изображение и изображение глубины, а в дальнейшем и данные о скелете. Мы просто декларируем его, пока не создавая экземпляра.

В функции setup сначала мы устанавливаем размер окна, создаваемого нами приложения. Изображения, получаемые с сенсора Kinect имеют разрешение 640×480 точек. Мы хотим, чтобы наши изображения размещались рядом друг с другом, поэтому ширину умножаем на два. Далее мы создаем, объявленный ранее экземпляр объекта SimpleOpenNI.

После этого, мы для нашего экземпляра kinect применяем методы enableDepth и enableRGB, которые позволяют получить нам доступ к RGB-изображению и изображению глубины сенсора.

Функция draw начинается с вызова метода update для нашего объекта kinect для получения новых данных с сенсора.

Далее, при помощи функции image и, используя метод depthImage, мы получаем изображение глубины и размещаем его относительно точки с координатами (0,0) в левом верхнем углу окна нашего приложения. Аналогично выводим RGB-изображение, сместив его на 640 пикселей по горизонтали.

Камера Kinect захватывает данные со скоростью 30 кадров в секунду. Другими словами, каждую 1/30 секунды, Kinect делает доступными для чтения новое изображение глубины и RGB-изображение. Если наше приложение работает быстрее, чем 30 кадров в секунду, функция draw будет вызвана несколько раз, прежде чем новый набор изображений глубины и RGB-данных можно будет получить с Kinect. Если наше приложение работает медленнее, чем 30 кадров в секунду, мы пропустим некоторые изображения. Но как быстро наше приложение реально работает? Какова наша частота кадров? На самом деле, мы не знаем ответа на этот вопрос. По умолчанию, Processing пытается запустить функцию draw 60 раз в секунду. Вы можете изменить это значение с помощью вызова функции Processing frameRate. Практически, фактическая частота кадров вашего скетча будет зависеть от того, что ваш код на самом деле делает. С увеличением вычислительных затрат нашего приложения, установленная частота кадров может стать чрезмерной для плавной отрисовки изображений и ее придется снижать.

Для понимания деталей работы функции depthImage перепишем наш скетч несколько иначе:

import SimpleOpenNI.*;
SimpleOpenNI kinect;

void setup()
{
 size(640*2, 480);
 kinect = new SimpleOpenNI(this);
 kinect.enableDepth();
 kinect.enableRGB();
}
void draw()
{
 kinect.update();
 PImage depthImage = kinect.depthImage();
 PImage rgbImage = kinect.rgbImage();
 image(depthImage, 0, 0);
 image(rgbImage, 640, 0);
}

В этом примере введены две новые строки кода в функции draw. Вместо того, чтобы неявно возвращать значения из kinect.depthImage и kinect.rgbImage в функцию image, мы сохраняем их теперь в локальных переменных, а затем уже передаем эти значения. Класс Processing PImage используется для хранения данных изображений. Этот класс обеспечивает множество полезных функций для работы с изображениями, такие как доступ отдельным пикселям и их измение. Имея данные Kinect в виде PImage также дает нам большое преимущество. Это означает, что мы можем автоматически использовать данные Kinect с другими библиотеками, которые ничего не знают вообще о Kinect, но знают, как обрабатывать изображения.

[add_ratings]

2 thoughts on “Первый скетч для Kinect

Оставить ответ

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