Pull to refresh

Побеждаем Kinect в Linux

Reading time 8 min
Views 7.9K
Это вторая статья из цикла о разработке C++ приложений, работающих с Microsoft Kinect. В этой части речь пойдет о том, как заставить устройство работать в Linux и как его можно использовать в своих приложениях.

Первую статью о разработке для Kinect можно почитать здесь. Настоятельно рекомендую к прочтению первую часть ибо без нее впечатления от второй будут неполными.


Введение


Как уже было сказано в первой статье, помимо официального SDK от Microsoft существует еще несколько библиотек от сторонних производителей:

Альтернативные SDK, в отличии от официального, могут быть использованы для разработки под Linux и Mac OS.
Наиболее простым в использовании является libfreenect (OpenNI, собранный и установленный по официальной документации у меня, например, не захотел работать в последней Ubuntu, вполне возможно что из-за того, что драйвер для Kinect требует unstable версию платформы OpenNI, а unstable такой unstable). Все, что сказано в официальной документации, является правдой и да, таки-работает.

Установка libfreenect


Отрадно, что для Ubuntu libfreenect доступен в виде бинарных пакетов. Это позволяет сэкономить кучу времени при установке — не надо ничего собирать и настраивать, достаточно установить зависимости и саму библиотеку. Примеры работают из коробки.
Но индейцы не ищут легких путей и поэтому мы рассмотрим процесс сборки библиотеки из исходников. Чем хорош этот путь? Тем что позволяет получить исходные коды примеров, которые идут в поставке с библиотекой, и разобраться что там к чему, а также, при необходимости поэкспериментировать с исходниками примеров или поотлаживать, если возникают вопросы о том, как это все работает изнутри.
Итак, процесс сборки:
sudo apt-get install git-core cmake libglut3-dev pkg-config build-essential libxmu-dev libxi-dev libusb-1.0-0-dev
git clone github.com/OpenKinect/libfreenect.git
cd libfreenect
mkdir build
cd build
cmake ..
make
sudo make install
sudo ldconfig /usr/local/lib/
sudo glview

Для Ubuntu x64 в предпоследней строке будет /usr/local/lib64/.
После этого можно погонять примерыв папке build/bin — они вполне неплохо иллюстрируют возможности библиотеки.

Установка wxWidgets в Linux


Для wxWidgets в Ubuntu тоже доступны бинарные пакеты (но только для стабильной ветки 2.8.x, которой, впрочем, вполне достаточно для работы). Устанавливаем пакет libwxgtk2.8-dev и вопрос решен.

Как не делать одну работу дважды


Хороший девиз был в свое время у Qt — Write Once, Run Anywhere. Неплохо было бы организовать такое же, но с wxWidgets. Но ситуация такова, что помимо исходников у нас еще есть файлы проектов (после первой части нам достались проекты от Visual Studio, но в Linux от них толку мало) и вот прежде чем писать платформо-зависимую часть кода для новой ОС, было бы неплохо как-то автоматизировать создание и настройку файлов проектов. В этом нам поможет утилита CMake.
Чем хорош CMake? Во-первых Linux'ы все разные. Если какая-то thirdparty библиотека у вас на машине находится по одному пути, то не факт что у всех ваших коллег или пользователей она будет находиться там же. Даже при использовании выдачи pkg-config, в случае изменения каких-то параметров сборки, в проектах, написанных вручную найдется куча мест, где новые параметры надо будет прописывать руками. Автоматизировав всю эту мелкую работу через CMake, который создает файлы проектов, уже настроенные под конкретную рабочую машину, мы также сможем сэкономить кучу времени.
С Windows история та же — зачем прописывать все параметры руками если CMake сама умеет искать довольно большое количество различных библиотек, если они установлены в системе, после чего генерировать проекты с уже настроенными параметрами.
Помимо, собственно, генерирования проектов, в скрипте для CMake можно указать условные параметры для каждой платформы, например включить/исключить некоторые файлы в проект для конкретной ОС, добавить или убрать зависимости от thirdparty-библиотек, специфичных для конкретной ОС и т.д.
В любом случае, тема использования CMake достойна отдельной статьи. В нашем же случае, дабы не перегружать текст листингами CMake-скриптов, приведу ссылки на них в репозитории:

