Photoruction工事中!

Photoructionの開発ブログです!

CameraXのmlkit-visionを使ってみた

はじめに

Photoructionでは工事現場の写真撮影をメイン機能としてさまざまな機能があり、その写真撮影機能では現在Camera2を使ってカメラの制御をしています。

昨今では、Camera2の代わりにCameraXがstableで出始め、Camera2と比べ出来ることにもメリットデメリットがありますが、そろそろPhotoructionでもCameraXへの移行を進めようと考えています。

通常の写真撮影とは別の機能として、QRコードリーダーを実装する案件があったため、これを気にQRコードリーダーをCameraXで実装することになりました。

実装はcameraXの1.1.0をベースに実装しましたが、本記事執筆段階では1.2.0がstableとなり、1.3.0はalphaが出始めています。

今回は1.2.0で追加されるmlkit-visionへの置き換えを見ていこうと思います。

結果

今回1.1.0から1.2.0になることでMlKitAnalyzerを利用でき、そこにscannerをセットするだけでMlKitを使えるのでとても簡単になったと感じました。

ただし、1.1.0以前で既にImageAnalysis.Analyzerを作成済みで、特にそこのロジックをいじる予定もない場合は1.2.0への移行を急ぐ必要はないのかなと感じました。

それよりもCameraControllerを使用することで1.1.0でもカメラ周りの設定がより簡潔に行えることがわかりました。

準備

以下のライブラリ設定が現在です。

// camerax
implementation 'androidx.camera:camera-core:1.1.0'
implementation 'androidx.camera:camera-camera2:1.1.0'
implementation 'androidx.camera:camera-lifecycle:1.1.0'
implementation 'androidx.camera:camera-view:1.1.0'
// mlkit barcode scanning
implementation("com.google.mlkit:barcode-scanning:17.0.2")

まずbuild.gradleのcameraxライブラリのアップデートを行ない、mlkit-visionを追加します。公式ドキュメントはこちらです。

// camerax
implementation 'androidx.camera:camera-core:1.2.0'
implementation 'androidx.camera:camera-camera2:1.2.0'
implementation 'androidx.camera:camera-lifecycle:1.2.0'
implementation 'androidx.camera:camera-view:1.2.0'
implementation 'androidx.camera:camera-mlkit-vision:1.2.0-beta02'
// mlkit barcode scanning
implementation("com.google.mlkit:barcode-scanning:17.0.2")

camerax系は全て1.2.0にします。 mlkit-visionのみ1.2.0-beta02までしかでていません。 barcode-scanningはmlkit-visionがあっても必要です。

cameraX1.1.0の実装

startCamera()

プレビュー画面の設定からバーコード解析の設定、カメラの起動を行います。

private var cameraProvider: ProcessCameraProvider? = null
private fun startCamera() {
    val cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor()
    val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
    if (cameraProvider != null) {
        cameraProvider?.unbindAll()
    }
    cameraProvider = cameraProviderFuture.get()
    val surfaceProvider = binding.previewView.surfaceProvider
    val preview = Preview.Builder().build().apply {
        setSurfaceProvider(surfaceProvider)
    }
    val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
    val imageAnalysis = ImageAnalysis.Builder().build().apply {
        setAnalyzer(cameraExecutor, BarcodeAnalyzer())
    }
    cameraProviderFuture.addListener(
        {
            cameraProvider?.unbindAll()
            cameraProvider?.bindToLifecycle(
                viewLifecycleOwner,
                cameraSelector,
                preview,
                imageAnalysis
            )
        },
        mainThreadExecutor()
    )
}

ImageAnalysis.Analyzer

このクラスでバーコード解析を行います。

バーコード以外のMLKitも同じようにAnalyzerを実装するだけで、解析ができます。

一度に読み込んだバーコードをList\<Barcode>で取得し、Barcodeクラスにはフォーマットやデータ、PreviewView上のバーコードの位置情報などさまざまな情報が入っています。

今回は取得したBarcode情報をOverlayViewなどのカスタムビューへの出力の実装はしていませんが、実装してあげることでプレビュー上で認識したバーコードに被せて枠を表示したりできます。

class BarcodeAnalyzer :
    ImageAnalysis.Analyzer {
    override fun analyze(imageProxy: ImageProxy) {
        val mediaImage = imageProxy.image
        if (mediaImage != null) {
            val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
            BarcodeScanning.getClient().process(image)
                .addOnSuccessListener { barcodes ->
                    barcodes.forEach { it.format }
                    // バーコード情報の受け取り
                    // barcodesからデータを取り出して使用
                }
                .addOnCompleteListener {
                    imageProxy.close()
                }
        }
    }
}

cameraX1.2.0(mlkit-vision:1.2.0-beta02)

startCamera()

1.2.0(mlkit-vision:1.2.0-beta02)ではCameraControllerを使用し、mlkit-visionのMLKitAnalyzerを使用することで簡潔に実装できます。

なお、CameraControllerは以前から使用できます。

private var barcodeScanner: BarcodeScanner? = null
private lateinit var cameraController: LifecycleCameraController
private fun startCamera() {
    cameraController = LifecycleCameraController(requireContext())
    cameraController.cameraSelector = DEFAULT_BACK_CAMERA
    cameraController.bindToLifecycle(viewLifecycleOwner)
    binding.previewView.controller = cameraController
    barcodeScanner?.close()
    barcodeScanner = BarcodeScanning.getClient()
    cameraController.clearImageAnalysisAnalyzer()
    cameraController.setImageAnalysisAnalyzer(
        mainThreadExecutor(),
        MlKitAnalyzer(
            listOf(barcodeScanner),
            CameraController.COORDINATE_SYSTEM_VIEW_REFERENCED,
            mainThreadExecutor()
        ) { result ->
            // バーコード情報の受け取り
            val barcodes = result.getValue(barcodeScanner!!)
            // barcodesからデータを取り出して使用
        })
}

MlKitAnalyzerにlist型でbarcodeScannerを渡しているので、他のScannerを複数セットし、結果のresultのgetValueでそれぞれのScannerを元にデータを取得することができ、より短い記述で複数のMlKitを利用できるようです。

余談

ちなみに1.1.0で使用しているBarcodeAnalyzerはImageAnalysis.Analyzerであり、1.2.0-beta02で使用しているMlKitAnalyzerもImageAnalysis.Analyzerなので、 1.1.0でもCameraControllerを使用することで以下のようにstartCameraを実装できるようです。

private lateinit var cameraController: LifecycleCameraController
private fun startCamera() {
    cameraController = LifecycleCameraController(requireContext())
    cameraController.cameraSelector = DEFAULT_BACK_CAMERA
    cameraController.bindToLifecycle(viewLifecycleOwner)
    binding.previewView.controller = cameraController
    cameraController.clearImageAnalysisAnalyzer()
    cameraController.setImageAnalysisAnalyzer(
        mainThreadExecutor(),
        BarcodeAnalyzer()
    )
}

最後に

CameraXは1.0.0から1.1.0になったタイミングでPreviewViewが追加され、プレビュー画面の生成やTextureViewでの実装の場合onSurfaceTextureAvailableなどを考慮しなくて済むようになったため、更にカメラ機能の実装が容易になりました。

ただ、Camera2と比べ超高解像度が扱えないなどまだ不足している点はあるようなので、今後1.3.0やそれ以上になると更にカメラを扱いやすい機能が追加されることに期待したいです。