和风网标志

CameraX:在Android上简化摄影!

日期:


Google I/O 2019 的回归……

Google 今年在 Google I/O 2019 上宣布的最激动人心的变化之一是 CameraX. 它是一个 API,将带来大量新功能,并且据说可以更轻松地在 Android 上实现相机功能。

但这怎么比现在的好 Camera2 API? CameraX 真的有很多东西可以提供还是只是一个噱头?

让我们在这篇文章中找出答案。 但首先,让我们来看看如今 Camera2 API 是如何在应用程序中实现的。

备注:本 Camera2 API,它取代了传统 相机 API,让您更好地控制设备的摄像头。 但请记住,Camera2 API 支持 Lollipop 5.0 及更高版本,因此如果您想支持低于该版本的设备,则必须坚持使用旧版 Camera API。

(现在已经过时了?)Camera2 API

在本文中,我们将重点介绍在 Android 中使用自定义相机 UI 拍摄照片。 我们将省略视频,因为 Android 中的视频实现需要单独的文章。

让我们看一下使用 Camera2 API 拍摄照片的基本实现。

首先,我们必须在布局中设置 TextureView 和 Button 来捕获图像:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <TextureView android:id="@+id/texture_view" android:layout_width="match_parent" android:layout_height="match_parent" … /> <Button android:id="@+id/btn_capture" android:layout_width="wrap_content" android:layout_height="wrap_content" … />
</RelativeLayout>

然后,我们必须设置一个相机预览,如下所示:

try { val texture = texture_view.getSurfaceTexture() texture.setDefaultBufferSize(imageDimension.getWidth(), imageDimension.getHeight()) val surface = Surface(texture) captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) captureRequestBuilder.addTarget(surface) cameraDevice.createCaptureSession(Arrays.asList(surface), object:CameraCaptureSession.StateCallback() { fun onConfigured(@NonNull cameraCaptureSession:CameraCaptureSession) { if (null == cameraDevice) { return } cameraCaptureSessions = cameraCaptureSession updatePreview() } fun onConfigureFailed(@NonNull cameraCaptureSession:CameraCaptureSession) { Toast.makeText(this@AndroidCameraApi, "Configuration changed!", Toast.LENGTH_SHORT).show() } }, null)
} catch (e:CameraAccessException) { e.printStackTrace()
}

我们仍然需要:

  • 获取实例 CameraManager
  • 获取 CameraCharacteristicsStreamConfigurationMap
  • 设置一个 CameraDevice.StateCallbackCameraCaptureSession.CaptureCallback 分别用于图像预览和图像捕获
  • 设置 TextureView.SurfaceTextureListener 用于预览 TextureView
  • 打开照相机
  • 写拍照的代码
  • 编写更新预览的代码
  • 不使用时关闭相机

呸! 听起来工作量很大! 我不想在本文的其余部分继续讨论如何实现 Camera2 API。 因此,让我们在这里简短地讨论一下为什么在 Android 应用程序中实现 Camera2 API 是一件非常痛苦的事情:

  • 它需要一个 代码,正如您可以从上面推断的那样。 如果您不熟悉在 Android 中实现相机,这是一个巨大的缺点。
  • 这太复杂了。 API 中的许多代码可以简化,但由于 API 旨在提供对摄像头的更多控制,因此对于刚在其应用程序中实现摄像头的开发人员来说太难理解了。
  • 它需要你实现很多状态,当这些状态发生变化时,你需要执行一堆特定的方法来处理。 您可以查看详尽的州列表 相关信息.
  • 它还在相机的手电筒部分引入了一些错误。 关于 Camera2 中“手电筒”模式和“闪光灯”模式之间的区别,存在很多混淆。
  • 如果这还不足以驱动 Camera2 的新开发人员,那么有很多特定于供应商的错误需要修复,因此需要更多代码。 这些也称为设备兼容性问题,需要开发人员编写特定于设备的代码来管理解决方案的变通方法。

虽然旧版 Camera API 更易于使用并支持较低的 Android OS 设备,但它并没有提供对相机的大量控制。 它也不缺少自己的错误和特定于供应商的问题。 它与 Camera2 API 有许多相同的不可预测的问题,因此在您的自定义相机中实现并不是一个好主意。

