Poniższy artykuł to jedna z części tworzonego na bieżąco kursu, który pomoże Ci opanować praktyczne umiejętności, które pozwolą Ci w pełni wykorzystać potencjał sztucznej inteligencji. Chcesz przejść do pozostałych lekcji?
Zmiana architektury w React Native na Fabric, a w jej skutek zastosowanie synchronicznej komunikacji między częścią natywną a JS, umożliwiło stworzenie bibliotek, które prowadzą do świetnej wydajności, nieosiągalnej wcześniej.
Jedną z bibliotek umożliwiającą taką komunikację jest react-native-reanimated, którą wykorzystam wspólnie z react-native-vision-camera do stworzenia aplikacji umożliwiającej wykrywanie elementów z kamery urządzenia w czasie rzeczywistym z wykorzystaniem biblioteki OpenCV.
OpenCV to wieloplatformowa i otwarta biblioteka służąca do obróbki obrazu, stworzona w języku C++, z możliwością skorzystania z nakładek dla innych języków takich jak Java, JavaScript, Python.
Obecnie brak jest jednak sensownych bibliotek dla React Native, które umożliwiły by w sposób prosty wykorzystanie funkcjonalności OpenCV bezpośrednio w kodzie JS. Z pomocą przychodzi nam jednak możliwość wykorzystania kodu natywnego oraz stworzenie komunikacji między wątkiem natywnym, a wątkiem JS.
Standardowe podejście opisywane w niektórych postach, umożliwia stworzenie mostu, z którym komunikacja będzie przebiegać w sposób asynchroniczny. Jednak jest to podejście, które często nie działa wystarczająco sprawnie w taki sposób, aby dokonywać detekcji czy przekształceń w czasie rzeczywistym.
- Link: Szczegóły dotyczące Biblioteki OpenCV
- Link: Informacje o react-native-vision-camera oraz natywnych frame procesorach
Istotna informacja Wpis był tworzony z wykorzystaniem OpenCV 4.6.0 oraz React Native 0.68.2. W przypadku innych wersji biblioteki, niektóre kroki mogą się różnić. Zwłaszcza importowania OpenCV do projektu.
Tworzenie projektu
Pierwszym krokiem będzie stworzenie nowej aplikacji z wykorzystaniem polecenia:
npx react-native init opencvframeprocessor
Po instalacji niezbędnych podów oraz stworzeniu katalogów, przechodzimy do importowania OpenCV do naszego projektu, oddzielnie dla systemu iOS i Android.
Importowanie OpenCV
Importowanie OpenCV dla iOS
Pierwszym krokiem będzie pobranie OpenCV w wersji dla iOS. W moim przypadku jest to wersja 4.6.0.
Po pobraniu biblioteki, uruchamiamy nasz projekt w Xcode (pamiętajmy aby był to projekt z rozszerzeniem .xcworkspace. Aby zaimportować bibliotekę, przeciągamy pobrany katalog o nazwie opencv2.framework do głównego projektu (lewy panel okna).
Następnie zaznaczamy opcję >Copy items if needed< oraz klikamy >Finish<. Biblioteka powinna pojawić się w panelu po lewej stronie okna.
Kolejnym krokiem, będzie dołączenie do projektu wymaganych frameworków. Możemy to zrobić w ustawieniach projektu -> Build Phases -> Link Binary With Libraries.
Do projektu powinna zostać dodana następująca lista elementów:
- QuartzCore.framework,
- CoreVideo.framework,
- CoreImage.framework,
- AssetsLibrary.framework,
- CoreFoundation.framework,
- CoreGraphics.framework,
- CoreMedia.framework,
- Accelerate.framework.
Następnym krokiem będzie stworzenie plików z obsługą OpenCV w projekcie. Pliki tworzymy w katalogu głównym projektu (tam gdzie znajdują się pliki AppDelegate.h i AppDelegate.m).
Najpierw tworzymy nowy plik z headerem naszego pliku – nazwijmy go OpenCV.h.
Deklarujemy w nim nową klasę oraz przykładową metodę do pobierania wersji OpenCV. Aby to zrobić korzystamy z poniższego kodu.
#ifndef OpenCV_h
#define OpenCV_h
#include <Foundation/Foundation.h>
@interface OpenCV: NSObject
+ (NSString *) getOpenCVVersion;
@end
#endif /* OpenCV_h */
Następnie tworzymy nowy plik Objective-C o nazwie OpenCV.m.
Jako, że biblioteka OpenCV jest napisana z wykorzystaniem C++, konieczna będzie zmiana formatu utworzonego pliku na format .mm (czyli inaczej Objective C++). Możemy to zrobić w prawym panelu (poprzez dopisanie formatu do nazwy pliku).
Następnie w utworzonym pliku tworzymy kod implementujący klasę z pliku z nagłówkiem.
#import <Foundation/Foundation.h>
#import "OpenCV.h"
#import <opencv2/opencv.hpp>
@implementation OpenCV : NSObject
+ (NSString *) getOpenCVVersion {
return [NSString stringWithFormat:@"Version: %s", CV_VERSION];
}
@end
Kolejnym krokiem będzie utworzenie pliku PCH, w którym dodamy informację, że biblioteka OpenCV będzie wymagała kompilatora dla języka Objective C++. Aby to zrobić dodajemy nowy plik PCH o nazwie PrefixHeader w lokalizacji pozostałych, wcześniej utworzonych plików.
I ustawiamy mu następującą treść:
#ifndef PrefixHeader_pch
#define PrefixHeader_pch
#ifdef __cplusplus
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#endif
#endif
Następnie w ustawieniach projektu musimy wskazać jego lokalizację.
W tym celu w Build Settings -> Prefix Header dodajemy wpis o treści:
${PROJECT_DIR}/PrefixHeader.pch.
Po tym wszystkim sprawdzamy czy aplikacja się buduje – jeżeli tak, nasza biblioteka została dodana poprawnie i możemy przejść do kolejnych kroków.
Importowanie OpenCV dla Androida
Aby pobrać bibliotekę OpenCV dla Androida. Wracamy do strony głównej OpenCV, z której pobieraliśmy bibliotekę dla iOS jednak tym razem wybieramy pakiet dla systemu Android.
Po pobraniu i rozpakowaniu archiwum otwieramy nasz projekt w Android Studio. Pierwszym krokiem do importu naszego modułu będzie wybranie opcji File -> Import module oraz wskazaniu lokalizacji do katalogu sdk (Uwaga! nie będzie to katalog sdk/java). Bibliotekę nazywamy, np. openCVLib, a pozostałe opcje pozostawiamy domyślnie.
Następnie musimy dodać wsparcie dla języka Kotlin. W pliku build.gradle dodajemy następujące elementy:
ext {
...
kotlin_version = '1.6.10'
...
}
...
dependencies {
...
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
Przechodzimy do dodania biblioteki jako zależności dla projektu. W menu File wybieramy opcję Project Structure.
Wchodzimy w zakładkę Dependencies i klikamy ikonę + wybierając opcję Module Dependency. W kolejnym kroku wybieramy naszą bibliotekę OpenCV i dodajemy zależność.
Kolejnym krokiem będzie dodanie plików jniLibs do naszej aplikacji. W katalogu app/src/main tworzymy katalog jniLibs i kopiujemy tam zawartość katalogu sdk/native/libs z wcześniej pobranego archiwum.
W pliku dodajemy następującą linijkę, aby naprawić błąd przy budowaniu aplikacji.
android {
// ...
packagingOptions {
pickFirst '**/*.so'
}
}
Następnie musimy sprawdzić czy biblioteka została poprawnie zaimportowana. W pliku MainActivity.java importujemy pakiet biblioteki.
import org.opencv.android.OpenCVLoader;
Oraz w klasie MainActivity dodajemy statyczne pole:
static {
if(OpenCVLoader.initDebug()) {
Log.d(„TEST”, "opencv loaded");
}
}
Po zbudowaniu i włączeniu aplikacji w logach powinien wyświetlić się komunikat o załadowaniu OpenCV.
Instalacja wymaganych bibliotek
Jak wspomniałem wcześniej, kolejnym krokiem będzie dodanie bibliotek Vision Camera oraz Reanimated do naszego projektu. W tym celu w katalogu głównym projektu React Native wykonujemy polecenia:
yarn add react-native-vision-camera react-native-reanimated
npx pod-install
Aby poprawnie dodać bibliotekę oraz dodać niezbędne uprawnienia, przejdź przez proces instalacji opisany tutaj: https://mrousavy.com/react-native-vision-camera/docs/guides.
Tworzenie Frame Processorów
Frame Processor dla iOS
Aby stworzyć nowy frame procesor dla biblioteki Vision Camera, konieczne jest stworzenie pliku w którym zaszyjemy logikę. Jednak zanim to zrobimy musimy rozbudować nasz plik OpenCV.mm o funkcje umożliwiające detekcję obiektów.
W naszym przypadku będzie to wykrywanie niebieskiego kwadratu. Frame processor domyślnie zwraca nam klatkę z aparatu w formie obiektu o typie CMSampleBufferRef i dlatego konieczne będzie przygotowanie funkcji, która umożliwi nam jego konwersję na standardowy obraz używany w iOS o typie UIImage. Możemy to zrobić za pomocą funkcji (dodajmy ją w klasie OpenCV w pliku OpenCV.mm):
+ (UIImage *) toUIImage:(CMSampleBufferRef)samImageBuff
{
CVImageBufferRef imageBuffer =
CMSampleBufferGetImageBuffer(samImageBuff);
CIImage *ciImage = [CIImage imageWithCVPixelBuffer:imageBuffer];
CIContext *temporaryContext = [CIContext contextWithOptions:nil];
CGImageRef videoImage = [temporaryContext
createCGImage:ciImage
fromRect:CGRectMake(0, 0,
CVPixelBufferGetWidth(imageBuffer),
CVPixelBufferGetHeight(imageBuffer))];
UIImage *image = [[UIImage alloc] initWithCGImage:videoImage];
CGImageRelease(videoImage);
return image;
}
Biblioteka OpenCV wykonuje operacje na tzw. matrycach. Stąd konieczna będzie funkcja, która umożliwi nam konwersję UIImage na obiekt typu Mat. Możemy to zrobić na przykład w następujący sposób:
+ (cv::Mat)cvMatFromUIImage:(UIImage *)image
{
CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);
CGFloat cols = image.size.width;
CGFloat rows = image.size.height;
cv::Mat cvMat(rows, cols, CV_8UC4);
CGContextRef contextRef = CGBitmapContextCreate(cvMat.data,
cols,
rows,
8,
cvMat.step[0],
colorSpace,
kCGImageAlphaNoneSkipLast |
kCGBitmapByteOrderDefault);
CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
CGContextRelease(contextRef);
return cvMat;
}
Następnie dodajmy funkcję w której będzie implementowane wykrywanie niebieskich obiektów.
+ (NSDictionary *)findObjects:(UIImage *)image {
}
Nasza detekcja będzie przebiegała w następujący sposób:
- Obrazek domyślnie zapisany w formacie RGB, przekonwertujemy na BGR oraz następnie na HSV.
- Bazując na przedziale wytniemy tylko taki kolor który nas interesuje (będzie to niebieski).
- Wykryjemy kontury niebieskich elementów.
- Pierwszy z nich większy niż sprecyzowana wartość będzie naszym wykrytym elementem, więc zwrócimy jego pozycję oraz wielkość.
Na początku sprecyzujmy więc nasze przedziały wartości. Dla koloru niebieskiego, będą to np.
cv::Vec3b lowerBound(90, 120, 120);
cv::Vec3b upperBound(140, 255, 255);
Następnie wykonajmy niezbędne transformacje kolorów.
cv::Mat matBGR, hsv;
std::vector<cv::Mat> channels;
cv::Mat matRGB = [self cvMatFromUIImage:(image)];
cv::cvtColor(matRGB,matBGR,cv::COLOR_RGB2BGR);
cv::cvtColor(matBGR,hsv,cv::COLOR_BGR2HSV);
cv::inRange(hsv, lowerBound, upperBound, hsv);
cv::split(hsv, channels);
Następnie wykryjmy nasz kontur, i zwróćmy w postaci obiektu NSDictionary (tak aby był możliwy do odebrania po stronie JS).
std::vector<std::vector<cv::Point>> contours;
cv::findContours(channels[0], contours, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE );
std::vector<NSDictionary *> rects;
for( int i = 0; i< contours.size(); i++ ) {
double area = contourArea(contours[i],false);
if (area>3000) {
cv::Rect rect = cv::boundingRect(contours.at(i));
return @{@"x": [NSNumber numberWithInt:rect.x] , @"y":
[NSNumber numberWithInt: rect.y], @"width": [NSNumber numberWithInt:rect.width], @"height": [NSNumber numberWithInt:rect.height] };
}
}
W przypadku braku takich elementów w klatce, zwróćmy pusty obiekt.
return @{};
Dodane przez nas funkcje musimy dodać do pliku z nagłówkiem tj. OpenCV.h. Po zmianach będzie on wyglądał następująco.
#ifndef OpenCV_h
#define OpenCV_h
#include <Foundation/Foundation.h>
#import <UIKit/UIImage.h>
#import <CoreMedia/CMSampleBuffer.h>
@interface OpenCV: NSObject
+ (NSString *) getOpenCVVersion;
+ (UIImage *) toUIImage:(CMSampleBufferRef)samImageBuff;
+ (NSDictionary *)findObjects:(UIImage *)image;
@end
#endif /* OpenCV_h */
Następnie musimy utworzyć nowy plik z naszym Frame procesorem. Nazwijmy go ObjectDetectFrameProcessor.mm i dodajmy do niego następujący kod.
#import <Foundation/Foundation.h>
#import <VisionCamera/FrameProcessorPlugin.h>
#import <VisionCamera/Frame.h>
#import <opencv2/opencv.hpp>
#import "OpenCV.h"
@interface ObjectDetectFrameProcessor : NSObject
@end
@implementation ObjectDetectFrameProcessor
static inline id objectDetect(Frame* frame, NSArray* args) {
CMSampleBufferRef buffer = frame.buffer;
return [OpenCV findObjects:[OpenCV toUIImage:buffer]];
}
VISION_EXPORT_FRAME_PROCESSOR(objectDetect)
@end
Tak dodany kod, możemy już wykorzystać w kodzie JS. Najpierw jednak dodajmy podobną funkcjonalność także dla systemu Android.
Frame Processor dla Androida
Domyślnym formatem zwracanym przez bibliotekę Vision Camera w Frame procesorze dla Androida jest ImageProxy. Aby dodać wsparcie dla niego w pliku app/build.gradle w sekcji dependencies musimy dodać:
implementation 'androidx.camera:camera-core:1.1.0-beta02'
Dodajmy plik OpenCV.java, który będzie zawierał funkcję findObject, która będzie odpowiadała za wykrywanie niebieskich obiektów. Dodatkowo dodajmy pomocnicza metodę do konwersji obiektu ImageProxy na obiekt Mat.
package com.opencvframeprocessor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.YuvImage;
import com.facebook.react.bridge.WritableNativeMap;
import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.imgproc.Imgproc;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import androidx.camera.core.ImageProxy;
public class OpenCV {
static WritableNativeMap findObjects(Mat matRGB) {
Scalar lowerBound = new Scalar(90, 120, 120);
Scalar upperBound = new Scalar(140, 255, 255);
Mat matBGR = new Mat(), hsv = new Mat();
List<Mat> channels = new ArrayList<>();
Imgproc.cvtColor(matRGB, matBGR, Imgproc.COLOR_RGB2BGR);
Imgproc.cvtColor(matBGR, hsv, Imgproc.COLOR_BGR2HSV);
Core.inRange(hsv, lowerBound, upperBound, hsv);
Core.split(hsv, channels);
List<MatOfPoint> contours = new ArrayList<>();
Mat hierarchy = new Mat();
Imgproc.findContours(channels.get(0), contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE);
for (int i = 0; i < contours.size(); i++) {
MatOfPoint contour = contours.get(i);
double area = Imgproc.contourArea(contour);
if(area > 3000) {
Rect rect = Imgproc.boundingRect(contour);
WritableNativeMap result = new WritableNativeMap();
result.putInt("x", rect.x);
result.putInt("y", rect.y);
result.putInt("width", rect.width);
result.putInt("height", rect.height);
return result;
}
}
return new WritableNativeMap();
}
static Mat imageToMat(ImageProxy imageProxy) {
ImageProxy.PlaneProxy[] plane = imageProxy.getPlanes();
ByteBuffer yBuffer = plane[0].getBuffer();
ByteBuffer uBuffer = plane[1].getBuffer();
ByteBuffer vBuffer = plane[2].getBuffer();
int ySize = yBuffer.remaining();
int uSize = uBuffer.remaining();
int vSize = vBuffer.remaining();
byte[] nv21 = new byte[ySize + uSize + vSize];
yBuffer.get(nv21, 0, ySize);
vBuffer.get(nv21, ySize, vSize);
uBuffer.get(nv21, ySize + vSize, uSize);
try {
YuvImage yuvImage = new YuvImage(nv21, ImageFormat.NV21, imageProxy.getWidth(), imageProxy.getHeight(), null);
ByteArrayOutputStream stream = new ByteArrayOutputStream(nv21.length);
yuvImage.compressToJpeg(new android.graphics.Rect(0, 0, yuvImage.getWidth(), yuvImage.getHeight()), 90, stream);
Bitmap bitmap = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());
Matrix matrix = new Matrix();
matrix.postRotate(90);
stream.close();
Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
Mat mat = new Mat(rotatedBitmap.getWidth(), rotatedBitmap.getHeight(), CvType.CV_8UC4);
Utils.bitmapToMat(rotatedBitmap, mat);
return mat;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
Aby dodać Frame Processor, musimy stworzyć plik ObjectDetectFrameProcessorPlugin.java z następującą zawartością:
package com.opencvframeprocessor;
import androidx.annotation.NonNull;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import com.mrousavy.camera.frameprocessor.FrameProcessorPlugin;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nonnull;
public class ObjectDetectFrameProcessorPluginModule implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
FrameProcessorPlugin.register(new ObjectDetectFrameProcessorPlugin());
return Collections.emptyList();
}
@Nonnull
@Override
public List<ViewManager> createViewManagers(@Nonnull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
Oraz ObjectDetecFrameProcessorPluginModule.java:
package com.opencvframeprocessor;
import androidx.camera.core.ImageProxy;
import com.mrousavy.camera.frameprocessor.FrameProcessorPlugin;
import org.opencv.core.Mat;
public class ObjectDetectFrameProcessorPlugin extends FrameProcessorPlugin {
@Override
public Object callback(ImageProxy image, Object[] params) {
Mat mat = OpenCV.imageToMat(image);
return OpenCV.findObjects(mat);
}
ObjectDetectFrameProcessorPlugin() {
super("objectDetect");
}
}
Następnie moduł musi zostać zarejestrowany. W pliku MainApplication.java pod linijką:
List<ReactPackage> packages = new PackageList(this).getPackages();
Dodajemy nowy wpis z naszym modułem.
packages.add(new ObjectDetectFrameProcessorPluginModule());
Tak przygotowany moduł jest już gotowy do użycia w kodzie JS.
Wykorzystanie Frame Procesorów po stronie JS
Aby umożliwić korzystanie z procesora klatek po stronie aplikacji, musimy dodać możliwość wykrywania go przez plugin react-native-reanimated. Aby to zrobić musimy dodać odpowiedni wpis w pliku babel.config.js(znajduje się on w katalogu głównym aplikacji).
plugins: [
[
'react-native-reanimated/plugin',
{
globals: ['__objectDetect'],
},
],
],
Nazwa __objectDetect nie jest przypadkowa – taką samą podaliśmy w kodzie natywnym naszych procesorów. Dodajemy tylko znaki „__” na początku nazwy.
Przejdźmy do pliku App.js. Najpierw musimy zadeklarować naszą funkcję odpowiedzialną za wywołanie kodu natywnego.
function objectDetect(frame) {
'worklet';
return __objectDetect(frame);
}
W komponencie App dodajemy następnie nasz kod. Najpierw zacznijmy od zadeklarowania miejsca do przechowywania parametrów wykrytego kwadratu.
const flag = useSharedValue({height: 0, left: 0, top: 0, width: 0});
const flagOverlayStyle = useAnimatedStyle(
() => ({
backgroundColor: 'blue',
position: 'absolute',
...flag.value,
}),
[flag],
);
Dzięki zastosowaniu hooka useSharedValue możemy przekazywać wartości pozycji i wielkości kwadratu bezpośrednio do stylu wykorzystującego hook useAnimatedStyle. Oba pochodzą z biblioteki react-native-reanimated.
Ważną sprawą jest również sprawdzenie uprawnień do aparatu, bez tego nie uda nam się uruchomić aparatu.
useEffect(() => {
const checkPermissions = async () => {
await Camera.requestCameraPermission();
};
checkPermissions();
}, []);
Przejdźmy do deklaracji frame procesora. Po wykryciu obiektu, musimy przekonwertować wartość pozycji i wielkości z klatki z aparatu na wielkości rozdzielczości ekranu urządzenia (z powodu, że mają one różne wymiary). Z powodu, że wielkość klatki jest podawana odwrotnie na iOS niż na Android musimy dokonać zamiany wielkości.
const dimensions = useWindowDimensions();
const frameProcessor = useFrameProcessor(frame => {
'worklet';
const rectangle = objectDetect(frame);
const xFactor =
dimensions.width / Platform.OS === 'ios' ? frame.width : frame.height;
const yFactor =
dimensions.height / Platform.OS === 'ios' ? frame.height : frame.width;
if (rectangle.x) {
flag.value = {
height: rectangle.height * yFactor,
left: rectangle.x * xFactor,
top: rectangle.y * yFactor,
width: rectangle.width * xFactor,
};
} else {
flag.value = {height: 0, left: 0, top: 0, width: 0};
}
}, []);
Następnie nasz komponent musi zwrócić komponent <Camera /> oraz animowany kwadrat.
if (device == null) {
return null;
}
return (
<>
<Camera
frameProcessor={frameProcessor}
style={StyleSheet.absoluteFill}
device={device}
isActive={true}
orientation="portrait"
/>
<Animated.View style={flagOverlayStyle} />
</>
);
Cały plik wygląda następująco:
import React, {useEffect} from 'react';
import 'react-native-reanimated';
import {Platform, StyleSheet, useWindowDimensions} from 'react-native';
import {
Camera,
useFrameProcessor,
useCameraDevices,
} from 'react-native-vision-camera';
import {useSharedValue, useAnimatedStyle} from 'react-native-reanimated';
import Animated from 'react-native-reanimated';
export function objectDetect(frame) {
'worklet';
return __objectDetect(frame);
}
function App() {
const flag = useSharedValue({height: 0, left: 0, top: 0, width: 0});
const flagOverlayStyle = useAnimatedStyle(
() => ({
backgroundColor: 'blue',
position: 'absolute',
...flag.value,
}),
[flag],
);
const dimensions = useWindowDimensions();
const frameProcessor = useFrameProcessor(frame => {
'worklet';
const rectangle = objectDetect(frame);
const xFactor =
dimensions.width / Platform.OS === 'ios' ? frame.width : frame.height;
const yFactor =
dimensions.height / Platform.OS === 'ios' ? frame.height : frame.width;
if (rectangle.x) {
flag.value = {
height: rectangle.height * yFactor,
left: rectangle.x * xFactor,
top: rectangle.y * yFactor,
width: rectangle.width * xFactor,
};
} else {
flag.value = {height: 0, left: 0, top: 0, width: 0};
}
}, []);
const devices = useCameraDevices();
const device = devices.back;
useEffect(() => {
const checkPermissions = async () => {
await Camera.requestCameraPermission();
};
checkPermissions();
}, []);
if (device == null) {
return null;
}
return (
<>
<Camera
frameProcessor={frameProcessor}
style={StyleSheet.absoluteFill}
device={device}
isActive={true}
orientation="portrait"
/>
<Animated.View style={flagOverlayStyle} />
</>
);
}
export default App;
Rezultaty
Sprawdźmy jak działa nasz kod na obu systemach.
Podgląd działania aplikacji dla systemu iOS
Podgląd działania aplikacji dla systemu Android
Podsumowanie
Proces importowania biblioteki OpenCV oraz wykorzystanie jej do detekcji obiektów w czasie rzeczywistym nie jest łatwym zadaniem. Mnogość wersji oraz sposób ich wykorzystania, często dostarcza wielu problemów trudnych do rozwiązania.
Nie mniej jednak efekt jest wystarczającą nagrodą za przebytą drogę. Niestety głównym problemem przy wykorzystywaniu OpenCV w aplikacjach React Native jest konieczność tworzenia kodu Natywnego czy to w Java (lub Kotlin) w przypadku systemu Android, czy Objective C/C++ (lub Swift) w przypadku iOS.
Linki
1. Pełne repozytorium z kodem.
2. Artykuł w wersji angielskiej.
Chcesz stworzyć projekt oparty na algorytmach uczenia maszynowego i sztucznej inteligencji? Szukasz doświadczonego zespołu specjalistów?
Bibliografia
- https://brainhub.eu/library/opencv-react-native-image-processing – bardzo dobry artykuł zawierający sposób na importowanie OpenCV do React Native. Niestety nie jest aktualny dla najnowszych wersji biblioteki.
- https://opencv.org – strona główna biblioteki OpenCV, zawierająca dokumentację oraz przykłady użycia.
- https://mrousavy.com/react-native-vision-camera/docs/guides/frame-processors – dokumentacja zawierająca przykłady użycia procesorów klatek.