blog

備忘録です。雑多な内容を書きます。

React Nativeでカメラ機能を活用する - React Native Vision Cameraの魅力

この記事は、 React Native Advent Calendar 2023 の2日目の記事です。

はじめに

みなさん、モバイルアプリケーション開発でカメラ機能使ってますか?
React Nativeではカメラを利用するのにライブラリを利用するのが多いかと思います。 ベーシックなカメラライブラリとしては expo-camera などがありますが、より拡張性の高いカメラライブラリとして react-native-vision-camera があり、私も開発しているアプリのいくつかでこのライブラリを採用しています。

公式のREADMEから引用すると、

VisionCamera is a powerful, high-performance Camera library for React Native. It features:
・ 📸 Photo and Video capture
・ 👁️ QR/Barcode scanner
・ 📱 Customizable devices and multi-cameras ("fish-eye" zoom)
・ 🎞️ Customizable resolutions and aspect-ratios (4k/8k images)
・ ⏱️ Customizable FPS (30..240 FPS)
・ 🧩 Frame Processors (JS worklets to run facial recognition, AI object detection, realtime video chats, ...)
・ 🔍 Smooth zooming (Reanimated)
・ ⏯️ Fast pause and resume
・ 🌓 HDR & Night modes
・ ⚡ Custom C++/GPU accelerated video pipeline (OpenGL)

とあるとおり、高いパフォーマンスで動作し、特に Frame Processors を用いた拡張が非常に強力です。
最近(2023年末時点) Vision Camera V3がリリースされ、これまでは Frame Processer を使ってQRコードのスキャンをする必要があったのがネイティブでサポートされ、より便利に使えるようになっています。

この記事では、Vision Cameraの強力な機能である Frame Processer を主に取り上げて紹介しようと思います。

FrameProcessorとは?

react-native-vision-camera.com

名前の通りですが、カメラのフレームを受け取って処理するための機構であり、ネイティブコード(Swift,Kotlin等)で書かれたフレームの処理結果をJavaScript側で受け取ることができるようになります。
コミュニティで作成された Frame Processor としてはGoogleのMLKitを使ったものが多く、QRコードのスキャンやオンデバイスOCRなどは簡単に利用できます。

下記は公式サイトから引用した FaceDetector の FrameProcessor を利用するコードですが、非常にシンプルなAPIになっており、scanFaces が Frame Processorの実装です。

const onFaceDetected = Worklets.createRunInJsFn((face: Face) => {
  navigation.push("FiltersPage", { face: face })
})

const frameProcessor = useFrameProcessor((frame) => {
  'worklet'
  const faces = scanFaces(frame)
  if (faces.length > 0) {
    onFaceDetected(faces[0])
  }
}, [onFaceDetected])

もう少しコードの説明をすると、 createRunInJsFn で関数を定義しているのは Frame Processor のコードは worklet と呼ばれる環境で実行されており、React NativeのJavaScriptコードが動作するスレッドとは別になっているため、値を受け渡すにはこのような書き方が必要になっています。*1

FrameProcessorを作ってみる

個人的にVIsion Camera V3対応の Frame Processorを作っていたのでこの記事で紹介しようと思っていたのですが、Advent Calendarの記事公開日までに間に合わなかった……ので、既存のプラグインをベースにして解説していこうと思います。

基本は、ほぼ以下のドキュメントに従うだけで Frame Processor は作れちゃいます。

react-native-vision-camera.com

個人的にはReact Nativeのネイティブライブラリのセットアップのほうが、ビルドまわりでハマりがちなので数倍難しい……。

vision-camera-ocrの実装を見る

MLKitのOCRプラグインである、vision-camera-ocr の実装を見ていきます。*2

github.com

iOS

https://github.com/ismaelsousa/vision-camera-ocr/blob/v2/ios/VisionCameraOcr.swift#L122

プラグインの本体はこの callback が入り口になっており、 Frame* がカメラのフレームになっています。

    public override func callback(_ frame: Frame, withArguments arguments: [AnyHashable: Any]?) -> Any? {
        
        guard let imageBuffer = CMSampleBufferGetImageBuffer(frame.buffer) else {
          print("Failed to get image buffer from sample buffer.")
          return nil
        }

        let ciImage = CIImage(cvPixelBuffer: imageBuffer)
        
        guard let cgImage = CIContext().createCGImage(ciImage, from: ciImage.extent) else {
            print("Failed to create bitmap from image.")
            return nil
        }
        
        let image = UIImage(cgImage: cgImage)

JavaScript側で設定したフレームレートで各フレームごとにcallbackが呼び出される仕組みになっており、Frame* からは CMSampleBufferRef _Nonnull buffer;UIImageOrientation orientation; が取れるようになっているので、あとはネイティブコードで画像に対して処理をするだけ。とてもシンプル。

https://github.com/mrousavy/react-native-vision-camera/blob/main/package/ios/Frame%20Processor/Frame.h

Android

https://github.com/ismaelsousa/vision-camera-ocr/blob/v2/android/src/main/java/com/visioncameraocr/OCRFrameProcessorPlugin.kt#L116

Androidもほぼ同じインターフェイスなので説明不要!

     override fun callback(frame: Frame, params: Map<String, Any>?): Any? {
         val result = hashMapOf<String, Any>()

         val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)

         @SuppressLint("UnsafeOptInUsageError")
         val mediaImage: Image? = frame.image

https://github.com/mrousavy/react-native-vision-camera/blob/main/package/android/src/main/java/com/mrousavy/camera/frameprocessor/Frame.java

Frame Processorの作り方のまとめ

callback の関数にフレームの構造体が来るので、そこで処理を書くだけ。
ゼロからFrame Processorを作る場合は、

  1. create-react-native-library でライブラリをセットアップ
  2. vision-camera-plugin-builder で Frame Processor の雛形が出来上がる
  3. インターフェイスに合うようにネイティブコードを書く

になると思います。

おわりに

iPadOS 17でUVCの外部カメラサポートも入りましたが、Vision CameraでもUVCが利用できるようになりました。 *3
開発が盛んで、本業でもMLKit の Text recognition V2 を使った日本語OCRのFrame Processorを開発して*4バイス上でOCRをしたりと活用の幅が非常に大きく、今後も使っていくであろう推しライブラリです。

参考

*1: React Native Reanimatedとおなじ

*2:元の実装は記事執筆時点でメンテナが不在になっているので、Vision Camera V3対応した、フォーク先をここでは参照

*3:https://github.com/mrousavy/react-native-vision-camera/pull/1824

*4:不安定なのでまだOSSとして公開してない。https://github.com/aarongrider/vision-camera-ocr/pull/32Vision Camera V3対応のものが出てきているので、そちらに取り込んでもらうようにするかも。