Как мы портировали OpenCV на WindowsRT

в 7:32, , рубрики: Без рубрики
image

OpenCV вместе с расширением функциональности продолжает мигрировать на разные платформы. А новые платформы – это другие компиляторы, особенности системного API, тестирование и много новых приключений. В этой статье мы хотели бы поделиться опытом портирования большого и достаточно хорошо устоявшегося проекта на новую и аппаратную, и программную платформу. Под Windows RT будем понимать и новый API для разработки пользовательских приложений Windows Runtime, и операционную систему Windows RT для основанных на архитектуре ARM процессоров. Надо сказать, что основной целью был второй пункт, именно он нужен был заказчику.

Первый подход

Основанная на CMake инфраструктура библиотеки уже многократно переживала переезд на разные платформы, но в этот раз блицкрига не получилось. Как оказалось, cmake (тогда ещё 2.8.11) не поддерживает генерацию проектов для Visual Studio под Windows RT. Обходной манёвр довольно быстро нашёлся на просторах сети: вместо генерации проектов для Visual Studio под Windows можно использовать один из генераторов make-файлов. Для построения потом можно будет использовать make, nmake или Ninja, нужно лишь настроить окружение для сборки с ARM-компилятором.

В борьбе за компиляцию существующего кода всплыло множество мелких нюансов. В частности, построение обычного Win32-приложения для архитектуры ARM ограничено на уровне заголовочных файлов и проектов Visual Studio. Поэтому для успешной компиляции проверочных файлов cmake и наших тестов пришлось сделать хак – добавить в командную строку компилятора макрос _ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE. Он подавляет проверку платформы. То же справедливо и для всех тестов и примеров, развивающихся совместно с библиотекой.

Чтобы решить все проблемы с макросами и ключами компиляции, в библиотеку был добавлен псевдо-toolchain файл. “Псевдо” из-за того, что он не задаёт пути к компилятору, ОС, платформу и прочее, а только добавляет необходимые флаги и макросы. Кроме того, пришлось поставить Windows Platform SDK. Он приносит ещё некоторое количество статических библиотек из C и C++ рантайма для ARM.

В попытках довести билд модуля highgui до конца пришлось пожертвовать некоторыми частями для работы с видео и окнами – в системных библиотеках отсутствуют функции, необходимые для компоновки. На Windows RT нет поддержки старого API для окон, GDI, Video For Windows и некоторых частей COM. Пришлось также отказаться от поддержки FFmpeg'a. Это хорошая и переносимая библиотека, но для её интеграции нужны дополнительные усилия. В итоге мы остались без окон и без ввода-вывода видео. Окна – не большая потеря, их поддержка нужна скорее только для прототипирования и отладки, нежели для создания конечного приложения. А ввод-вывод видео, напротив, вещь очень нужная, и над ней стоило поработать.

Ещё пара подтянутых винтиков – и тесты удачно собрались и почти удачно побежали на ARM-планшете. Но успешная компиляция и выполнение тестов основной библиотеки – это даже не половина дела. Кроме самой OpenCV, на новую платформу хотелось перенести ещё и достаточно большое количество оптимизаций, сделанных на ассемблере и интринсиках для Андроида и рассчитанных на компиляцию GCC, добавить поддержку Intel TBB, всё-таки поддержать ввод-вывод видео и много чего ещё.

Ввод-вывод видео

Активная разработка для Windows RT началась с ввода-вывода видео. Начиная с Windows XP, в операционных системах Windows появился очередной API для работы с мультимедиа – Microsoft Media Foundation. Как быстро выяснилось, это единственный API для видео на планшетах с ARM. Новая реализация для cv::VideoCapture сулила большую пользу и для настольных, и для планшетных ПК. В OpenCV на этот момент уже была поддержка некоторых бэкендов для работы с видео в Windows, в том числе через объявленный устаревшим Direct Show. Реализация cv::VideoCapture на базе DirectShow выполнена с помощью библиотеки VideoInput. Была надежда, что проект VideoInput жив и, может быть, в каком-то виде поддерживает Media Foundation. Но вскрытие показало, что проект достаточно давно заброшен, и даже наша копия библиотечки сильно ушла вперёд по сравнению с оригиналом. Большим прорывом в работе стала находка на CodeProject. Наш соотечественник с Дальнего Востока Евгений Перегуда реализовал свою VideoInput-подобную библиотеку для интеграции Media Foundation в свой проект с OpenCV (!). Огромное ему спасибо за проделанную работу.

