你怎么用camerax拍照?

How do you take a picture with camerax?

我仍在练习 Kotlin 和 Android 开发。据我所知,Camera class 已被弃用,Android 邀请使用 Camerax,因为这个高级 class 是独立于设备的,并且它们使在应用程序上实现相机的过程。

我试图阅读文档 (https://developer.android.com/training/camerax),但它写得太糟糕了,我几乎无法理解他们试图解释的内容。 所以我去阅读了文档中给出的完整示例代码 (https://github.com/android/camera-samples/tree/main/CameraXBasic)。 CameraFragment 代码长约 500 行(忽略导入和各种注释)。

我真的需要写 500 行代码来简单地拍照吗? 这怎么算是“比以前简单了”?

我的意思是,Android 编程时我只需要编写 4 行代码就可以让用户从他的存储中 select 一个图像并检索并显示它在 ImageView 中。 有没有真正简单的拍照方法,或者我真的需要停下来浪费一整天的工作来编写所有这些行代码?

编辑: 获取文档的这一页: https://developer.android.com/training/camerax/architecture#kotlin 从这段代码开始。

val preview = Preview.Builder().build()
val viewFinder: PreviewView = findViewById(R.id.previewView)

// The use case is bound to an Android Lifecycle with the following code
val camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

cameraProvider 不知从何而来。这应该是什么?我发现它是一个 ProcessCameraProvider,但我该如何初始化它呢? 它应该是一个 lateinit var 还是已经在其他地方初始化了? 因为如果我尝试写 val cameraProvider = ProcessCameraProvider() 我会得到一个错误,那我该怎么办? 什么是 cameraSelector 参数?之前没有定义。我发现它是 selector 用于前置或后置摄像头,但我应该如何通过阅读文档的那一页来了解它? 这份文件怎么能发布有这些缺陷呢? 一个人应该如何轻松学习?

在使用 CameraX 与设备的相机交互之前,您需要初始化库。初始化过程是异步的,涉及加载有关设备相机的信息。

您使用 ProcessCameraProvider 与设备的摄像头进行交互。它是一个 Singleton,所以当您第一次获得 if 的实例时,CameraX 会执行它的初始化。

val cameraProviderFuture: ListenableFuture<ProcessCameraProvider> = ProcessCameraProvider.getInstance(context)

获取 ProcessCameraProvider 单例 return 是 Future 因为它可能需要异步初始化库。第一次获取它时,可能需要一些时间(通常不到一秒),但后续调用会立即 return,因为初始化已经执行。

有了ProcessCameraProvider,您就可以开始与设备的摄像头互动了。您可以使用 CameraSelector 选择要与之交互的相机,它为您要使用的相机包装了一组滤镜。通常,如果您只是想使用主后置或前置摄像头,您会使用 CameraSelector.DEFAULT_BACK_CAMERACameraSelector.DEFAULT_FRONT_CAMERA.

现在您已经定义了要使用的相机,您可以构建所需的用例。例如,您想要拍照,因此您将使用 ImageCapture 用例。它允许使用相机拍摄单个捕获帧(通常是高质量帧),并将其作为原始缓冲区提供,或将其存储在文件中。要使用它,您可以根据需要对其进行配置,或者您可以让 CameraX 使用默认配置。

val imageCapture = ImageCapture.Builder().build()

在 CameraX 中,相机的生命周期由 LifecycleOwner 控制,这意味着当 LifecycleOwner 的生命周期开始时,相机打开,当它停止时,相机关闭。因此,您需要选择一个可以控制相机的生命周期。如果您使用 Activity,您通常希望相机在 Activity 启动时启动,并在它停止时停止,因此您将使用 Activity 实例本身作为LifecycleOwner,如果您使用的是 Fragment,您可能希望使用它的视图生命周期 (Fragment.getViewLifecycleOwner())。

最后,您需要将拼图的各个部分拼在一起。

processCameraProvider.bindToLifecycle(
   lifecycleOwner,
   cameraSelector,
   imageCapture
)

应用程序通常包含一个显示相机预览的取景器,因此您可以使用 Preview 用例,并将其与 ImageCapture 用例绑定。 Preview 用例允许将相机帧流式传输到 Surface。由于设置 Surface 并在其上正确绘制预览可能很复杂,CameraX 提供了 PreviewView,一个 View 可以与 Preview 用例一起使用来显示相机预览。您可以查看如何使用它们 here.

// Just like ImageCapture, you can configure the Preview use case if you'd wish.
val preview = Preview.Builder().build()

// Provide PreviewView's Surface to CameraX. The preview will be drawn on it.
val previewView: PreviewView = findViewById(...)
preview.setSurfaceProvider(previewView.surfaceProvider)

// Bind both the Preview and ImageCapture use cases
processCameraProvider.bindToLifecycle(
   lifecycleOwner,
   cameraSelector,
   imageCapture,
   preview
)

现在要真正拍照,您可以使用 ImageCapturetakePicture 方法之一。一个提供捕获图像的 JPEG 原始缓冲区,另一个将其保存在您提供的文件中(如果需要,请确保您具有必要的存储权限)。

imageCapture.takePicture(
   ContextCompat.getMainExecutor(context), // Defines where the callbacks are run
   object : ImageCapture.OnImageCapturedCallback() {
      override fun onCaptureSuccess(imageProxy: ImageProxy) {
         val image: Image = imageProxy.image // Do what you want with the image
         imageProxy.close() // Make sure to close the image
      }

      override fun onError(exception: ImageCaptureException) {
         // Handle exception
      }
   }
)
val imageFile = File("somePath/someName.jpg") // You can store the image in the cache for example using `cacheDir.absolutePath` as a path.
val outputFileOptions = ImageCapture.OutputFileOptions
      .Builder(imageFile)
      .build()
takePicture(
   outputFileOptions,
   CameraXExecutors.mainThreadExecutor(),
   object : ImageCapture.OnImageSavedCallback {
      override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
                }

      override fun onError(exception: ImageCaptureException) {
      }
   }
)

Do I really need to write 500 lines of code to simply take a picture? How is this supposed to be considered "simpler than before"?

CameraXBasic 并不像其名称所暗示的那样“基本”x) 它更像是 CameraX 的 3 个用例的完整示例。尽管 CameraFragment 很长,但它很好地解释了事情,因此每个人都更容易理解。

CameraX“比以前更简单”,之前主要指的是Camera2,起码上手有点难度。 CameraX 通过其使用用例的方法提供了对开发人员更友好的 API。它还处理兼容性,这在以前是个大问题。确保您的相机应用在大多数 Android 设备上可靠地工作非常具有挑战性。