图像分析

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

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

操作模式

当应用程序的分析管道无法跟上 CameraX 的帧率要求时,可以将 CameraX 配置为以下列方式之一丢弃帧

  • 非阻塞(默认):在此模式下,执行程序始终将最新的图像缓存到图像缓冲区(类似于深度为一的队列),而应用程序分析前一个图像。如果 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,CameraX 是图像生产者。应用程序可以使用 ImageAnalysis.Builder 来构建 ImageAnalysis 对象。使用 ImageAnalysis.Builder,应用程序可以配置以下内容

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

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

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 的 close 函数(Media.Image.close())。

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

为 ImageAnalysis 配置您的分析器

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

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

将 ImageAnalysis 绑定到生命周期

强烈建议将您的 ImageAnalysis 与现有的 AndroidX 生命周期绑定,使用 ProcessCameraProvider.bindToLifecycle() 函数。请注意,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 的更多信息,请参阅以下其他资源。

代码实验室

  • CameraX 入门
  • 代码示例

  • CameraX 示例应用