[Из песочницы] Интерактивный пол на Android
Наверное, многие из вас видели интерактивные игры для детей в торговых центрах. Где динамическая сцена проецируется на пол, а рядом установленный сенсор определяет точки касания с поверхностью и преобразует их в события для приложения на управляющем компьютере. После поиска в интернете информации об этом устройстве оказалось, что это довольно дорогая игрушка. Например, китайские клоны стартуют с ценника в $1200, а что-то более оригинальное стоит уже $10 тыс. После анализа технической составляющей продукта было решено сделать аналогичное устройство самому.Железо проекта состоит из трех частей:
Сенсор глубины (в оригинале это ASUS Xtion); Управляющий компьютер (Cubieboard A80, ODROID-U3); Проектор. В идеале все железки вместе не должны стоить больше 700 долларов. Предполагалось, что соединить все три части должно быть относительно легко, так как в интернете есть такие библиотеки, как OpenNI и libfreenect, которые работают и на Android, и на Linux. Из-за недостатка опыта на раннем этапе казалось, что есть выбор и в железе, и в ОС; есть примеры открытого кода и соединить все вместе не составит большого труда. Через некоторое время после начала проекта оказалось, что это не так. Интеграция всех частей и даже запуск библиотек на целевом устройстве есть самая сложная задача. Пришлось выбирать между доступностью информации по настройке Linux и обилием приложений в маркете под платформу Android.
Однако, обо всем по порядку.
Для того, что бы сразу начать экспериментировать с железом, был куплен б/у сенсор Microsoft Kinect и проектор. Затем из квадратной профилированной трубы изготовлено вот такое крепление для проектора и сенсора:
В верхней части крепления приварен небольшой кусок уголка для монтажа к потолку. В местах изгибов трубы для усиления конструкции приварены пластинки в виде косынок. Проектор соединяется с креплением через треугольную пластину из фанеры. Для соединения сенсора с креплением используется специальный аксессуар для Kinect, который можно без проблем найти на ebay. В качестве управляющего компьютера для удешевления была выбрана плата Cubieboard A10, которую также без проблем можно найти на ebay. На момент написания статьи уже вышли Cubieboard A20 и A80, соответственно двух и восьми-ядерные аналоги. Если позволяет бюджет, то желательно купить A80, чтобы у системы был запас мощности для одновременной работы пользовательских приложений и сервиса по захвату и обработке данных от сенсора глубины. За питание платы и сенсора отвечает USB блок питания с выходным током на 4A. Проектор и сенсор соединяются с креплением так, что бы камера глубины была в одной плоскости с объективом проектора:
Модель проектора лучше выбрать такую, чтобы картинка получалась максимально большой с малого расстояния. На этом описание железной части можно закончить. Теперь о программном обеспечении.
В качестве операционной системы была выбрана сборка Android под Cubieboard с забавной заставкой на рабочем столе. Мне пришлось немного подправить файлы конфигурации и скомпилировать сборку самому из-за того, что в Android нельзя изменить последовательность загрузки модулей, точнее можно, но до следующей перезагрузки системы.
Для внедрения событий потребовался модуль драйвера сенсорного экрана sun4i-ts. На самом деле, тестовое приложение реализует TUIO клиент, но, как оказалось, даже с драйвером сенсорной панели существующий под Android сервер TUIO не поддерживает multitouch события. Возможно, это связано с самим драйвером сенсорной панели sun4i-ts под Allwinner. Исходя из этих фактов был выбран вариант с прямым внедрением событий.
Для захвата данных о глубине используется легковесная и быстрая библиотека libfreenect, которая, в свою очередь, использует libusb для передачи данных по USB. Полученные данные о глубине обрабатываются с помощью OpenCV для Android. Суть обработки довольно простая: карту глубины необходимо преобразовать в замкнутые контуры с длиной не меньше, чем пороговая, для исключения ложных срабатываний — и найти их геометрические центры.
В самом начале работы, когда на сцене нет ни одного объекта, приложение строит карту глубины фона, затем в процессе работы карта используется для отделения целевых объектов от фона. Приложение представляет собой управляющую часть и сервис с кодом на С/C++. Вся логика обработки и захвата данных о глубине реализована на С/C++. Часть кода по работе с TUIO и OpenCV была взята из этого проекта на github.
Рассмотрим код более подробно. В коде, как я уже говорил, используется OpenCV. В самом начале работы приложение строит карту глубины:
1 void STouchDetector: process (const uint16_t& depthData) {
2 frmCount++;
3 // create background model (average depth)
4 if (frmCount < BackgroundTrain) {
5 depth.data = (uchar*)(&depthData);
6 buffer[frmCount] = depth;
7 }
8 else {
9 if (frmCount == BackgroundTrain) {
10 // Calculate average depth based on all frames from buffer
11 average(buffer, background);
12 Scalar bmeanVal = mean(background(roi));
13 double bminVal = 0.0, bmaxVal = 0.0;
14 minMaxLoc(background(roi), &bminVal, &bmaxVal);
15 LOGD("Background extraction completed. Average depth is %f min %f max %f", bmeanVal.val[0], bminVal, bmaxVal);
16 }
В строке 6 данные о глубине сохраняются в буфере. Следует отметить, что буфер имеет тип std::vector. Это массив из матриц и присваивание в строке 6 — это фактически копирование всех пикселей кадра в буфер. После достижения счетчиком кадров порогового значения BackgroundTrain вызывается функция подсчета среднего значения глубины по всем кадрам в строке 11:
1 void STouchDetector::average(vector
1 // Update 16 bit depth matrix
2 depth.data = (uchar*)(&depthData);
3 // Extract foreground by simple subtraction of very basic background model
4 foreground = background — depth;
5
6 // Find touch mask by thresholding (points that are close to background = touch points)
7 touch = (foreground > TouchDepthMin) & (foreground < TouchDepthMax);
8
9 // Extract ROI
10 Mat touchRoi = touch(roi);
11
12 // Find contours by depth data
13 vector< vector Для автоматического запуска драйвера тачскрина после старта устройства, как я говорил в начале, нужно изменить конфигурацию сборки Android. Для сборки Android последнюю версию Ubuntu в моем случае версия была 14.10. Исходный код берем отсюда Cubieboard A10 Android и распаковываем. Нам необходимо изменить два файла:
android/device/softwinner/apollo-cubieboard/init.sun4i.rc
android/frameworks/base/data/etc/platform.xml
В файле init.sun4i.rc необходимо раскоментировать строку insmod /system/vendor/modules/sun4i-ts.ko. В файле platform.xml необходимо добавить группы usb, input и shell в секцию INTERNET:
Видео демонстрации работы: [embedded content]