考虑到这些障碍,谷歌推出了新的 CameraX API 来解决这些问题。

向 CameraX API 打个招呼! 👋

如上所述,CameraX 使构建自定义相机变得更加容易。 它还带来了一个全新的易于实现的功能列表。

备注:CameraX API 仍处于 alpha 阶段,因此该 API 在未来几个月内可能会发生变化。 谨慎使用。 ⚠️

以下是迄今为止 CameraX 的一些主要功能:

简单

CameraX 库提供了一个简单易用的 API。 此 API 在大多数运行 Lollipop 5.0 及更高版本的 Android 设备上是一致的。

解决设备兼容性问题

CameraX 还旨在解决特定设备上出现的问题。 使用 Camera2 和旧版 Camera,开发人员通常不得不编写大量特定于设备的代码。 多亏了 CameraX,他们现在可以告别编写代码了!

谷歌通过在自动化测试实验室中对来自许多制造商的许多不同设备上的 CameraX 进行测试来实现这一目标。

利用所有 Camera2 API 功能

虽然 CameraX 解决了 Camera2 的问题,但有趣的是,它还利用了所有 Camera2 API 功能。 仅这一事实就让我很难想出在未来几个月内不迁移到 CameraX 的理由。

更少的代码

这对于想要开始使用 CameraX API 的开发人员来说是一个福音。 与 Camera2 和旧版 Camera API 相比,您编写的代码更少,以实现对相机的相同级别的控制。

CameraX 中的用例

使用 CameraX 是一种享受,因为它的使用可以分为 3 个用例:

  • 图片预览:拍照前在您的设备上预览图像。
  • 图像采集:捕获高质量图像并将其保存到您的设备。
  • 图像分析:分析您的图像以对其执行图像处理、计算机视觉或机器学习推理。

CameraX 扩展

这个是我个人最喜欢的。 CameraX Extensions 是一些小插件,只需 2 行代码即可实现人像、HDR、夜间模式和美颜模式等功能!

现在我们已经介绍了 CameraX 为相机开发带来的功能,让我们来看看它的基本实现。

在我们深入编码之前,您可以找到本文中介绍的应用程序的演示存储库 相关信息,以防您想克隆它并直接跳到代码。

将 CameraX 添加到您的应用程序

将以下依赖项添加到您的应用程序级别 build.gradle 文件:

def camerax_version = "1.0.0-alpha02"
// CameraX core library
implementation "androidx.camera:camera-core:${camerax_version}"
// CameraX library for compatibility with Camera2
implementation "androidx.camera:camera-camera2:${camerax_version}"

让我们在 AndroidManifest.xml 文件:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

在您的应用中使用 CameraX

构建基本用户界面

让我们为您设置相同的基本布局 MainActivity 如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <TextureView android:id="@+id/texture_view" android:layout_width="match_parent" android:layout_height="match_parent" … /> <Button android:id="@+id/btn_capture" android:layout_width="wrap_content" android:layout_height="wrap_content" … />
</RelativeLayout>

请求所需的权限

现在我们已经构建了基本的 UI,您应该请求 CAMERAWRITE_EXTERNAL_STORAGE 您的应用程序中的权限。 您可以了解如何在 Android 中请求权限 相关信息, 或者你可以参考我的 Github repo 来代替这个项目。

配置 TextureView

接下来,实施 LifecycleOwner 并设置 TextureView 在您的 MainActivity,如下:

