Введение
Изменение архитектуры React Native на Fabric и, следовательно, использование синхронной связи между нативной частью и JS позволило создавать библиотеки, обеспечивающие высокую производительность, недостижимую ранее. Одна из библиотек, обеспечивающих такую коммуникацию, — это react-native-reanimated, которую я буду использовать вместе с react-native-vision-camera для создания приложения, позволяющего обнаруживать элементы с камеры устройства в режиме реального времени с помощью библиотеки OpenCV.
OpenCV — это кроссплатформенная библиотека с открытым исходным кодом для обработки изображений, разработанная на C++, но с портами для других языков, таких как Java, JavaScript и Python. Однако в настоящее время не хватает значимых библиотек для React Native, которые позволили бы легко использовать функциональность OpenCV непосредственно в JS-коде. Однако с помощью появляется возможность использовать собственный код и создавать связь между собственным потоком и потоком JS. Стандартный подход, описанный в некоторых постах, позволяет создать бридж, с которым общение будет происходить асинхронно. Однако это подход, который часто работает недостаточно эффективно, чтобы выполнять обнаружение или преобразование в режиме реального времени.
Подробности о библиотеке OpenCV можно найти здесь: https://opencv.org. Информацию о react-native-vision-camera и нативных кадровых процессорах можно найти здесь: https://mrousavy.com/react-native-vision-camera/docs/guides/frame-processors
Важное примечание: запись была создана с использованием OpenCV 4.6.0 и React Native 0.68.2. Для других версий особенно шаги по импорту OpenCV в проект могут отличаться.
Создание проекта
Первым шагом будет создание нового приложения с помощью команды:
Процессор инициализации opencvframe с помощью npx react-native
После установки необходимых pod’ов и создания каталогов приступаем к импорту OpenCV в наш проект, отдельно для iOS и Android.
Импорт OpenCV для iOS
Первым шагом будет перейти на https://opencv.org/releases/ и загрузить версию OpenCV для iOS. В моем случае это версия 4.6.0.
После загрузки библиотеки запускаем наш проект в Xcode (не забудьте сделать его проектом с расширением .xcworkspace). Чтобы импортировать библиотеку, мы перетаскиваем скачанный каталог с именем opencv2.framework в основной проект (левая панель окна).
Затем установите флажок «Копировать элементы, если необходимо» и нажмите «Готово». Библиотека должна появиться на панели в левой части окна.
Следующим шагом будет присоединение необходимых фреймворков к проекту. Мы можем сделать это в настройках проекта -› Фазы сборки -› Связать двоичный файл с библиотеками. В проект необходимо добавить следующий список элементов:
- QuartzCore.framework
- CoreVideo.framework
- CoreImage.framework
- AssetsLibrary.framework
- CoreFoundation.framework
- CoreGraphics.framework
- CoreMedia.framework
- Ускорение.framework
Следующим шагом будет создание файлов поддержки OpenCV в проекте. Мы создаем файлы в корневом каталоге проекта (где находятся файлы AppDelegate.h и AppDelegate.m).
Сначала мы создаем новый файл с нашим заголовком — назовем его OpenCV.h.
В нем мы объявляем новый класс и пример метода для получения версии OpenCV. Для этого мы используем следующий код.
Затем мы создаем новый файл Objective-C с именем OpenCV.m.
Поскольку библиотека OpenCV написана с использованием C++, необходимо будет изменить формат создаваемого файла на формат .mm (другими словами, Objective C++). Мы можем сделать это на правой панели (дописав формат к имени файла).
Затем в созданном файле создаем код, реализующий класс из заголовочного файла.
Следующим шагом будет создание файла PCH, в который мы добавим информацию о том, что библиотеке OpenCV потребуется компилятор для Objective C++. Для этого мы добавим новый файл PCH с именем PrefixHeader в расположение других ранее созданных файлов.
И задаем ему следующий контент:
Далее в настройках проекта нам нужно указать его расположение. В разделе Настройки сборки -> Префикс заголовка мы добавляем запись следующего содержания: ${PROJECT_DIR}/PrefixHeader.pch.
После этого проверяем, собирается ли приложение — если да, то наша библиотека добавлена корректно и можно переходить к следующим шагам.
Импорт OpenCV для Android
Чтобы загрузить библиотеку OpenCV для Android, мы возвращаемся на https://opencv.org/releases/, однако на этот раз мы выбираем пакет Android.
Скачав и распаковав архив, открываем наш проект в Android Studio. Первым шагом для импорта нашего модуля будет выбор Файл -> Импорт модуля и указание местоположения в каталоге /sdk (обратите внимание! это не будет каталогом sdk/java). Мы называем библиотеку, например. openCVLib, а остальные параметры оставьте по умолчанию.
Далее нам нужно добавить поддержку языка Kotlin. В файле build.gradle добавляем следующее:
Мы приступаем к добавлению библиотеки в качестве зависимости для проекта. В меню Файл выберите Структура проекта.
Мы переходим на вкладку Зависимости и нажимаем значок +, выбирая Зависимость модуля. На следующем шаге мы выбираем нашу библиотеку OpenCV и добавляем зависимость.
Следующим шагом будет добавление файлов jniLibs в наше приложение. В каталоге app/src/main создаем каталог jniLibs и копируем туда содержимое каталога sdk/native/libs из скачанного ранее архива.
В файл app/build.gradle мы добавляем следующую строку, чтобы исправить ошибку при сборке приложения.
Далее нам нужно проверить, правильно ли была импортирована библиотека. В файле MainActivity.java мы импортируем пакет библиотеки.
А в класс MainActivity добавляем статическое поле:
После сборки и запуска приложения в журналах должно появиться сообщение о том, что OpenCV загружен.
Установка необходимых библиотек
Как я упоминал ранее, следующим шагом будет добавление в наш проект библиотек Vision Camera и Reanimated. Для этого запускаем команды в корневом каталоге проекта React Native:
добавить пряжу
npx pod-установить
Чтобы правильно добавить библиотеку и добавить необходимые разрешения, пройдите процесс установки, описанный здесь: https://mrousavy.com/react-native-vision-camera/docs/guides.
Кадровый процессор для iOS
Для того, чтобы создать новый процессор кадра для библиотеки Vision Camera, необходимо создать файл, в который мы зашиваем логику. Однако прежде чем мы это сделаем, нам нужно расширить наш файл OpenCV.mm функциями для обнаружения объектов. В нашем случае это будет обнаружение синего квадрата. Обработчик кадров по умолчанию возвращает нам кадр с камеры в виде объекта с типом CMSampleBufferRef, а потому необходимо будет подготовить функцию, которая позволит нам преобразовать его в стандартное используемое изображение в iOS с типом UIImage. Мы можем сделать это с помощью функции (давайте добавим ее в класс OpenCV в файле OpenCV.mm):
Библиотека OpenCV выполняет операции над так называемыми матрицами. Следовательно, нам понадобится функция, которая позволит нам преобразовать UIImage в объект Mat. Мы можем сделать это, например, следующим образом:
Далее добавим функцию, в которой будет реализовано обнаружение синих объектов.
Наше обнаружение будет происходить следующим образом:
- Мы преобразуем изображение, которое по умолчанию сохраняется в формате RGB, в BGR, а затем в HSV.
- Исходя из диапазона, мы будем резать только интересующий нас цвет (это будет синий).
- Мы обнаружим контуры синих элементов.
- Первым элементом, превышающим указанное значение, будет наш обнаруженный элемент, поэтому мы вернем его позицию и размер.
Итак, для начала давайте укажем наши диапазоны значений. Для синего цвета это будут, например:
Далее давайте выполним необходимые преобразования цвета.
Далее давайте обнаружим наш контур и вернемся в виде объекта NSDictionary (чтобы его можно было подобрать на стороне JS).
При отсутствии таких элементов в кадре вернуть пустой объект.
Нам нужно добавить функции, которые мы добавили в заголовочный файл, то есть OpenCV.h. После изменений это будет выглядеть следующим образом.
Далее нам нужно создать новый файл с нашим процессором Frame. Назовем его ObjectDetectFrameProcessor.mm и добавим в него следующий код.
Такой добавленный код мы уже можем использовать в JS-коде. Но сначала давайте добавим аналогичный функционал и для Android.
Фрейм-процессор для Android
Формат по умолчанию, возвращаемый библиотекой Vision Camera в процессоре Frame для Android, — ImageProxy. Чтобы добавить его поддержку в файл app/build.gradle в разделе dependencies, нам нужно добавить:
Давайте добавим файл OpenCV.java, который будет содержать функцию findObject, которая будет отвечать за обнаружение синих объектов. Кроме того, давайте добавим вспомогательный метод для преобразования объекта ImageProxy в объект Mat.
Чтобы добавить процессор кадров, нам нужно создать файл ObjectDetectFrameProcessorPlugin.java со следующим содержимым:
И ObjectDetectFrameProcessorPluginModule.java:
Затем модуль необходимо зарегистрировать. В файле MainApplication.java под строкой:
Мы добавляем новую запись с нашим модулем.
Подготовленный таким образом модуль теперь готов к использованию в JS-коде.
Использование процессоров Frame на стороне JS
Чтобы разрешить использование процессора фреймов на стороне приложения, нам нужно добавить возможность плагина react-native-reanimated обнаруживать его. Для этого нам нужно добавить соответствующую запись в файл babel.config.js (расположенный в корневом каталоге приложения).
Название __objectDetect не случайно, мы дали такое же имя в нативном коде наших процессоров. Мы просто добавляем символы «__» в начале имени.
Перейдем к файлу App.js. Сначала нам нужно объявить нашу функцию ответственной за вызов нативного кода.
Затем в компоненте приложения мы добавляем наш код. Во-первых, начнем с объявления места для хранения параметров обнаруженного квадрата.
Используя хук useSharedValue, мы можем передавать значения положения и размера квадрата непосредственно в стиль, используя хук useAnimatedStyle. Оба взяты из библиотеки react-native-reanimated.
Также важно проверить разрешения камеры, без этого мы не сможем запустить камеру.
Перейдем к объявлению фреймового процессора. Как только объект обнаружен, нам нужно преобразовать значения положения и размера из кадра с камеры в размеры разрешения экрана устройства (по той причине, что они разные). Из-за того, что размер кадра на iOS задается обратно пропорционально, чем на Android, нам необходимо выполнить преобразование размера.
Затем наш компонент должен вернуть компонент ‹Camera /› и анимированный квадрат.
Весь файл выглядит следующим образом:
Полученные результаты
Давайте проверим, как наш код работает в обеих системах. Начнем с iOS.
С другой стороны, на Android наше приложение работает следующим образом:
Краткое содержание
Процесс импорта библиотеки OpenCV и ее использования для обнаружения объектов в реальном времени — непростая задача. Множество версий и способы их использования часто создают множество трудноразрешимых проблем. Тем не менее, результат является достаточной наградой за путешествие. К сожалению, основная проблема с использованием OpenCV в приложениях React Native заключается в необходимости создавать собственный код, будь то на Java (или Kotlin) в случае Android или на Objective C/C++ (или Swift) в случае iOS.
Вы можете найти полный репозиторий с кодом здесь: https://github.com/dogtronic/blog-opencv-frame-processor
Рекомендации
https://brainhub.eu/library/opencv-react-native-image-processing, отличный пост, в котором рассказывается, как импортировать OpenCV в React Native, но, к сожалению, он не актуален для более новых версий библиотеки.
https://opencv.org, домашняя страница библиотеки OpenCV, включая документацию и примеры использования.
https://mrousavy.com/react-native-vision-camera/docs/guides/frame-processors, документация, включающая примеры использования процессоров кадров.
—
Вы можете найти польскую версию статьи здесь: https://dogtronic.io/tworzenie-natywnych-procesorow-klatek/
Найдите нас на нашем сайте Dogtronic.