图像分析

图像分析 用例中,您的应用会获得一个 CPU 可访问的图像,您可以在其上执行图像处理、计算机视觉或机器学习推断。应用实现了一个 analyze() 方法,该方法在每一帧上运行。

要了解如何将 Google 的 ML Kit 集成到您的 CameraX 应用中,请参阅 ML Kit 分析器

操作模式

当应用程序的分析管道无法满足 CameraX 的帧速率要求时,可以配置 CameraX 以以下方式丢帧

  • 非阻塞(默认):在此模式下,执行器始终将最新图像缓存到图像缓冲区(类似于深度为 1 的队列),同时应用程序分析之前的图像。如果 CameraX 在应用程序完成处理之前收到新图像,则将新图像保存到同一缓冲区,覆盖之前的图像。请注意,ImageAnalysis.Builder.setImageQueueDepth() 在此场景中无效,缓冲区内容始终会被覆盖。可以通过调用 setBackpressureStrategy() 以及 STRATEGY_KEEP_ONLY_LATEST 来启用此非阻塞模式。有关执行器影响的更多信息,请参阅 STRATEGY_KEEP_ONLY_LATEST 的参考文档。

  • 阻塞:在此模式下,内部执行器可以将多个图像添加到内部图像队列,并且仅当队列已满时才开始丢帧。阻塞发生在整个摄像机设备范围内:如果摄像机设备绑定了多个用例,则所有用例在 CameraX 处理这些图像时都将被阻塞。例如,当预览和图像分析都绑定到摄像机设备时,预览也会在 CameraX 处理图像时被阻塞。可以通过将 STRATEGY_BLOCK_PRODUCER 传递给 setBackpressureStrategy() 来启用阻塞模式。还可以使用 ImageAnalysis.Builder.setImageQueueDepth() 来配置图像队列深度。

对于总分析图像时间小于 CameraX 帧持续时间(例如,60fps 时为 16ms)的低延迟高性能分析器,两种操作模式都能提供流畅的整体体验。在某些情况下,阻塞模式仍然很有用,例如处理短暂的系统抖动时。

对于高延迟高性能分析器,需要使用更长的队列的阻塞模式来弥补延迟。但是请注意,应用程序仍然可以处理所有帧。

对于高延迟且耗时的分析器(分析器无法处理所有帧),非阻塞模式可能是更合适的选择,因为必须为分析路径丢弃帧,但其他并发绑定的用例仍然可以看到所有帧。

实现

要在应用程序中使用图像分析,请按照以下步骤操作

绑定后,CameraX 会立即将图像发送到您注册的分析器。分析完成后,请调用 ImageAnalysis.clearAnalyzer() 或取消绑定 ImageAnalysis 用例以停止分析。

构建 ImageAnalysis 用例

ImageAnalysis 将您的分析器(图像消费者)连接到 CameraX(图像生产者)。应用程序可以使用 ImageAnalysis.Builder 来构建 ImageAnalysis 对象。使用 ImageAnalysis.Builder,应用程序可以配置以下内容

应用程序可以设置分辨率或纵横比,但不能同时设置两者。确切的输出分辨率取决于应用程序请求的大小(或纵横比)和硬件功能,并且可能与请求的大小或比率不同。有关分辨率匹配算法的信息,请参阅 setTargetResolution() 的文档

应用程序可以配置输出图像像素为 YUV(默认)或 RGBA 色彩空间。当设置 RGBA 输出格式时,CameraX 在内部将图像从 YUV 转换为 RGBA 色彩空间,并将图像位打包到 ImageProxy 的第一个平面(其他两个平面未使用)的 ByteBuffer 中,按照以下顺序

ImageProxy.getPlanes()[0].buffer[0]: alpha
ImageProxy.getPlanes()[0].buffer[1]: red
ImageProxy.getPlanes()[0].buffer[2]: green
ImageProxy.getPlanes()[0].buffer[3]: blue
...

