Search code examples
kotlinfirebase-mlkitandroid-camerax

Camerax Analyzer ImageProxy - Image Already Closed


I am using CameraX (Beta) and using Analyzer with an executor to analyze the image and draw bounds/overlay on it. Based on the documentation, I need to close the image proxy to continue to get the images into the analyze function. When I add the imageProxy.close() call, it fails with Image Already closed error. What am I missing here? Analyzer Code:

       private val successListener = OnSuccessListener<List<DetectedObject>> { papers ->
        isAnalyzing.set(false)

        val  rectPoints= mutableListOf<Rect>()
        Log.d(TAG," overlayRef Info: ${overlayRef.get()}")

        for (paper in papers) {
            val bounds = paper.boundingBox
            val paperId = paper.trackingId
            rectPoints+=bounds


            Log.d(TAG, "Successful Paper Analysis - Bounds of the paper: $bounds")
            Log.d(TAG," Labels found on the paper: ${paper.labels}")

        }
        Log.d(TAG, "Invoking pointsRectListener for : $rectPoints")
        pointsRectListener?.invoke(rectPoints)

    }
    private val failureListener = OnFailureListener { e ->
        isAnalyzing.set(false)
        Log.e(TAG, "Paper analysis failure.", e)
    }

    @SuppressLint("UnsafeExperimentalUsageError")
    override fun analyze(imageProxy: ImageProxy) {
        val mediaImage = imageProxy?.image ?: return
        Log.d(TAG,"entered analysis..analysis in progress?$isAnalyzing.get()")


        if (!isAnalyzing.get()){
            isAnalyzing.set(true)
            Log.d(TAG,"No other analysis in progress..so starting analysis now")
            val currentTimestamp = System.currentTimeMillis()
            if (currentTimestamp - lastAnalyzedTimestamp >= TimeUnit.SECONDS.toMillis(1)) {
                currentTimestamp - lastAnalyzedTimestamp
                    analysisSizeListener?.invoke(Size(imageProxy.width, imageProxy.height))

                    val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)

                    objectDetector.process(image)
                            .addOnSuccessListener(successListener)
                            .addOnFailureListener(failureListener)

            }

        }
        imageProxy.close()


    }

Code where I am instantiating and binding to lifecycle

        paperAnalyzer=ImageAnalysis.Builder()
                .setTargetAspectRatio(screenAspectRatio)
                .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                .setTargetRotation(rotation)
                .build()
                .also {
                    it.setAnalyzer(cameraExecutor, PaperAnalyzer( WeakReference(overlayView)).apply {
                        pointsRectListener = { rectPoints ->
                            overlayView.rectPoints = rectPoints
                        }
                        analysisSizeListener = {
                            updateOverlayTransform(overlayView, it)
                        }
                    }


                      )

                }

        cameraProvider.unbindAll()

        try {
            camera = cameraProvider.bindToLifecycle(
                    this, cameraSelector, preview, imageCapture, paperAnalyzer)

            // Attach the viewfinder's surface provider to preview use case
            preview?.setSurfaceProvider(viewFinder.createSurfaceProvider())
        } catch (exc: Exception) {
            Log.e(TAG, "Use case binding failed", exc)
        }

Solution

  • It seems like the logic you're trying to accomplish looks like this.

    fun analyze(imageProxy) {
        if (isAnalyzing) {
            imageProxy.close() // 1
        } else {
            val image = imageInput.fromMediaImage(...)
            objectDetector.process(image)
                        .addOnSuccessListener(OnSuccessListener { result ->
                            // Do something with the result
                            imageProxy.close() // 2
                            isAnalyzing.set(false)
                        })
                        .addOnFailureListener(OnFailureListener { exception ->
                            // Do something with the exception
                            imageProxy.close() // 3
                            isAnalyzing.set(false)
                        })
        }
    }
    

    I might be wrong, but it looks like you're doing 1, but not 2 and 3. Could you update your code to follow this pattern and see if you're still encountering the issue.