Давайте подитожим, что нам досталось с прошлого раза:
  • Решение разделено на два проекта: статическую библиотеку wxKinectHelper и тестовое приложение KinectTest
  • Файлы проектов (Visual Studio для Windows и Makefile/CodeBlocks для Linux) генерируются с помощью утилиты CMake
  • В проекте wxKinectHelper есть класс wxKinectHelper, который умеет возвращать количество устройств Kinect в системе, выдавать имя устройства по его индексу, а также создавать класс граббера
  • Класс граббера умеет выполнять захват изображения (глубина, цветное) с камеры устройства, а также получать положение игроков
  • При получении нового кадра или инофрмации о положении игроков, вызывается событие, на которое можно подписать, например, класс формы
  • Для отрисовки игроков используется класс wxKinectHelper, который использует отдельный класс имплементора для каждой платформы, тем самым реализуя паттерн bridge.
  • Тестовое приложение состоит из одной формы и умеет отображать список устройств, картинку от сенсора глубины, цветную картинку с камеры, а также положение игроков

Отлично, вспомнили все, теперь можно переходить к покорению libfreenect под Linux.

Кодинг



Инициализация и деинициализация библиотеки


Перед тем, как начать использовать API для доступа к устройствам Kinect, необходимо вызвать метод freenect_init() и в качестве параметра передать ему адрес переменной, которая будет содержать указатель но инициализированный контекст библиотеки, в случае успешного завершения работы функции.
FREENECTAPI int freenect_init(
  freenect_context **ctx,
  freenect_usb_context *usb_ctx);

После завершения работы с устройствами необходимо деинициализировать контекст библиотеки с помощью функции freenect_shutdown():
FREENECTAPI int freenect_shutdown(freenect_context *ctx);

В нашей библиотеке-обертке инициализацией и деинициализацией контекста библиотеки занимается класс wxKinectHelper, для которого, после перехода на Linux, пришлось также реализовать классы-имплементоры отдельно для работы в Windows и Linux.

Получение списка устройств


Для получения количества доступніх устройств в libfreenect используется функция freenect_num_devices():
FREENECTAPI int freenect_num_devices(freenect_context *ctx);

К сожалению, libfreenect не содержит API для получения уникального имени отдельного устройства, поэтому дабы как-то идентифицировать устройства в нашем приложении прийдется придумать обходной путь, например выдавать какое-то строковое значение, содержащее индекс устройства, например так:
wxString KinectHelperImplFreenect::GetDeviceName(size_t index)
{
  wxString name = wxT("Unknown Kinect Sensor");
  if(m_Context)
  {
    name = wxString::Format(wxT("Kinect %u"), index);
  }
  return name;
}


Захват изображения с Kinect


Для захвата изображений с Kinect в библиотеке libfreenect есть два способа — использование синхронного или асинхронного варианта API. В случае асинхронного варианта, при поступлении нового кадра будет вызвана callback-функция, в которой можно реализовать обработку пришедшего буфера. Память для буфера можно выделить самостоятельно или же библиотека сама может позаботиться о создании внутреннего буфера и очистке памяти по завершении работы.

Для запуска захвата изображений необходимо вызвать функцию freenect_open_device(), которая в качестве параметров принимает:
  • Указатель на контекст библиотеки
  • Указатель на переменную, которая будет содержать контекст устройства, в случае успешного завершения работы функции
  • индекс устройства

FREENECTAPI int freenect_open_device(
  freenect_context *ctx,
  freenect_device **dev, int index);

Для завершения захвата изображений нужно вызвать функцию freenect_close_device().
FREENECTAPI int freenect_close_device(freenect_device *dev);

Хотя API и является «асинхронным», но все равно без создания управляющего потока вручную не обойтись. Время от времени нужно вызывать функцию freenect_process_events(), которая будет обрабатывать очередь событий от libusb. В wxWidgets-приложении поток обработки событий можно организовать так:
wxThread::ExitCode KinectGrabberFreenect::Entry()
{
  int status(0);
  while(!GetThread()->TestDestroy() && status >= 0)
  {
    status = freenect_process_events(m_Context);
  }
  return NULL;
}

Получение буфера глубины


Для того, что бы начать получть кадры с буфером глубины, необходимо вызвать несколько функций, которые настраивают режим работы устройства:
  • С помощью freenect_set_depth_callback() можно задать функцию, которая будет вызвана при поступлении нового кадра с буфером глубины.
  • С помощью freenect_set_depth_buffer() можно задать область памяти, в которую libfreenect будет записывать показатели глубины с устройства (это необязательный шаг, но использование этой функции может быть удобно в случае, если требуется какая-то пост-обработка данных)
  • С помощью функции freenect_set_depth_mode() нужно задать режим работы устройства. В параметры этой функции передается структура freenect_frame_mode, которая содержит информацию о желаемом размере картинки и о формате данных.

Значения freenect_resolution могут быть такими:
typedef enum {
  FREENECT_RESOLUTION_LOW  = 0, /**< QVGA - 320x240 */
  FREENECT_RESOLUTION_MEDIUM = 1, /**< VGA - 640x480 */
  FREENECT_RESOLUTION_HIGH  = 2, /**< SXGA - 1280x1024 */
  FREENECT_RESOLUTION_DUMMY = 2147483647, /**< Dummy value to force enum to be 32 bits wide */
} freenect_resolution;

А у freenect_depth_format такими:
typedef enum {
  FREENECT_DEPTH_11BIT    = 0, /**< 11 bit depth information in one uint16_t/pixel */
  FREENECT_DEPTH_10BIT    = 1, /**< 10 bit depth information in one uint16_t/pixel */
  FREENECT_DEPTH_11BIT_PACKED = 2, /**< 11 bit packed depth information */
  FREENECT_DEPTH_10BIT_PACKED = 3, /**< 10 bit packed depth information */
  FREENECT_DEPTH_DUMMY    = 2147483647, /**< Dummy value to force enum to be 32 bits wide */
} freenect_depth_format;

Как видно из описания, в libfreenect, в отличии от официального SDK от Microsoft, для значения глубины используется всего 11 бит, а не 12. Также отсутствует возможность получения индекса игрока.

Получение цветного изображения


В принципе, алгоритм настройки устройства для получения цветного изображения схож с алогоритмом при получении буфера глубины, только используются, соответственно, функции freenect_set_video_callback(), freenect_set_video_buffer() и freenect_set_video_mode().

Еще немного общих сведений о захвате изображений


После всего, сказанного выше, давайте посмотрим на код иниализации захвата изображений:
if(freenect_open_device(m_Context, &m_Device,
   (int)m_DeviceIndex) < 0) break;

freenect_set_depth_callback(m_Device, KinectGrabberFreenect::DepthCallback);
freenect_set_video_callback(m_Device, KinectGrabberFreenect::VideoCallback);
freenect_set_video_mode(m_Device, m_VideoMode);
freenect_set_depth_mode(m_Device, m_DepthMode);
m_VideoBufferLength = m_VideoMode.bytes;
m_DepthBufferLength = m_DepthFrameSize.GetWidth() * m_DepthFrameSize.GetHeight() * 3;
m_VideoBuffer = new unsigned char[m_VideoBufferLength];
m_DepthBufferIndex = 0;
m_GotDepth = false;
m_DepthBuffers[0] = new unsigned char[
  m_DepthFrameSize.GetWidth() *
  m_DepthFrameSize.GetHeight() * 3];
m_DepthBuffers[1] = new unsigned char[
  m_DepthFrameSize.GetWidth() *
  m_DepthFrameSize.GetHeight() * 3];
freenect_set_video_buffer(m_Device, m_VideoBuffer);
DeviceHash[m_Device] = this;
freenect_start_video(m_Device);
freenect_start_depth(m_Device);

Т.к. формат данных для буфера глубины немного другой, то и функция преобразования значения глубины в градацию серого тоже немного изменилась:
unsigned char KinectGrabberFreenect::GetDepthValue(unsigned short data)
{
  // 0x7ff is 0000011111111111 in binary format - max value for 11bit depth;
  return (double)255 * (double)data / (double)0x7ff;
}

В результате всех описанных действий был создан новый класс граббера, который вполне неплохо работает в Linux. К сожалению, в libfreenect нет API для получения положения игроков, для этих целей прийдется использовать сторонние (или свои) наработки, например, базирующиеся на OpenCV. Но и того, что есть, вполне достаточно для распознавания, например, движения.



В завершение


Думаю, на этом с теорией пока закончим. В следующей части будет практика.
Напоминаю еще раз о том, что все исходные коды примера и библиотеки-обертки для работы с Kinect можно скачать здесь.
Сгенерировать файлы проектов для Windows можно с помощью скрипта build/cm.bat, для Linux — build/cmLinux.sh
Также не откажусь от помощи в приручении OpenNI для Linux и Mac OS.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+3
Comments 8
Comments Comments 8

Articles