Android NDK开发详解相机之图像分析
图像分析用例为您的应用提供可供 CPU 访问的图像,您可以对这些图像执行图像处理、计算机视觉或机器学习推断。应用会实现对每个帧运行的 analyze() 方法。
如需了解如何将 Google 的机器学习套件与 CameraX 应用集成,请参阅机器学习套件分析器。
操作模式
当应用的分析流水线无法满足 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 毫秒),那么上述两种操作模式均可提供顺畅的总体体验。在某些情况下,阻塞模式仍非常有用,例如在处理非常短暂的系统抖动时。
如果分析器延迟高且性能高,则需要结合使用阻塞模式和较长的队列来抵补延迟。但请注意,在这种情况下,应用仍可以处理所有帧。
如果分析器延迟高且耗时长(分析器无法处理所有帧),非阻塞模式可能更为适用,因为在这种情况下,系统必须针对分析路径进行丢帧,但要让其他同时绑定的用例仍能看到所有帧。
实现
如需在您的应用中使用图像分析,请按以下步骤操作:
构建 ImageAnalysis 用例。
创建 ImageAnalysis.Analyzer。
将分析器设为 ImageAnalysis。
将生命周期所有者、相机选择器和 ImageAnalysis 用例绑定到生命周期。
绑定后,CameraX 会立即将图像发送到已注册的分析器。 完成分析后,调用 ImageAnalysis.clearAnalyzer() 或解除绑定 ImageAnalysis 用例以停止分析。
构建 ImageAnalysis 用例
ImageAnalysis 可将分析器(图像使用方)连接到 CameraX(图像生成方)。应用可以使用 ImageAnalysis.Builder 来构建 ImageAnalysis 对象。借助 ImageAnalysis.Builder,应用可以进行以下配置:
图像输出参数:
格式:CameraX 可通过 setOutputImageFormat(int) 支持 YUV_420_888 和 RGBA_8888。默认格式为 YUV_420_888。
Resolution 和 AspectRatio:您可以设置其中一个参数,但请注意,您不能同时设置这两个值。
旋转角度。
目标名称:使用该参数进行调试。
图像流控制:
后台执行器
图像队列深度(分析器和 CamaraX 之间)
背压策略
应用可以设置分辨率或宽高比,但不能同时设置这两个值。确切的输出分辨率取决于应用请求的大小(或宽高比)和硬件功能,并可能与请求的大小或宽高比不同。如需了解分辨率匹配算法,请参阅有关 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 用例。
在分析器中,应用应执行以下操作:
尽快分析给定的帧,最好在给定的帧速率时间限制内进行分析(例如,如果帧速率为 30 fps,则用时应低于 32 毫秒)。如果应用无法足够快地分析帧,请考虑采用一种受支持的丢帧机制。
通过调用 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 绑定到生命周期
注意:该步骤适用于所有 CameraX 用例。如需详细了解绑定和生命周期自定义,请参阅 CameraX API 模型。
强烈建议您使用 ProcessCameraProvider.bindToLifecycle() 函数将 ImageAnalysis 绑定到现有的 AndroidX 生命周期。请注意,bindToLifecycle() 函数会返回选定的 Camera 设备,该函数可用于微调曝光等高级设置。如需详细了解如何控制相机输出,请参阅此指南。
以下示例结合了上述步骤中的所有操作,将 CameraX ImageAnalysis 和 Preview 用例绑定到了 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 示例应用