Всем привет! Как вы уже знаете по нашим статьям, мы в Smart Engines занимаемся распознаванием, причем распознавать мы стараемся на чем угодно и в любых условиях. Мы поддерживаем все популярные операционные системы: iOS, Android, Windows, Linux, MacOS, Solaris. Поддерживаем мы и отечественного производителя: Эльбрус и AstraLinux. Наши алгоритмы оптимизированы под ARMv7-v8, AArch64, x86, x86_64, SPARC, E2K, MIPS.
Поэтому, когда мы увидели нарастающую популярность российской операционной системы Sailfish Mobile OS RUS, мы не смогли обойти ее стороной. Sailfish Mobile OS RUS — это POSIX-совместимая операционная система для мобильных устройств, развиваемая отечественной компанией «Открытая Мобильная Платформа» для решения задач корпоративных пользователей и государственных учреждений. По состоянию на февраль 2018 года является единственной мобильной операционной системой, включенной в реестр Отечественного ПО и прошедшей сертификацию ФСБ по классу АК1/КС1.
В этой статье мы расскажем о своем опыте портирования нашей библиотеки распознавания Smart IDReader (технология Hieroglyph) на Sailfish OS. В ней будет код, ссылки и видео. Мы хотим, чтобы эта статья была технически информативной и полезной в качестве общей инструкции для тех, кто портирует С++ приложения на Sailfish OS.
Мы выражаем благодарность коллегам из Открытой Мобильной Платформы (ОМП) Кириллу Чувилину и Алексею Андрееву за консультации в разработке приложения и предоставление двух устройств INOI R7 для тестирования и демонстрации.
TL;DR — портировать было быстро и просто. Суммарно потребовалось меньше одной рабочей человеко-недели с момента скачивания Sailfish OS SDK до снятия первого видео с демонстрацией распознавания документов на устройстве INOI R7 (видео будут в конце статьи).
Установка инструментария
Первым шагом была установка Sailfish OS SDK. Скачать установочный пакет можно с официального сайта, но мы выбрали метод еще проще и установили SDK из AUR — пользовательского репозитория Arch Linux (самого популярного Linux-дистрибутива среди разработчиков Smart Engines), в котором, как обычно, все есть и устанавливается одной командой:
yaourt -S sailfishos-sdk-bin
Средства для разработки включают в себя три основных компонента:
- Окружение и система сборки — Oracle VM VirtualBox образ, содержащий установленные компиляторы и библиотеки для сборки приложений для устройства или эмулятора.
- SailfishOS Emulator, аналогичный эмуляторам для других мобильных платформ (iOS, Android, ...)
- IDE — всем известный Qt Creator, доделанный для поддержки взаимодействия с системой сборки и запуска программы на устройстве или эмуляторе.
Соединение с виртуальной машиной для сборки происходит через SSH. Единственной проблемой было то, что при установке SDK для виртуальной машины и IDE жестко проставляется порт 2222
, который уже использовался для других целей на компьютере разработчика. В VirtualBox этот порт меняется понятным образом, а вот в IDE поменять его из GUI было нельзя. К счастью, его можно было поменять в mersdk.xml
файле локальных настроек:
<!-- ~/.config/SailfishOS-SDK/qtcreator/mersdk.xml -->
<value type="QString" key="SshPort">2222</value>
Об этой проблеме после ее решения было сообщено Кириллу, а других проблем с инструментарием в дальнейшем не возникало.
Сборка С++ ядра распознавания
Наше ядро распознавания написано на С++ и в данном случае очевидным выбором для портирования на Sailfish OS была сборка ядра в виде статических библиотек.
Короткая, но информативная инструкция по ручной сборке библиотек находится на странице Building Sailfish OS packages manually официальной документации. По ней стало понятно, что требуется просто подключиться по SSH к виртуальной машине, а затем вызвать обычные команды сборки с указанием правильной архитектуры. Опишем процесс по шагам.
- Запускаем виртуальную машину Sailfish OS Build Engine (достаточно headless режима)
-
Подключаемся к ней:
ssh -p 2222 -i /opt/SailfishOS/SDK/vmshare/ssh/private_keys/engine/mersdk mersdk@localhost
Кстати, внешняя пользовательская home-директория по умолчанию доступна в виртуальной машине по адресу
/home/src1
. -
Получаем список поддерживаемых платформ:
[mersdk@SailfishSDK ~]$ sdk-assistant list Toolings: SailfishOS-2.1.3.7 Targets: SailfishOS-2.1.3.7-armv7hl # Устройство SailfishOS-2.1.3.7-i486 # Эмулятор
В отличие от примера на сайте, в именах платформ может присутствовать номер версии, которые тоже нужно будет учитывать. Далее сборка будет проводиться для устройства (SailfishOS-2.1.3.7-armv7hl), но для эмулятора весь процесс аналогичен.
Для требуемой архитектуры соответствующее окружение обеспечивается командой
sb2
(Scratchbox2), поэтому все нужно делать через нее:sb2 -t SailfishOS-2.1.3.7-armv7hl <args....>
-
Устанавливаем необходимые для сборки пакеты. В нашем случае это были CMake, GCC, G++ и Zip:
sb2 -t SailfishOS-2.1.3.7-armv7hl -m sdk-install -R zypper in cmake gcc gcc-c++ zip
-
Создаем папку сборки и вызываем
CMake
, причем передаем ему правильныйCMAKE_SYSROOT
, чтобы компиляторы искались в нужном нам месте:mkdir build && cd build sb2 -t SailfishOS-2.1.3.7-armv7hl -m sdk-build cmake -DCMAKE_SYSROOT=/srv/mer/targets/SailfishOS-2.1.3.7-armv7hl ..
-
Собираем проект:
sb2 -t SailfishOS-2.1.3.7-armv7hl -m sdk-build make install -j8
Вот и все! Таким образом, сборка под Sailfish OS практически не отличается от сборки под Linux — требуется лишь подключиться к виртуальной машине и вызывать команды с правильным окружением.
Создание и настройка Qt проекта
Процесс создания проекта тривиален и про него можно почитать, например, в хабе Sailfish OS на Хабрахабре. После этого нужно было сделать только одно — добавить пути к заголовочным файлам и статическим библиотекам в .pro файл следующим образом:
INCLUDEPATH += /path/to/install.armv7-linux.release/include
LIBS += -L/path/to/install.armv7-linux.release/lib
-lsomeStaticLibrary
-lanotherStaticLibrary
После этого проект с вызовом нашего С++ ядра распознавания успешно скомпилировался и запустился.
Написание GUI с QML и Sailfish Silica
GUI приложений для Sailfish OS пишется с помощью Qt на C++ и QML: как со стандартной библиотекой QtQuick, так и специальной Sailfish SIlica.
Практически вся требуемая информация по этим технологиям присутствует в их официальной документации. К дополнительным полезным ссылкам относятся хаб разработки под Sailfish OS на Хабре, шпаргалки по цветам и отступам, иконкам и основным компонентам, а также бесплатный вводный курс на Stepik.
Для отрисовки графических примитивов мы использовали Canvas и его метод onPaint
. Выбор документа горизонтальной прокруткой — SlideshowView (спасибо Алексею за пример реализации). Отображение результатов распознавания — SilicaListView с ListModel.
Помимо написания основных GUI элементов важно научиться вызывать С++ классы из QML кода, что довольно просто и понятно описано на странице Integrating QML and C++ официальной документации Qt: делаем С++ класс, регистрируем его в С++, импортируем его в QML. Для оборачивания С++ структур в QML самым очевидным способом показалось использование Q_GADGET
, про что даже есть статья на Хабре.
Взаимодействие с камерой устройства: детективно-научное расследование
Понимание того, как правильно взаимодействовать с API камеры, было единственной трудностью во всем процессе разработки демо-приложения и заслуживает отдельного пункта — потребовалось небольшое исследование с мозговым штурмом, гипотезами и экспериментами.
Сценарий использования наших библиотек распознавания видеопотока в реальном времени обычно такой: пока пользователь видит превью камеры и держит перед ней объект (например, паспорт РФ или банковскую карту), в библиотеку распознавания один за одним поступают приходящие с камеры кадры и распознаются. При этом, во время распознавания каждого кадра пользователю показывается промежуточная информация о расположении документа, его полей и т.д. Тем самым, требуется одновременно захватывать кадры с камеры и показывать превью пользователю.
При создании камеры и превью в QML вопросов не возникло: создаем Camera и ссылаемся на нее в VideoOutput:
Camera {
id: camera
position: Camera.BackFace
// ...
}
VideoOutput {
anchors.fill: parent
source: camera
}
Осталось найти способ перехватывать приходящие с камеры кадры и передавать их в С++ на распознавание.
В качестве первого способа мы попробовали стандартный QVideoProbe, специально существующий для получения приходящих с камеры кадров. Однако, вызов probe.setSource(camera)
возвращал false
, после чего в callback для перехвата кадров ничего не приходило. Судя по информации в интернете, такая проблема актуальна и для Android устройств, что может свидетельствовать о простом отсутствии реализации QVideoProbe для Sailfish OS. Надеемся, что в скором времени это исправится.
Второй способ — наследование от QAbstractVideoSurface и реализация метода present(const QVideoFrame &frame)
, который передает пришедший кадр на распознавание. После вызова camera.setViewFinder(&videoSurface)
кадры действительно начали приходить (в формате NV21) и даже распознаваться, если держать документ перед камерой… вот только превью в VideoOutput пропало и пользователь потерял возможность видеть, что же снимает его камера. Победить эту проблему быстро не удалось и мы вместе с Кириллом и Алексеем из ОМП начали искать третью альтернативу.
К счастью, третий способ коллективными усилиями был найден довольно быстро. Им оказалась связка из QAbstractVideoFilter, который методом createFilterRunnable()
создает QVideoFilterRunnable, в свою очередь реализующий функционал обработки кадра. После этого реализованный фильтр нужно добавить в QML и сослаться на него в имеющемся VideoOutput:
RecognitionVideoFilter {
id: recognitionVideoFilter
// ...
}
VideoOutput {
anchors.fill: parent
source: camera
filters: [ recognitionVideoFilter ]
}
Скомпилировалось, запустилось. Превью видео есть, но кадры по прежнему не приходят. И вдруг, Кирилл поделился инсайдерской информацией о том, что для работы фильтров на устройстве необходимо обновить QtMultimedia плагин, который оказался доступен по ссылке. После этого все заработало — кадры в формате BGRA начали приходить в ядро. Небольшая деталь — функция QAbstractVideoSurface::run
блокирует превью камеры (что логично, потому что фильтр подразумевает изменение кадра, а нам нужно его только прочитать и распознать), поэтому для плавного превью потребовалось запускать распознавание в отдельном потоке — например, через QtConcurrent::run(...).
Вот и все! Оставалось только дописать GUI приложения, что не составило особого труда.
Демонстрация на Mobile World Congress 2018 в Барселоне и другие видео
В этом году наша компания уже второй раз представила свои технологии распознавания на ежегодной выставке Mobile World Congress 2018 в Барселоне, снова поразившей нас своим масштабом: более 107 000 посетителей из 205 стран, 2400 стендов компаний, расположенных на 120 000 квадратных метрах павильонов. Как и в прошлом году, она произвела на нас крайне положительное впечатление.
Конечно, мы не упустили возможности как пойти похвастаться нашей демкой на стенде финской компании Jolla, являющейся оригинальным разработчиком Sailfish OS, так и показать работу программы "вживую" коллегам из Открытой Мобильной Платформы, которые даже сняли несколько видео и выложили их в группе ВКонтакте:
А вот видео с первой рабочей версией программы:
Много других видео с демонстрациями технологий Smart Engines можно посмотреть на нашем YouTube канале.
Заключение
В этой статье мы рассказали о своем опыте портирования библиотеки распознавания Smart IDReader на Sailfish OS, включая написание демо-приложения. Мы были приятно удивлены количеством релевантной официальной документации и качеством инструментария. Конечно, пара технических вопросов возникла, но их удалось оперативно победить. Надеемся, что наш опыт будет полезен и другим.
Автор: SmartEngines