在执行设备无法跟上帧速率的复杂图像分析时,可以使用本主题的 操作模式 部分中描述的策略来配置 CameraX 以丢帧。

创建您的分析器

应用程序可以通过实现 ImageAnalysis.Analyzer 接口并覆盖 analyze(ImageProxy image) 来创建分析器。在每个分析器中,应用程序都会收到一个 ImageProxy,它是 Media.Image 的包装器。可以使用 ImageProxy.getFormat() 查询图像格式。格式是应用程序使用 ImageAnalysis.Builder 提供的以下值之一

  • 如果应用程序请求 OUTPUT_IMAGE_FORMAT_RGBA_8888,则为 ImageFormat.RGBA_8888
  • 如果应用程序请求 OUTPUT_IMAGE_FORMAT_YUV_420_888,则为 ImageFormat.YUV_420_888

有关颜色空间配置以及像素字节的检索位置,请参阅 构建 ImageAnalysis 用例

在分析器内部,应用程序应执行以下操作

  1. 尽快分析给定帧,最好在给定的帧速率时间限制内(例如,对于 30fps,小于 32ms)。如果应用程序无法足够快地分析帧,请考虑使用 支持的帧丢弃机制 之一。
  2. 通过调用 ImageProxy.close()ImageProxy 释放回 CameraX。请注意,不应调用包装的 Media.Image 的关闭函数 (Media.Image.close())。

应用程序可以直接使用 ImageProxy 内部的包装的 Media.Image。只需不要对包装的图像调用 Media.Image.close(),因为这会破坏 CameraX 内部的图像共享机制;而应使用 ImageProxy.close() 将底层 Media.Image 释放回 CameraX。

为 ImageAnalysis 配置您的分析器

创建分析器后,使用 ImageAnalysis.setAnalyzer() 将其注册以开始分析。分析完成后,使用 ImageAnalysis.clearAnalyzer() 删除注册的分析器。

只能为图像分析配置一个活动分析器。调用 ImageAnalysis.setAnalyzer() 会替换已存在的注册分析器。应用程序可以在任何时间设置新的分析器,在绑定用例之前或之后。

将 ImageAnalysis 绑定到生命周期

强烈建议使用 ProcessCameraProvider.bindToLifecycle() 函数将您的 ImageAnalysis 绑定到现有的 AndroidX 生命周期。请注意,bindToLifecycle() 函数返回选定的 Camera 设备,该设备可用于微调高级设置,例如曝光等。有关控制摄像机输出的更多信息,请参阅 本指南

以下示例将前面步骤中的所有内容组合在一起,将 CameraX ImageAnalysisPreview 用例绑定到 lifeCycle 所有者

Kotlin

val imageAnalysis = ImageAnalysis.Builder()
    // enable the following line if RGBA output is needed.
    // .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
    .setTargetResolution(Size(1280, 720))
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
    .build()
imageAnalysis.setAnalyzer(executor, ImageAnalysis.Analyzer { imageProxy ->
    val rotationDegrees = imageProxy.imageInfo.rotationDegrees
    // insert your code here.
    ...
    // after done, release the ImageProxy object
    imageProxy.close()
})

cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, imageAnalysis, preview)

Java

ImageAnalysis imageAnalysis =
    new ImageAnalysis.Builder()
        // enable the following line if RGBA output is needed.
        //.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
        .setTargetResolution(new Size(1280, 720))
        .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
        .build();

imageAnalysis.setAnalyzer(executor, new ImageAnalysis.Analyzer() {
    @Override
    public void analyze(@NonNull ImageProxy imageProxy) {
        int rotationDegrees = imageProxy.getImageInfo().getRotationDegrees();
            // insert your code here.
            ...
            // after done, release the ImageProxy object
            imageProxy.close();
        }
    });

cameraProvider.bindToLifecycle((LifecycleOwner) this, cameraSelector, imageAnalysis, preview);

其他资源

要了解有关 CameraX 的更多信息,请参阅以下其他资源。

Codelab

  • CameraX 入门
  • 代码示例

  • CameraX 示例应用程序