class MainActivity : AppCompatActivity(), LifecycleOwner { override fun onCreate(...) {
if (areAllPermissionsGranted()) {
texture_view.post { startCamera() }
} else {
ActivityCompat.requestPermissions(
this, PERMISSIONS, CAMERA_REQUEST_PERMISSION_CODE
)
} texture_view.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
updateTransform()
}
} private fun startCamera() { // TODO: We’ll implement all the 3 below use cases here, later in this article.
// Setup the image preview val preview = setupPreview() // Setup the image capture val imageCapture = setupImageCapture() // Setup the image analysis val analyzerUseCase = setupImageAnalysis() // Bind camera to the lifecycle of the Activity CameraX.bindToLifecycle(this, preview, imageCapture, analyzerUseCase)
} private fun updateTransform() { val matrix = Matrix() val centerX = texture_view.width / 2f val centerY = texture_view.height / 2f val rotationDegrees = when (texture_view.display.rotation) { Surface.ROTATION_0 -> 0 Surface.ROTATION_90 -> 90 Surface.ROTATION_180 -> 180 Surface.ROTATION_270 -> 270 else -> return } matrix.postRotate(-rotationDegrees.toFloat(), centerX, centerY) texture_view.setTransform(matrix) } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { if (requestCode == CAMERA_REQUEST_PERMISSION_CODE) { if (areAllPermissionsGranted()) { texture_view.post { startCamera() } } else { Toast.makeText(this, "Permissions not granted!", Toast.LENGTH_SHORT).show() finish() } } } private fun areAllPermissionsGranted() = PERMISSIONS.all { ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED } companion object { private const val CAMERA_REQUEST_PERMISSION_CODE = 13 private val PERMISSIONS = arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE) }
}

实现图像预览

与任何其他相机应用程序一样,在我们的相机应用程序中捕获之前,我们首先需要查看图像的预览。 为此,我们需要创建一个实例 预览配置 通过 预览配置生成器.

所以让我们开始编写我们的 setupPreview() in MainActivity:

