CameraX 入门指南   您所在的位置:网站首页 谷歌相机apk官网 CameraX 入门指南  

CameraX 入门指南  

2024-07-11 12:06| 来源: 网络整理| 查看: 265

1. 准备工作

在本 Codelab 中,您将学习如何使用 CameraX 创建相机应用,以显示取景器、拍照并分析相机的图像流。

为了实现此目标,我们将引入 CameraX 中的用例概念,您可在多个操作中应用此概念,包括显示取景器和实时分析画面帧等。

先决条件 基本的 Android 开发体验。 您需要执行的操作 了解如何添加 CameraX 依赖项。 了解如何在 Activity 中显示相机预览内容。(Preview 用例) 构建可以拍照并将照片保存在存储空间中的应用。(ImageCapture 用例) 了解如何实时分析相机中的画面帧。(ImageAnalysis 用例) 您需要用到的工具 一台 Android 设备,也可以使用 Android Studio 模拟器。我们建议使用搭载 Android 11 或更高版本的 AVD。 受支持的最低 API 级别为 21。 Android Studio 3.6 或更高版本。 2. 创建项目 使用 Android Studio 菜单,新建项目并在收到系统提示时选择*"Empty Activity"* (空 Activity)。

ddc817a03892e44.png

下一步,将应用命名为"CameraX App"。确保将语言设置为 Kotlin、将最低 API 级别设为 21(对于 CameraX,这是所需的最低级别),并确保您使用 AndroidX 工件。

2383e490b6550aed.png

添加 Gradle 依赖项 打开 build.gradle(Module: app) 文件并将 CameraX 依赖项添加到应用 Gradle 文件中的"依赖项"部分内: def camerax_version = "1.0.0-beta07" // CameraX core library using camera2 implementation implementation "androidx.camera:camera-camera2:$camerax_version" // CameraX Lifecycle Library implementation "androidx.camera:camera-lifecycle:$camerax_version" // CameraX View class implementation "androidx.camera:camera-view:1.0.0-alpha14" CameraX 需要用到 Java 8 中的一些方法,因此我们需要对编译选项进行相应设置。 在 android 块末尾,紧跟 buildTypes 的位置添加以下内容: compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } 如果尚未添加此插件,请在最上方进行添加。 apply plugin: 'kotlin-android-extensions'

收到系统提示时,点击 Sync Now(立即同步),这样我们便做好了在应用中使用 CameraX 的准备。

创建取景器布局

使用以下代码替换默认布局:

打开 activity_main 布局文件并将其替换为以下代码。 设置 MainActivity.kt 将 MainActivity.kt 中的代码替换为此内容。其中将包含输入语句、将会实例化的变量、将实现的函数以及常量。

系统已实现 onCreate(),可便于您检查相机权限、启动相机、为拍照按钮设置 onClickListener(),并实现 outputDirectory 和 cameraExecutor。即使您已实现了 onCreate(),相机也不会工作,直到您在文件中实施了这些方法为止。

