【译】微笑,是相机![分析和扩展]

190 阅读6分钟

原文地址:magdamiu.com/2020/09/16/…

作者:MAGDA MIU

图片拍摄时的图像分析和应用扩展是用户经常访问的用例。通过使用CameraX API,我们能够以非常简单的方式实现它们。如果您想了解更多,请查看当前文章中的代码示例和这些特性背后的理论。

在我之前关于CameraX的文章中,我涵盖了以下主题:

  • 我们使用最古老的相机API所面临的挑战

  • 使用这个新API的优点是什么

  • 实现预览用例的步骤

  • 实现捕获用例的步骤

如果你想查看以前的帖子,这里有详细信息:

分析用例

用于图像处理的CPU可访问图像,计算机视觉,ML

对于每个用例,首先我们将检查实现步骤,然后检查每个步骤的分配代码。

步骤1:创建Image Analysis引用

val imageAnalysis = ImageAnalysis.Builder().build()


// the executor receives sequentially the frames
val blocking = ImageAnalysis.STRATEGY_BLOCK_PRODUCER


// the executor receives last available frame (default)
val nonBlocking = ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST


val imageAnalysis = ImageAnalysis.Builder()
    .setBackpressureStrategy(nonBlocking)
    .build()
  • Image Analysis是UseCase抽象类提供的实现之一

  • 这是创建Image Analysis类实例的最简单方法

  • Image Analysis通过Image Reader从相机获取图像

  • 这些图像可以用两种不同的方式处理:

    • 按顺序地
  • 或者只使用最后可用的帧

  • 所以实际上我们有一个反压力策略是通过使用set Backpress Policy方法添加的

  • 如果没有设置,默认策略是当执行程序接收到最后一个可用帧时

步骤2和3:定义一个自定义分析器并实现Analysis()方法

class PurpleColorAnalyser : ImageAnalysis.Analyzer {
      private var lastAnalyzedTimestamp = 0L

      private fun ByteBuffer.toByteArray(): ByteArray {
          rewind()
          val data = ByteArray(remaining())
          get(data)
          return data
      }

      override fun analyze(image: ImageProxy) {
          val currentTimestamp = System.currentTimeMillis()
          val oneSecond = TimeUnit.SECONDS.toMillis(1)
          if (currentTimestamp – lastAnalyzedTimestamp >= oneSecond) {
              val buffer = image.planes[0].buffer
              val data = buffer.toByteArray()
              val pixels = data.map { it.toInt() and 0x9370DB }
              val averagePurplePixels = pixels.average()
              Log.e("PURPLE", "Average purple pixels: $averagePurplePixels")
              lastAnalyzedTimestamp = currentTimestamp
          }
          image.close()
      }
  }
  • 在从分析()返回之前,通过调用image.close()关闭图像引用,以避免阻塞进一步图像的生成(导致预览停滞),并避免可能丢弃图像。

  • 分析图像以产生结果。

  • 该方法对来自摄像机的每个图像调用一次,并以摄像机的帧速率调用。每个分析调用被顺序执行。

步骤4:设置自定义分析器

imageAnalysis.setAnalyzer(executor, PurpleColorAnalyser())

步骤5:更新调用以绑定生命周期

// bind the image analysis use case to camera
camera = cameraProvider.bindToLifecycle(
    this as LifecycleOwner,
    cameraSelector,
    imageAnalysis,
    preview
)

图像格式

我们都知道RGB色系。它从红色、绿色和蓝色的组合中构建所有的颜色。

CameraX产生YUV_420_888格式的图像。YUV颜色编码为像素分配2个维度:

  • **亮度=**指像素的亮度=Y(灰度图像)

  • 色度=指颜色=UV

**YUV=>Y=亮度U=**蓝色的色度V=红色的色度。YUV=YCbCr

该格式是一种通用的YCbCr格式,能够描述任何4:2:0色度子采样平面或半平面缓冲区(但不是完全交错),每个颜色样本有8位。