private fun setupPreview(): Preview { val previewConfig = PreviewConfig.Builder().apply { // Sets the camera lens to front camera or back camera. setLensFacing(CameraX.LensFacing.BACK) // Sets the aspect ratio for the preview image. setTargetAspectRatio(Rational(1, 1)) // Sets the resolution for the preview image. // NOTE: The below resolution is set to 800x800 only for demo purposes. setTargetResolution(Size(800, 800)) }.build() // Create a Preview object with the PreviewConfig val preview = Preview(previewConfig) // Set a listener for the preview’s output preview.setOnPreviewOutputUpdateListener { // val parent = texture_view.parent as ViewGroup // Update the parent View to show the TextureView parent.removeView(texture_view) parent.addView(texture_view, 0) texture_view.surfaceTexture = it.surfaceTexture updateTransform() } return preview
}

瞧! 您现在可以在设备上看到实时图像预览。 要自定义图像预览功能的体验,请浏览文档 相关信息. 这是公共方法的列表 PreviewConfig.Builder 来自官方文档:
CameraX PreviewConfig Builder.png

现在我们已经完成了图像预览设置,让我们开始使用我们的新相机捕获和保存图像。

实现图像捕获

为此,您首先需要获取 图像捕捉配置和一个 ImageConfig 目的。

让我们编写代码来设置图像捕获 setupImageCapture() 方法中 MainActivity.

private fun setupImageCapture(): ImageCapture { val imageCaptureConfig = ImageCaptureConfig.Builder() .apply { setTargetAspectRatio(Rational(1, 1)) // Sets the capture mode to prioritise over high quality images // or lower latency capturing setCaptureMode(ImageCapture.CaptureMode.MAX_QUALITY) }.build() val imageCapture = ImageCapture(imageCaptureConfig) // Set a click listener on the capture Button to capture the image btn_capture.setOnClickListener { // Create the image file val file = File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "${System.currentTimeMillis()}_CameraXPlayground.jpg" ) // Call the takePicture() method on the ImageCapture object imageCapture.takePicture(file, object : ImageCapture.OnImageSavedListener { // If the image capture failed override fun onError( error: ImageCapture.UseCaseError, message: String, exc: Throwable? ) { val msg = "Photo capture failed: $message" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.e("CameraXApp", msg) exc?.printStackTrace() } // If the image capture is successful override fun onImageSaved(file: File) { val msg = "Photo capture succeeded: ${file.absolutePath}" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.d("CameraXApp", msg) } }) } return imageCapture
}

您可以阅读有关如何自定义图像捕获体验的更多信息 相关信息. 此外,这里是一个公共方法表 ImageCaptureConfig.Builder 来自官方文档的类:
CameraX ImageCaptureConfig.png

实施图像分析

现在我们已经了解了基本的用例,让我们学习如何在我们的相机应用程序中分析图像。 为了这个演示,我们将分析实时图像预览的红色像素数量。

为此,您需要一个实例 ImageAnalysisConfig, 我们将使用 图像分析配置.Builder 类。

首先,我们需要编写我们的图像分析器,它应该实现 ImageAnalysis.Analyzer:

class RedColorAnalyzer : ImageAnalysis.Analyzer { private var lastAnalyzedTimestamp = 0L // Helper method to convert a ByteBuffer to a ByteArray private fun ByteBuffer.toByteArray(): ByteArray { rewind() val data = ByteArray(remaining()) get(data) return data } override fun analyze(image: ImageProxy, rotationDegrees: Int) { val currentTimestamp = System.currentTimeMillis() if (currentTimestamp > lastAnalyzedTimestamp) { val buffer = image.planes[0].buffer val data = buffer.toByteArray() val pixels = data.map { it.toInt() and 0xFF0000 } val averageRedPixels = pixels.average() Log.d("CameraXPlayground", "Average red pixels: $averageRedPixels") lastAnalyzedTimestamp = currentTimestamp } }
}

现在让我们写 setupImageAnalysis() 方法:

private fun setupImageAnalysis(): ImageAnalysis { val analyzerConfig = ImageAnalysisConfig.Builder().apply {
// Create a HandlerThread for image analysis val analyzerThread = HandlerThread( "RedColorAnalysis" ).apply { start() } setCallbackHandler(Handler(analyzerThread.looper)) // Set the image reader mode to read either the latest image or next image setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE) }.build() // Create an ImageAnalysis object and set the analyzer return ImageAnalysis(analyzerConfig).apply { analyzer = RedColorAnalyzer() }
}

您现在可以在 Android Logcat 的实时图像预览中看到红色像素的数量!

通过阅读官方文档自定义您的图像分析体验并构建您自己的图像分析器 相关信息. 下面是公共方法的表格 ImageAnalysisConfig.Builder 来自官方文档:
CameraX_ImageAnalysisConfig.png

将 CameraX 绑定到生命周期

到目前为止,我们已经介绍了如何在 CameraX 中使用所有 3 个用例,但不要忘记将 CameraX 绑定到 Activity的生命周期。 我们可以通过调用 bindToLifecycle() 方法,如下所示:

// Bind camera to the lifecycle of the Activity
CameraX.bindToLifecycle(this, preview, imageCapture, analyzerUseCase)

我们过去了 this 作为这里的第一个参数,我们的 bindToLifecycle() 方法需要 LifecycleOwner 作为其调用中的第一个参数。 由于我们已经实施 LifecycleOwner 请通过 WestEd 就业网页,在我们的 MainActivity,这是由 Activity 我们。

bindToLifecycle() 方法还采用与上述用例相对应的其他 3 个参数。 以下是可以与调用此方法一起使用的用例的可能组合,直接来自 官方 CameraX 架构文档:
CameraX_Architecture.png

CameraX 扩展

请记住,CameraX 扩展仅支持 一些设备 截至目前。 希望谷歌的目标是在今年晚些时候将这种支持扩展到更多设备。

虽然 API 尚未准备好实施 CameraX 扩展,但您可以阅读有关它的更多信息 相关信息.

结论

您可以在本教程中找到演示应用程序的代码 相关信息. 克隆存储库并尝试使用 CameraX 必须为自己提供的自定义设置!

虽然 CameraX API 仍处于 alpha 阶段,但您可以看到它已经比 Camera2 API 和旧的 Camera API 更好。 考虑到这一点,如果您决定立即在生产中使用它,请谨慎使用它。

我正在等待 Google 提供更多关于 API 中的更多自定义、支持 CameraX 扩展的新设备以及 API 在未来几个月将如何变化的消息。 CameraX 对开发者来说确实很有希望,无论是新手还是老手。

这是为了更好地在 Android 中开发相机! 🚀

来源:https://www.codementor.io/blog/camerax-7fh6g0yxyg

现货图片

最新情报

现货图片