import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.Manifest import android.content.pm.PackageManager import android.net.Uri import android.util.Log import android.widget.Toast import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import java.util.concurrent.Executors import androidx.camera.core.* import androidx.camera.lifecycle.ProcessCameraProvider import kotlinx.android.synthetic.main.activity_main.* import java.io.File import java.nio.ByteBuffer import java.text.SimpleDateFormat import java.util.* import java.util.concurrent.ExecutorService typealias LumaListener = (luma: Double) -> Unit class MainActivity : AppCompatActivity() { private var imageCapture: ImageCapture? = null private lateinit var outputDirectory: File private lateinit var cameraExecutor: ExecutorService override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Request camera permissions if (allPermissionsGranted()) { startCamera() } else { ActivityCompat.requestPermissions( this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS) } // Set up the listener for take photo button camera_capture_button.setOnClickListener { takePhoto() } outputDirectory = getOutputDirectory() cameraExecutor = Executors.newSingleThreadExecutor() } private fun takePhoto() {} private fun startCamera() {} private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all { ContextCompat.checkSelfPermission( baseContext, it) == PackageManager.PERMISSION_GRANTED } private fun getOutputDirectory(): File { val mediaDir = externalMediaDirs.firstOrNull()?.let { File(it, resources.getString(R.string.app_name)).apply { mkdirs() } } return if (mediaDir != null && mediaDir.exists()) mediaDir else filesDir } override fun onDestroy() { super.onDestroy() cameraExecutor.shutdown() } companion object { private const val TAG = "CameraXBasic" private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" private const val REQUEST_CODE_PERMISSIONS = 10 private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA) } } 运行代码。应用的界面应如下所示:

86b7a9ccc8e13fb0.png

3. 请求相机权限

在应用打开相机前,其需要用户授予执行此操作的权限。在此步骤中,您将用到相机权限。

打开 AndroidManifest.xml 并在 application 标记之前添加以下行。

添加用于确保设备配备有相机的 android.hardware.camera.any。指定 .any,用以表示相机可以是前置摄像头或后置摄像头。

如果您使用不带 .any 的 android.hardware.camera,则在您使用没有后置摄像头的设备(例如,大多数 Chromebook)的情况下,此类将无法工作。在第二行中添加对于该相机的访问权限。

将此代码复制到 MainActivity.kt. 中。下方的项目符号点将把您刚才复制的代码分为几个部分。 override fun onRequestPermissionsResult( requestCode: Int, permissions: Array, grantResults: IntArray) { if (requestCode == REQUEST_CODE_PERMISSIONS) { if (allPermissionsGranted()) { startCamera() } else { Toast.makeText(this, "Permissions not granted by the user.", Toast.LENGTH_SHORT).show() finish() } } } 检查请求代码是否正确;如果此代码不正确,请将其忽略。 if (requestCode == REQUEST_CODE_PERMISSIONS) { } 如果已授予权限,系统则会调用 startCamera()。 if (allPermissionsGranted()) { startCamera() } 如果未授予权限,系统则会显示一个消息框,告知用户未授予权限。 else { Toast.makeText(this, "Permissions not granted by the user.", Toast.LENGTH_SHORT).show() finish() } 运行应用。

应用现在应该会请求相机使用权限:

885896e144926d4e.png

4. 实现预览用例

在相机应用中,用户可借助取景器预览他们要拍摄的照片。您可以使用 CameraX Preview 类实现取景器功能。

如要使用 Preview,您首先需要定义配置,然后使用该配置创建用例的实例。所生成的实例是您要绑定到 CameraX 生命周期的内容。

将此代码复制到 startCamera() 函数中。

下方的项目符号点将把您刚才复制的代码分为几个部分。

private fun startCamera() { val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener(Runnable { // Used to bind the lifecycle of cameras to the lifecycle owner val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() // Preview val preview = Preview.Builder() .build() .also { it.setSurfaceProvider(viewFinder.createSurfaceProvider()) } // Select back camera as a default val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA try { // Unbind use cases before rebinding cameraProvider.unbindAll() // Bind use cases to camera cameraProvider.bindToLifecycle( this, cameraSelector, preview) } catch(exc: Exception) { Log.e(TAG, "Use case binding failed", exc) } }, ContextCompat.getMainExecutor(this)) } 创建 ProcessCameraProvider 的实例。此实例用于将相机的生命周期绑定到生命周期所有者。由于 CameraX 具有生命周期感知能力,所以这样可以省去打开和关闭相机的任务。 val cameraProviderFuture = ProcessCameraProvider.getInstance(this) 向 cameraProviderFuture 中添加监听器。添加 Runnable 作为参数。我们将稍后为其填入数值。添加 ContextCompat.getMainExecutor() 作为第二个参数。这将返回在主线程上运行的 Executor。 cameraProviderFuture.addListener(Runnable {}, ContextCompat.getMainExecutor(this)) 在 Runnable 中,添加 ProcessCameraProvider。此类用于将相机的生命周期绑定到应用进程内的 LifecycleOwner。 val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() 初始化您的 Preview 对象,在该对象上调用 build,从取景器中获取表面提供程序,然后在预览中进行设置。 val preview = Preview.Builder() .build() .also { it.setSurfaceProvider(viewFinder.createSurfaceProvider()) } 创建 CameraSelector 对象并选择 DEFAULT_BACK_CAMERA。 val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA 创建 try 块。在该块中,确保任何内容都未绑定到您的 cameraProvider,然后将您的 cameraSelector 和预览对象绑定到 cameraProvider。 try { cameraProvider.unbindAll() cameraProvider.bindToLifecycle( this, cameraSelector, preview) } 在少数情况下,此代码会失败,例如应用不再处于焦点中。将此代码放入 catch 块中,以记录是否存在失败情况。 catch(exc: Exception) { Log.e(TAG, "Use case binding failed", exc) } 运行应用。您应会看到相机预览!

392602bf3da0a336.png

5. 实现 ImageCapture 用例

其他用例的运行方式非常类似于 Preview。首先,定义用于对实际用例对象进行实例化的配置对象。为了拍摄照片,您需要采用 takePhoto() 方法,当用户按下 **Take photo(拍摄照片)**按钮时,系统会调用此方法。

将此代码复制到 takePhoto() 方法中。

下方的项目符号点将把您刚才复制的代码分为几个部分。

private fun takePhoto() { // Get a stable reference of the modifiable image capture use case val imageCapture = imageCapture ?: return // Create time-stamped output file to hold the image val photoFile = File( outputDirectory, SimpleDateFormat(FILENAME_FORMAT, Locale.US ).format(System.currentTimeMillis()) + ".jpg") // Create output options object which contains file + metadata val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build() // Set up image capture listener, which is triggered after photo has // been taken imageCapture.takePicture( outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback { override fun onError(exc: ImageCaptureException) { Log.e(TAG, "Photo capture failed: ${exc.message}", exc) } override fun onImageSaved(output: ImageCapture.OutputFileResults) { val savedUri = Uri.fromFile(photoFile) val msg = "Photo capture succeeded: $savedUri" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.d(TAG, msg) } }) } 首先,获取对 ImageCapture 用例的引用。如果用例为 null,则退出函数。如果您在设置拍摄图像之前点按拍照按钮,则这将为 null。如果没有 return 语句,则在用例为 null 的情况下,应用会崩溃。 val imageCapture = imageCapture ?: return 接下来,创建一个容纳图像的文件。添加时间戳,以避免文件名重复。 val photoFile = File( outputDirectory, SimpleDateFormat(FILENAME_FORMAT, Locale.US ).format(System.currentTimeMillis()) + ".jpg") 创建 OutputFileOptions 对象。您可以在此对象中指定有关输出方式的设置。如果您希望将输出内容保存在刚创建的文件中,则添加您的 photoFile。 val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build() 对 imageCapture 对象调用 takePicture()。传入执行程序 outputOptions 以及在保存图像时使用的回调。接下来,您将填写回调。 imageCapture.takePicture( outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback {} ) 在图像拍摄失败或图像拍摄结果保存失败的情况下,添加一个错误示例,以记录失败情况。 override fun onError(exc: ImageCaptureException) { Log.e(TAG, "Photo capture failed: ${exc.message}", exc) } 如果拍摄未失败,则表示拍照成功!将照片保存到您先前创建的文件中,显示一个消息框以告知用户操作成功,然后输出日志语句。 override fun onImageSaved(output: ImageCapture.OutputFileResults) { val savedUri = Uri.fromFile(photoFile) val msg = "Photo capture succeeded: $savedUri" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.d(TAG, msg) } 使用 startCamera() 方法并将此代码复制到用于预览的代码之下。 imageCapture = ImageCapture.Builder() .build() 最后,更新对 try 块中对 bindToLifecycle() 的调用以加入新的用例: cameraProvider.bindToLifecycle( this, cameraSelector, preview, imageCapture)

此时,该方法将如下所示:

private fun startCamera() { val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener(Runnable { // Used to bind the lifecycle of cameras to the lifecycle owner val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() // Preview val preview = Preview.Builder() .build() .also { it.setSurfaceProvider(viewFinder.createSurfaceProvider()) } imageCapture = ImageCapture.Builder() .build() // Select back camera as a default val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA try { // Unbind use cases before rebinding cameraProvider.unbindAll() // Bind use cases to camera cameraProvider.bindToLifecycle( this, cameraSelector, preview, imageCapture) } catch(exc: Exception) { Log.e(TAG, "Use case binding failed", exc) } }, ContextCompat.getMainExecutor(this)) } 重新运行应用并按 Take Photo(拍照)。 屏幕上会出现一个消息框,其中会显示一条日志消息。

d59fc4ad1241154f.png

查看照片 检查日志语句。您将看到一条日志消息,其会通知您照片已拍摄成功。 2020-04-24 15:13:26.146 11981-11981/com.example.cameraxapp D/CameraXBasic: Photo capture succeeded: file:///storage/emulated/0/Android/media/com.example.cameraxapp/CameraXApp/2020-04-24-15-13-25-746.jpg 复制用于存储照片的文件,并省略 file:// prefix。 /storage/emulated/0/Android/media/com.example.cameraxapp/CameraXApp/2020-04-24-15-13-25-746.jpg 在 Android Studio 终端中,运行以下命令: adb shell cp [INSERT THE FILE FROM STEP 2 HERE] /sdcard/Download/photo.jpg 运行此 ADB 命令,然后退出 shell: adb pull /sdcard/Download/photo.jpg 您可以查看保存在当前文件夹的 photo.jpg 文件内的照片。

如果您需要实现简单的相机应用,则已达到目的。就是这么简单!如果您想要实现图像分析程序,请继续阅读!

6. 使用 ImageAnalysis 用例

**如果您运行的是 Android 10 或更低版本,则实现预览、图像拍摄和图像分析功能当前不适用于 Android Studio 的设备模拟器。**我们建议使用实体设备来测试 Codelab 的这个部分。

如要让您的相机应用变得更加有趣,使用 ImageAnalysis 功能不妨是一种好方法。 通过此功能,您可以定义用于实现 ImageAnalysis.Analyzer 接口的自定义类,该接口将使用传入的相机帧来进行调用。您不必管理相机会话状态,甚至无须处理图像,只需将其绑定到应用的预期生命周期即可,就像其他 具有生命周期感知能力的组件一样。

添加此分析程序,将其作为 MainActivity.kt 中的内部类。 此分析程序可用于记录图像的平均亮度。如要创建分析程序,您需要替换类中的 analyze 函数,该函数可用于负责实现 ImageAnalysis.Analyzer 界面。 private class LuminosityAnalyzer(private val listener: LumaListener) : ImageAnalysis.Analyzer { private fun ByteBuffer.toByteArray(): ByteArray { rewind() // Rewind the buffer to zero val data = ByteArray(remaining()) get(data) // Copy the buffer into a byte array return data // Return the byte array } override fun analyze(image: ImageProxy) { val buffer = image.planes[0].buffer val data = buffer.toByteArray() val pixels = data.map { it.toInt() and 0xFF } val luma = pixels.average() listener(luma) image.close() } }

通过使用我们的类实现 ImageAnalysis.Analyzer 界面,我们只需在 ImageAnalysis, 中对 LuminosityAnalyzer 的实例进行实例化(类似于其他用例),并再次更新 startCamera() 函数,然后再调用 CameraX.bindToLifecycle() 即可:

在 startCamera() 方法中,将此代码添加到 imageCapture() 代码下。 val imageAnalyzer = ImageAnalysis.Builder() .build() .also { it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma -> Log.d(TAG, "Average luminosity: $luma") }) } 更新 cameraProvider 中的 bindToLifecycle() 调用,以加入 imageAnalyzer。 cameraProvider.bindToLifecycle( this, cameraSelector, preview, imageCapture, imageAnalyzer)

完整方法现如下所示:

private fun startCamera() { val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener(Runnable { // Used to bind the lifecycle of cameras to the lifecycle owner val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() // Preview val preview = Preview.Builder() .build() .also { it.setSurfaceProvider(viewFinder.createSurfaceProvider()) } imageCapture = ImageCapture.Builder() .build() val imageAnalyzer = ImageAnalysis.Builder() .build() .also { it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma -> Log.d(TAG, "Average luminosity: $luma") }) } // Select back camera as a default val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA try { // Unbind use cases before rebinding cameraProvider.unbindAll() // Bind use cases to camera cameraProvider.bindToLifecycle( this, cameraSelector, preview, imageCapture, imageAnalyzer) } catch(exc: Exception) { Log.e(TAG, "Use case binding failed", exc) } }, ContextCompat.getMainExecutor(this)) } 现在运行应用!应用大约每秒会在 logcat 中生成一条类似于以下内容的消息。 D/CameraXApp: Average luminosity: ... 7. 恭喜!

您已成功地从零开始,在新的 Android 应用中实现了以下内容:

已将 CameraX 依赖项加入到您的项目中。 已显示相机取景器(使用 Preview 用例) 已能够拍摄照片,并可将图像保存到存储空间(使用 ImageCapture 用例) 已实现对来自相机的画面帧进行实时分析(使用 ImageAnalysis 用例)

如果您有兴趣了解更多有关 CameraX 的内容以及您可以用其完成的事情,请查看 文档或克隆 官方示例。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有