Мы решили использовать опубликованный код в качестве базы для новой реализации cv::VIdeoCapture. В процессе адаптации пришлось избавиться от ассемблерных вставок и заменить часть вызовов, которые не удалось слинковать для ARM. Кроме того, немного модифицировав построение конвейера, получилось добавить чтение и запись видеофайлов. На настольной системе новая cv::VideoCapture отлично заработала с камерой, но на планшете нас ждал неприятный сюрприз. В приложении для ARM, собранном для подсистемы Win32, инициализация конвейера MediaFoundation проходила нормально, но видео получить не удавалось: конвейер подвисал и ни одного кадра с камеры не приходило. В приложении, собранном для работы в контейнере, наблюдалась другая аномалия. Перечисление камер функцией MFEnumDeviceSources выдавало кучу мусора и NULL вместо указателя на интерфейсы камеры. Как в скором времени выяснилось, в первом случае виноват системный брокер, который следит за правами доступа к камере и другим устройствам. У нас Win32-приложение, у которого нет манифеста, поэтому разрешить доступ к камере мы не можем. Во втором случае проблема кроется в реализации функции MFEnumDeviceSources, а точнее в её отсутствии. Она числится в списке запрещённых для контейнерных приложений функций и, в отличие от многих других попавших под запрет, не реализована совсем. Точнее, для исключения ошибок линковки вызывается заглушка, которая возвращает NULL вместо указателя на интерфейс и мусор в описании. В итоге, удалось поддержать Media Foundation для настольных систем, а на ARM вопрос остался открытым.

Оптимизация

Следующим шагом стала сборка и интеграция библиотеки TBB. На тот момент библиотека уже активно использовалась как инструмент распараллеливания алгоритмов, который также хотелось перенести на Windows RT для ARM. Но сделать адаптацию быстро и малой кровью не получилось. Помимо стандартных системных вызовов, TBB использует свой для каждой платформы набор низкоуровневых примитивов синхронизации. Во всех предыдущих случаях, когда нам нужно было использовать TBB, применялся компилятор GCC, и в коде библиотеки отрабатывала ветка со специфичными для GCC интринсиками. Но в этот раз нужна была новая ветка. В портировании TBB для Windows RT на ARM нам помогла команда разработчиков библиотеки. Отдельное спасибо Владимиру Полину (vpolin). Стабильная поддержка Windows RT появилась в TBB 4.1 update 4.

После приведения в порядок кода самой библиотеки руки дошли и до множества собственных наработок и оптимизаций. Большинство из них были написаны для Андроида и компилятора GCC и под Windows никогда не собирались.

Начнём с оптимизаций, сделанных на интринсиках и встроенном ассемблере. С интринсиками дело оказалось очень даже хорошо. Все интринсики для NEON-инструкций, как и имена типов данных, совпадают с таковыми в GCC и практически не требуют модификаций кода. Единственное исключение – регистровые переменные. При работе со студийным компилятором нельзя явно указать имя регистра. Привязку переменных к конкретным регистрам приходилось делать для старых версий андроидного NDK, компилятор в котором иногда чудачил и использовал одни и те же регистры два раза – для плюсового кода и интринсиков, портя локальные переменные.

А вот с ассемблером всё оказалось очень плохо. Новые версии компилятора от Майкрософта не поддерживают инлайн-ассемблер для x86_64 и ARM, поэтому от идеи по-быстрому, поправив разницу в синтаксисе, перенести ассемблерный код, пришлось отказаться. Самые значимые части мы переписали с использованием компиляторных интринсиков, но и тут не обошлось без неожиданностей. У студийного компилятора нет интринсиков для VFPv3 (аппаратная реализация математики с плавающей точкой для ARM) инструкций и единственный нормальный способ использовать их явно – реализовывать функции полностью на ассемблере и выносить такие функции в отдельный asm-файл. Подход с отдельной компиляцией ассемблера нас совсем не устраивал, потому как требовались значительные модификации в билдовой инфраструктуре. Важно ещё то, что компилятор отказывается встраивать ассемблерные функции и проводить какие-либо оптимизации их вызова. Решить проблему удалось только применением ключевого слова __emit. Оно позволяет вставить в любом блоке C или C++ кода произвольный кусок машинного кода. Зная соглашения о вызовах и опкоды инструкций, можно составить любой недостающий интринсик самостоятельно.