如果我们使用Camera 2 API,在Image Format中捕获图像。YUV_420_888格式。如果我们使用旧的相机API,在Image Format中捕获图像。NV21格式。

YUV是彩色图像管道中主要使用的编码系统之一,这意味着它用于图像源(摄像机)和图像渲染器(设备的显示器)之间。这是图像处理应用程序中涉及显示器的最有效的方法之一。与RGB方案相比,传输误差减小。给定颜色的光度被分离,色调(颜色)被确定。因此,通过使用YUV,我们能够在不影响质量的情况下应用图像处理算法。

研究表明,人眼能够识别颜色的亮度,但不太擅长看到同一颜色的不同细微差别,所以这就是色度子采样的出现方式。色度子采样实际上是一种用于图像和视频的压缩技术。因此,关于图像亮度的信息比来自图像的颜色更重要。

色度子采样有3种常见方案:

  1. 4:4:4

  2. 4:2:2

  3. 4:2:0

其主要思想是降低颜色分量的分辨率,因此它在不影响人眼感知的情况下减小了图像的大小。

现在要解决的正常问题是我们保存了多少数据?!YUV_420_888我们节省50%。所以我认为这是一笔好交易。

相机控制

Camera Control提供各种异步操作,如缩放、对焦和测光,这些操作会影响当前绑定到该相机所有用例的输出。

此外,相机信息在这个等式中起着重要作用。Camera Info是一个用于检索相机信息的接口。

val cameraControl = camera.cameraControl
val cameraInfo = camera.cameraInfo

cameraInfo.torchState.observe(this, Observer { state ->
    if (state == TorchState.ON) {
       // state on
    } else {
       // state off
    }
})

扩展用例

专用API的可选效果,如HDR,肖像,夜间模式。

扩展与CameraX的Camera2核心分开。在下一个图表中,红色箭头表示当用户触发基于扩展的功能时的主要数据流,例如HDR图像捕获。

Image Capture Exender是一个抽象类,现在在api中它有5种不同的实现:

  • 加载自动效果类型的OEM(原始设备制造商)扩展实现。

  • 为美容效果类型加载OEM扩展实现。

  • 加载用于bokeh效果类型的OEM扩展实现。

  • 加载HDR效果类型的OEM扩展实现。

  • 加载夜间效果类型的OEM扩展实现。

博克效应

对于每个用例,首先我们将检查实现步骤,然后检查每个步骤的分配代码。

对于扩展来说,需要提到的一件重要事情是,它们不是在所有设备上都可用:

  • 华为(HDR,肖像): Mate 20系列,P30系列,荣誉魔法2,荣誉视图20。

  • 三星(HDR,夜间,美容,汽车):银河笔记10系列。

基于文档:“对于支持供应商扩展的设备,以下所有内容都必须为真:

  • 该效果具有来自设备OEM的库支持。

  • OEM库安装在当前设备上。

  • OEM库将设备报告为支持扩展。

  • 该设备具有库所需的操作系统版本。

您可以优先启用扩展:如果扩展既受设备支持,又在设备物理支持,那么它将被启用;否则,它会优雅地降级。"

步骤1:创建一个扩展器对象

val cameraControl = camera.cameraControl
val cameraInfo = camera.cameraInfo

cameraInfo.torchState.observe(this, Observer { state ->
    if (state == TorchState.ON) {
       // state on
    } else {
       // state off
    }
})

步骤2:启用扩展

if (beautyExtender.isExtensionAvailable(cameraSelector)) {
    beautyExtender.enableExtension(cameraSelector)
}

这就是所有人!简短回顾一下CameraX的主要优势:

如果你想了解更多关于CameraX的信息,你可以查看下一个资源:

如果有什么不清楚或有问题,请随时留下评论。如果你喜欢它,请分享!

感谢您的阅读!