6

我正在尝试将 ProxyImage 从 cameraX 分析器转换为位图,以使用张量流光分析图像。所以我实现了cameraX Analyze回调,它将图像作为proxyImage。我需要将那个 proxyImage 转换为位图。如果我在 UI 线程上进行此对话,它会使相机预览滞后。所以我想用协程来做。现在的问题是,每当我将 proxyImage 传递给协程以将其转换为后台线程上的位图时,它都会因“图像已关闭”的 IllegalStateException 而崩溃。

08-04 16:28:59.690 16185-16185/com.example.camerax E/libEGL: call to OpenGL ES API with no current context (logged once per thread)
08-04 16:29:00.849 16185-16308/com.example.camerax E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-1
    Process: com.example.camerax, PID: 16185
    java.lang.IllegalStateException: Image is already closed
        at android.media.Image.throwISEIfImageIsInvalid(Image.java:68)
        at android.media.ImageReader$SurfaceImage$SurfacePlane.getBuffer(ImageReader.java:787)
        at androidx.camera.core.AndroidImageProxy$PlaneProxy.getBuffer(AndroidImageProxy.java:141)
        at com.example.camerax.MainActivity.getImageFromProxy(MainActivity.kt:216)
        at com.example.camerax.MainActivity.convertProxyImageToBitmap(MainActivity.kt:150)
        at com.example.camerax.MainActivity.access$convertProxyImageToBitmap(MainActivity.kt:38)
        at com.example.camerax.MainActivity$startCamera$3$1.invokeSuspend(MainActivity.kt:136)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:233)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)

我认为下一帧正在取消引用前一帧,而应用程序正在后台线程中将 proxyImage 转换为位图。我阅读了有关文档,他们在那里说

从该方法返回后,图像引用将关闭。因此,该方法应完成分析或制作副本,而不是将图像参考传递到分析方法之外。

我在这里感到困惑的是,当我们将图像传递到分析方法之外时,复制图像意味着什么。我如何处理这种情况。下面是代码片段。

  val imageAnalysisConfig = ImageAnalysisConfig.Builder()
            .setTargetResolution(Size(1280, 720))
            .build()
        val imageAnalysis = ImageAnalysis(imageAnalysisConfig)
        imageAnalysis.setAnalyzer { image: ImageProxy, _: Int ->

            classifier = Classifier.create(this, Classifier.Model.FLOAT, Classifier.Device.CPU, 1)

            CoroutineScope(Default).launch {
                convertProxyImageToBitmap(image)
            }
        }

将 proxyImage 转换为 Bitmap 的方法。

private fun getImageFromProxy(image: ImageProxy): Bitmap {

        val yBuffer = image.planes[0].buffer // Y
        val uBuffer = image.planes[1].buffer // U
        val vBuffer = image.planes[2].buffer // V
        val ySize = yBuffer.remaining()
        val uSize = uBuffer.remaining()
        val vSize = vBuffer.remaining()
        val nv21 = ByteArray(ySize + uSize + vSize)
        //U and V are swapped
        yBuffer.get(nv21, 0, ySize)
        vBuffer.get(nv21, ySize, vSize)
        uBuffer.get(nv21, ySize + vSize, uSize)
        val yuvImage = YuvImage(nv21, ImageFormat.NV21, image.width, image.height, null)
        val out = ByteArrayOutputStream()
        yuvImage.compressToJpeg(Rect(0, 0, yuvImage.width, yuvImage.height), 100, out)
        val imageBytes = out.toByteArray()
        return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
    }

提前感谢您的帮助。

4

2 回答 2

1

我最近遇到了这个问题。如果我是正确的,这意味着您正在使用imageafterimage.close()已经被调用。在您的情况下,它可能会在 lambda 块的末尾自动调用setAnalyzer { },但是当这种情况发生时,您仍在使用协程进行一些异步工作。

您需要完全删除协程或将其包装在一个runBlocking { }块中以等待其完成,否则错误不会消失(至少我最近是这样解决的)。如果您将背压策略设置为STRATEGY_KEEP_ONLY_LATEST,理论上您可以将所有时间都花在执行图像分析的代码块中,因为相机当前生成的帧将被丢弃而不是不必要地等待。

如果在此期间您已更新项目以使用最新的 CameraX 版本并且您正在使用专用类,请记住image.close()每次从覆盖的方法返回时始终调用自己analyze(),否则屏幕上的相机预览将永远冻结.

我在这里感到困惑的是,当我们将图像传递到分析方法之外时,复制图像意味着什么。我如何处理这种情况。

我认为在这种情况下,您需要制作 的深层副本image这意味着您创建一个新实例并将其所有内容和内部状态设置为原始实例,而不是仅对引用进行简单分配。

于 2021-01-22T00:26:16.340 回答
-4

setAnalyzer api 已更新。您现在可以在 api 中设置执行程序。你可以在那里传递你自己的处理程序。例子:

mImageAnalysis.setAnalyzer(

            CameraXExecutors.newHandlerExecutor(mBackgroundHandler), //pass your background thread handler here
            new ImageAnalysis.Analyzer() {

                @Override
                public void analyze(@NonNull ImageProxy image, int rotationDegrees) {
                    // This will be called in your background thread;
                    }
                }
            });
于 2019-08-05T04:44:39.343 回答