Чистка Win32 API

Последним шагом в доведении OpenCV до готовности стало прохождение сертификационного теста для контейнерных приложений. Это необходимое условие для всех приложений в Marketplace.
В первую очередь пришлось во флаги линкера добавить /appcontainer. Далее пошла зачистка используемого системного API. Под опалу попали все ANSI-версии Win32-вызовов, а также некоторые из функций, имеющие Ex версию, как, например, InitializeCriticalSection.

Отдельное внимание пришлось уделить непримечательной на первый взгляд Win32 API функции GetTempFileName. В OpenCV она используется в вызове tempfile. GetTempFileName не имеет подходящего аналога, который можно было бы использовать в контейнере. Единственный найденный вариант – вызовы из библиотеки Windows Runtime. Код для функции взят здесь, а здесь – как слинковаться с Windows Runtime при построении обычного приложения.

Кроме GetTempFileName не нашлось замены для функций взаимодействия с TLS (Thread Local Storage). Но в данном случае альтернатива нашлась довольно быстро и там, где совсем не ждали – С++11. Новый стандарт C++ включает средства для работы с TLS-данными, что позволило перейти от системнозависимого API к конструкциям языка.

Замена Win32 API вызовов для работы с TLS на новые конструкции языка стала последним шагом для прохождения сертификционного тестирования. После добавления в тестовое приложение своей заставки, иконок на все случаи жизни и описания тест зазеленел.

Непрерывная интеграция

Параллельно с портированием кода шла работа по развитию тестовой инфраструктуры проекта. К сожалению, не нашлось приемлемых способов запускать тесты удалённо на планшете с ARM, поэтому всё автоматическое тестирование делалось на десктопе, а планшет тестировался вручную.

Первые грабли появились на горизонте при интеграции теста appcert.exe. Это стандартный тест на сертификацию приложений для Marketplace, выполненный в виде исполняемого файла и нескольких библиотек. Тест имеет интерфейс командной строки и хорошо запускается из консоли вручную. Но стоило добавить его в список шагов Buildbot'a, как начались интересные сюрпризы. Во-первых, тест порождает дополнительные консоли и пишет весь вывод в них. Так что Buildbot'у на вход ничего не достаётся. Во-вторых, тест зависает в некоторых местах, не доходя до конца за отведённые шагу 20 минут, при том, что в ручном режиме тест проходит от силы 2-3 минуты.

Разгадка парадокса нашлась на форуме MSDN в обсуждении интеграции аналогичных шагов в Jenkins. Для успешного прохождения теста на сертификацию необходима активная пользовательская сессия. Что Jenkins, что Buildbot запускают свои узлы на тестовых машинах в контексте системной службы, у которой активной сессии нет. Для преодоления зависания пришлось выключить запуск Buildslave'a как службы, сделать автоматический вход в систему на тестовом узле с Windows 8 и добавить Buildslave в стандартную автозагрузку. В новом режиме повисание теста пропало, но проблема с множественными консолями осталась актуальной.



Примеры приложения для Marketplace c OpenCV на планшете с Windows 8

Заключение

Общий итог работы: OpenCV готова к применению в продуктовых приложениях для обеих архитектур ARM и x86: работает вся алгоритмическая часть, есть поддержка распараллеливания с TBB, чтение и запись изображений в большинстве популярных форматов. Реализована пара примеров интеграции в xaml-приложение. Есть проблемы с вводом-выводом видео, но, как показала практика, эта функциональность малополезна в коде конечного приложения. В любом случае придётся встраиваться в уже существующий конвейер камеры или декодирования видео. Кроме того, опыт показал, что платформа не очень дружелюбна к автоматическому тестированию, поэтому не откладывайте этот вопрос в долгий ящик. Инфраструктурные вопросы могу занять существенно больше времени, чем вы предполагаете.

Сейчас, видимо, в связи с не очень большой популярностью платформы, интерес заказчика к развитию поддержки Windows Runtime существенно поубавился и плавно перешёл к энтузиастам из сообщества. Если у вас есть интерес к развитию OpenCV на Windows RT – присоединяйтесь (OpenCV How to contribute, http://itseez.com/jobs/)!

Автор: Alex_alhimik

Источник

Поделиться

* - обязательные к заполнению поля