图像分析

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

如需了解如何将 Google 的 ML Kit 与您的 CameraX 应用集成,请参阅ML Kit Analyzer

操作模式

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

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

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

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

对于高延迟和高性能分析器,具有更长队列的阻塞模式对于补偿延迟是必要的。但请注意,应用仍然可以处理所有帧。

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

实现

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

绑定后,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. 尽快分析给定帧,最好在给定帧速率时间限制内(例如,对于 30 fps 的情况,小于 32 毫秒)。如果应用无法足够快地分析帧,请考虑支持的帧丢弃机制之一。
  2. 通过调用 ImageProxy.close()ImageProxy 释放给 CameraX。请注意,您不应调用包装的 Media.Image 的关闭函数 (Media.Image.close())。

应用可以直接使用 ImageProxy 中包装的 Media.Image。请不要在包装图像上调用 Media.Image.close(),因为这会破坏 CameraX 内部的图像共享机制;相反,请使用 ImageProxy.close() 将底层 Media.Image 释放给 CameraX。

为图像分析配置分析器

创建分析器后,使用 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 示例应用