簡體   English   中英

使用 cameraX 對同一幀進行多幅圖像分析

[英]Multiple image analyses for same frame with cameraX

我使用 Android 中的 cameraX API 在 5 到 60 秒的時間內分析多個幀。 根據用戶選擇的任務,我想對圖像執行多個條件任務。 這些包括:

  1. 掃描條形碼/二維碼(使用 google mlkit)
  2. 掃描文本(使用 google mlkit)
  3. 使用 JNI 在 C++ 中使用 openCV 進行自定義邊緣檢測
  4. 將圖像保存為 png 文件(無損)
  5. 在應用程序中顯示框架(PreviewView 或 ImageView)

這些任務在工作量和完成時間上差異很大,因此我不想等待每個任務完成直到獲得新幀,而是希望接收恆定幀並讓每個任務僅在完成最后一個工作量時才從最新幀開始。

MLKit 將 YUV 圖像作為輸入,而 openCV 使用 RGBA(或 BGRA),因此無論我選擇哪種 output 格式,我都需要以某種方式進行轉換。 My choice was to use RGBA_8888 as output format and convert it into a bitmap since bitmap is supported from both MLKit and OpenCV and the conversion from RGBA to bitmap is much quicker than from YUV to bitmap. But using bitmaps I get huge problems with memory to應用程序的擴展剛剛在 Android 之前關閉。使用 Android Studio Profiler,我注意到 ram 使用的本機部分不斷上升,即使在工作量完成並且相機未綁定后仍保持高位。

我在網上看到強烈建議在使用后回收位圖以釋放其 memory 空間。 這里的問題是所有這些任務都是獨立運行和完成的,我無法想出一個好的解決方案來盡快回收 bitmap,而不是通過將它們保留在 memory 中一段時間(比如 10 秒)來大幅增加 memory 的使用。

我考慮過為每個任務使用作業並在所有作業完成后進行回收,但這對 MLKit 分析不起作用,因為它們使用偵聽器返回,導致作業在任務實際完成之前結束。

我感謝任何關於如何有效回收位圖、使用不同於位圖的東西、減少 memory 消耗或任何代碼改進的輸入!

以下是圖像分析和條碼掃描儀的代碼示例。 它們應該足以給出運行代碼的一般概念。

val imageAnalysisBuilder =
            ImageAnalysis
                .Builder()
                .setTargetResolution(android.util.Size(720, 1280))
                .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                .setOutputImageFormat(OUTPUT_IMAGE_FORMAT_RGBA_8888)


val imageAnalysis = imageAnalysisBuilder.build()

imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor()) { imageProxy ->

            //bitmap conversion from https://github.com/android/camera-samples
            var bitmap = Bitmap.createBitmap(imageProxy.width, imageProxy.height, Bitmap.Config.ARGB_8888)
            imageProxy.use { bitmap.copyPixelsFromBuffer(it.planes[0].buffer) }

            val rotationDegrees = imageProxy.imageInfo.rotationDegrees
            imageProxy.close()

            if (!barcodeScannerBusy) {
                        CoroutineScope.launch { startMlKitBarcodeScanner(bitmap, rotationDegrees) }
            }

            if (!textRecognitionBusy) {
                        CoroutineScope.launch { startMlKitTextRecognition(bitmap, rotationDegrees) }
            }
            //more tasks with same pattern
            //when to recycle bitmap???
}
    private fun startMlKitBarcodeScanner(bitmap: Bitmap, rotationDegrees: Int) {

        barcodeScannerBusy = true

        val inputImage = InputImage.fromBitmap(bitmap, rotationDegrees)

        barcodeScanner?.process(inputImage)
            ?.addOnSuccessListener { barcodes ->
                //do stuff with barcodes
            }
            ?.addOnFailureListener {
                //failure handling
            }
            ?.addOnCompleteListener {
                barcodeScannerBusy = false
                //can't recycle bitmap here since other tasks might still use it
            }
    }

我現在解決了這個問題。 主要是通過為每個處理圖像的任務使用 bitmap 緩沖區變量。 缺點是,在最壞的情況下,我會連續多次創建相同的 bitmap。 好處是每個任務都可以獨立於任何其他任務使用自己的 bitmap。 此外,由於我使用的設備不是最強大的(事實上恰恰相反),我決定將一些任務拆分到多個分析器中,並在需要時為相機分配一個新的分析器。 此外,如果按照我在此處執行的方式多次復制 imageProxy 的平面,則需要在使用它創建新的 bitmap 之前使用 rewind() 方法。

lateinit var barcodeScannerBitmapBuffer: Bitmap
lateinit var textRecognitionBitmapBuffer: Bitmap

val imageAnalysisBuilder =
            ImageAnalysis
                .Builder()
                .setTargetResolution(android.util.Size(720, 1280))
                .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                .setOutputImageFormat(OUTPUT_IMAGE_FORMAT_RGBA_8888)


val imageAnalysis = imageAnalysisBuilder.build()

imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor()) { imageProxy ->


            if (barcodeScannerBusy && textRecognitionBusy) {
                imageProxy.close()
                return@Analyzer
            }

            if (!::barcodeScannerBitmapBuffer.isInitialized) {
                barcodeScannerBitmapBuffer = Bitmap.createBitmap(
                    imageProxy.width,
                    imageProxy.height,
                    Bitmap.Config.ARGB_8888
                )
            }

            if (!::textRecognitionBitmapBuffer.isInitialized) {
                textRecognitionBitmapBuffer = Bitmap.createBitmap(
                    imageProxy.width,
                    imageProxy.height,
                    Bitmap.Config.ARGB_8888
                )
            }

            if (!barcodeScannerBusy) {
                imageProxy.use {
                    //bitmap conversion from https://github.com/android/camera-samples
                    barcodeScannerBitmapBuffer.copyPixelsFromBuffer(it.planes[0].buffer)
                    it.planes[0].buffer.rewind()
                }
            }

            if (!textRecognitionBusy) {
                imageProxy.use { textRecognitionBitmapBuffer.copyPixelsFromBuffer(it.planes[0].buffer) }
            }

            val rotationDegrees = imageProxy.imageInfo.rotationDegrees
            imageProxy.close()

            if (::barcodeScannerBitmapBuffer.isInitialized &&!barcodeScannerBusy) {
                    startMlKitBarcodeScanner(barcodeScannerBitmapBuffer, rotationDegrees)
            }

            if (::textRecognitionBitmapBuffer.isInitialized && !textRecognitionBusy) {
                    startMlKitTextRecognition(textRecognitionBitmapBuffer, rotationDegrees)
            }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM