Jetpack CameraX
文章目录
简介
CameraX是Jetpack支持库,旨在帮助您更轻松地开发相机应用程序。它提供了一致且易于使用的API表面,适用于大多数Android设备,向后兼容Android 5.0(API级别21)
它使用了Camera2的功能,但它使用了一种更简单,基于用例的方法,可以感知生命周期
CameraX 结构
开发人员使用CameraX通过一个称为用例的抽象与设备的摄像机进行交互。目前可用的用例如下:
-
预览:准备一个预览SurfaceTexture
-
图像分析:提供CPU可访问的缓冲区用于分析,例如用于机器学习
-
图像捕获:捕获并保存照片
用例可以同时组合和激活。例如,应用程序可以让用户使用预览用例查看相机看到的图像,有一个图像分析用例来确定照片中的人是否在微笑,包括一个图像捕获用例,如果他们在笑,去拍照
权限
权限声明
<uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> //可选
动态申请
要求
CameraX 具有以下最低版本要求:
- Android API 级别 21
- Android 架构组件 1.1.1
对于具有生命周期感知能力的 Activity,请使用
FragmentActivity
或AppCompatActivity
。声明依赖项
Getting Started with CameraX
1. 新建Android项目
2. 添加project的build.gradle依赖
allprojects {
repositories {
google()
jcenter()
}
}
3.添加app的build.gradle依赖
android{
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
// CameraX core library using the camera2 implementation
def camerax_version = "1.0.0-beta08"
// The following line is optional, as the core library is included indirectly by camera-camera2
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
// If you want to additionally use the CameraX Lifecycle library
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
// If you want to additionally use the CameraX View class
implementation "androidx.camera:camera-view:1.0.0-alpha15"
// If you want to additionally use the CameraX Extensions library
implementation "androidx.camera:camera-extensions:1.0.0-alpha15"
}
配置
关于分辨率
CameraX 会自动提供专用于您的应用运行所在设备的功能.如果您未指定分辨率或指定的分辨率不受支持,CameraX 会自动确定要使用的最佳分辨率。所有这些操作均由库进行处理,无需您编写设备专属代码。
CameraX 的目标是成功初始化相机会话。这意味着 CameraX 会根据设备功能降低分辨率和宽高比。降低的原因可能是:
- 设备不支持请求的分辨率。
- 设备存在兼容性问题,例如需要特定分辨率才能正常运行的旧设备。
- 在某些设备上,特定格式仅适用于特定宽高比。
- 对于 JPEG 或视频编码,设备首选“最近的 mod16”。如需了解详情,请参阅
SCALER_STREAM_CONFIGURATION_MAP
。
尽管 CameraX 会创建并管理会话,但您应始终检查代码中用例输出上返回的图像大小,并进行相应调整。
相机分辨率
您可以选择允许 CameraX 根据设备功能、设备支持的硬件级别、用例和所提供的宽高比组合设置图片分辨率。或者,您也可以在支持相应配置的用例中设置特定目标分辨率或特定宽高比
自动分辨率
CameraX 可以根据 cameraProcessProvider.bindToLifecycle()
中指定的用例自动确定最佳分辨率设置。请尽可能在单个 bindToLifecycle()
调用的单个会话中指定需要同时运行的所有用例。
CameraX 会考虑设备支持的硬件级别以及设备专属变化(设备可能会超出或不满足可用的信息流配置),根据绑定的一组用例确定分辨率。这样做的目的是使应用可在各种设备上运行,同时最大限度地减少设备专属代码路径。
图片拍摄和图片分析用例的默认宽高比为 4:3。
用例具有一个可配置的宽高比,可允许应用根据界面设计指定所需的宽高比。如果没有任何支持的完全匹配分辨率,则选择满足大多数条件的分辨率。因此,应用决定了相机应如何在应用中显示,CameraX 则会确定最佳相机分辨率设置,以在不同设备上满足这一要求。
应用可以执行以下任一操作:
-
为用例指定 4:3 或 16:9 的目标分辨率
-
指定自定义分辨率,CameraX 将尝试查找与其最接近的匹配项
-
为
ImageCapture
指定剪裁宽高比
指定分辨率
使用 setTargetResolution(Size resolution)
方法构建用例时,您可以设置特定分辨率,如以下代码示例所示:
ImageAnalysis imageAnalysis =
new ImageAnalysis.Builder()
.setTargetResolution(new Size(1280, 720))
.build();
如果没有可用分辨率,则回退到 640x480
CameraX 会根据请求应用最合适的分辨率。如果主要需求是满足宽高比要求,则仅指定 setTargetAspectRatio
,CameraX 会根据设备确定合适的特定分辨率。如果应用的主要需求是指定分辨率以提高图片处理效率(例如根据设备处理能力处理较小或中等大小的图片),请使用 setTargetResolution(Size resolution)
旋转方向
默认情况下,在用例创建期间,相机旋转方向默认设置为与默认的显示屏旋转方向保持一致。
在这种默认情况下,CameraX 会生成输出以允许应用轻松与您希望在预览中看到的画面保持一致。可以通过配置用例对象时传入当前显示屏方向或在创建用例对象之后动态传入显示屏方向,
将旋转方向更改为自定义值以支持多显示屏设备。
实现预览
PreviewView
,这是一种可以剪裁、缩放和旋转以确保正确显示的 View
。
当相机处于活动状态时,图片预览会流式传输到 PreviewView
中的 Surface
使用 PreviewView
如需使用 PreviewView
实现 CameraX 预览,请按以下步骤操作(稍后将对这些步骤进行说明):
- 配置
CameraXConfig.Provider
。 - 将
PreviewView
添加到布局。 - 请求
CameraProvider
。 - 在创建
View
时,请检查CameraProvider
。 - 选择相机并绑定生命周期和用例。
使用 PreviewView
时,您无法执行以下任何操作:
- 创建
SurfaceTexture
,以在TextureView
和PreviewSurfaceProvider
上进行设置。 - 从
TextureView
检索SurfaceTexture
,并在PreviewSurfaceProvider
上对其进行设置。 - 从
SurfaceView
获取Surface
,并在PreviewSurfaceProvider
上对其进行设置。
如果出现上述任何一种情况,则 Preview
会停止将帧流式传输到 PreviewView
配置 CameraXConfig.Provider
import androidx.camera.camera2.Camera2Config;
import androidx.camera.core.CameraXConfig;
public class MyCameraXApplication extends Application implements CameraXConfig.Provider {
@NonNull
@Override
public CameraXConfig getCameraXConfig() {
return Camera2Config.defaultConfig();
}
}
将 PreviewView 添加到布局
<FrameLayout
android:id="@+id/container">
<androidx.camera.view.PreviewView
android:id="@+id/preview_view" />
</FrameLayout>
请求 CameraProvider
public class MainActivity extends AppCompatActivity {
private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
cameraProviderFuture = ProcessCameraProvider.getInstance(this);
}
}
检查 CameraProvider 可用性
请求 CameraProvider
后,请验证它能否在视图创建后成功初始化。以下代码展示了如何执行此操作:
cameraProviderFuture.addListener(() -> {
try {
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
bindPreview(cameraProvider);
} catch (ExecutionException | InterruptedException e) {
// No errors need to be handled for this Future.
// This should never be reached.
}
}, ContextCompat.getMainExecutor(this));
选择相机并绑定生命周期和用例
创建并确认 CameraProvider
后,请执行以下操作:
- 创建
Preview
。 - 指定所需的相机
LensFacing
选项。 - 将所选相机和任意用例绑定到生命周期。
- 将
Preview
连接到PreviewView
。
以下代码展示了一个示例:
void bindPreview(@NonNull ProcessCameraProvider cameraProvider) {
Preview preview = new Preview.Builder().build();
CameraSelector cameraSelector = new CameraSelector.Builder().
requireLensFacing(CameraSelector.LENS_FACING_BACK).build();
Camera camera = cameraProvider.bindToLifecycle(this,cameraSelector,preview);
preview.setSurfaceProvider(previewView.createSurfaceProvider());
}
如此即可实现预览
分析图像
这里的执行器可以理解为线程
图像分析用例为您的应用提供了可供 CPU 访问以执行图像处理、计算机视觉或机器学习推断的图像。该应用会实现对每个帧运行的 analyze 方法
通过传递一个执行器(这个执行器运行图像分析)和一个ImageAnalysis.Analyzer
参数到setAnalyzer()方法来处理图像。
阻塞模式
图像分析可以分为两种模式:阻塞模式和非阻塞模式,通过调用方法setBackpressureStrategy()
设置不同参数来达到不同的模式,参数如下:
STRATEGY_BLOCK_PRODUCER
阻塞模式
在此模式下,执行程序会按顺序从相机接收帧;这意味着,如果 analyze() 方法所用的时间超过单帧在当前帧速率下的延迟时间,帧便可能不再是最新的帧,因为新帧已被阻塞进入管道,直到方法返回为止
STRATEGY_KEEP_ONLY_LATEST
非阻塞模式
在此模式下,执行程序在调用 analyze() 方法时会从相机接收最后一个可用帧。如果analyze方法所用的时间超过单帧在当前帧速率下的延迟时间,一些帧可能会跳过,以便在下一次 analyze() 接收数据时,它会获取相机管道中的最后一个可用帧。
在 analyze()
方法返回前,请通过调用 image.close()
关闭图片引用,以避免阻塞后续图像的生成(导致预览停顿)并避免可能出现的图像丢失。此方法必须完成分析或创建副本,而不是将图像引用传递到分析方法以外
ImageAnalysis imageAnalysis =
new ImageAnalysis.Builder()
.setTargetResolution(new Size(1280, 720))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build();
imageAnalysis.setAnalyzer(executor, new ImageAnalysis.Analyzer() {
@Override
public void analyze(@NonNull ImageProxy image) {
int rotationDegrees = image.getImageInfo().getRotationDegrees();
// insert your code here.
}
});
cameraProvider.bindToLifecycle((LifecycleOwner) this, cameraSelector, imageAnalysis, preview);
CameraX 会生成 YUV_420_888
格式的图片
图片拍摄
图片拍摄用例旨在拍摄高分辨率的优质照片,不仅提供简单的相机手动控制功能,还提供自动白平衡、自动曝光和自动对焦 (3A) 功能。调用程序负责决定如何使用拍摄的照片,具体包括以下选项:
- [
takePicture(Executor, OnImageCapturedCallback)
](https://developer.android.google.cn/reference/androidx/camera/core/ImageCapture#takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback)):此方法为拍摄的图片提供内存缓冲区。 - [
takePicture(OutputFileOptions, Executor, OnImageSavedCallback)
](https://developer.android.google.cn/reference/androidx/camera/core/ImageCapture#takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback)):此方法将拍摄的图片保存到提供的文件位置。
运行 ImageCapture
的可自定义执行程序有两种类型:回调执行程序和 IO 执行程序。
-
回调执行程序是
takePicture
方法的参数。它用于执行用户提供的OnImageCapturedCallback
。 -
如果调用方选择将图片保存到文件位置,您可以指定执行程序以执行 IO。如需设置 IO 执行程序,请调用
ImageCapture.Builder.setIoExecutor(Executor)
。如果执行程序不存在,则默认 CameraX 为任务的内部 IO 执行程序。实现
提供了拍照所需的基本控制功能。照片是使用闪光灯选项和连续自动对焦拍摄的。
缩短照片拍摄的延迟时间
ImageCapture.CaptureMode
设置为CAPTURE_MODE_MINIMIZE_LATENCY
优化照片质量,请将其设置为
CAPTURE_MODE_MAXIMIZE_QUALITY
ImageCapture imageCapture =
new ImageCapture.Builder()
.setTargetRotation(view.getDisplay().getRotation())
.build();
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, imageCapture, imageAnalysis, preview);
配置好相机后,以下代码会根据用户操作拍照:
public void onClick() {
imgPath = Environment.getExternalStorageDirectory()+"/APicture";
File file = new File(imgPath);
if (!file.exists()){
file.mkdirs();
}
File imgFile = new File(imgPath,"temp.jpg");
ImageCapture.OutputFileOptions outputFileOptions =
new ImageCapture.OutputFileOptions.Builder(imgFile).build();
capture.takePicture(outputFileOptions,
ContextCompat.getMainExecutor(this), new ImageCapture.OnImageSavedCallback() {
@Override
public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
Log.d("SAVE SUCCESS =",Thread.currentThread().getName());
Log.d("SAVE SUCCESS",outputFileResults.toString());
}
@Override
public void onError(@NonNull ImageCaptureException exception) {
Log.d("SAVE error",exception.toString());
}
});
}
图片拍摄方法完全支持 JPEG
格式
供应商扩展
CameraX 用例旋转
疑难问题:
1.java.lang.IllegalStateException: Must call CameraX.initialize() first
原因:CameraX未初始化,为null
解决:ProcessCameraProvider必须放在Preview 、ImageCapture、ImageAnalysis用例之前
ProcessCameraProvider.getInstance(this);
//Preview
//ImageAnalysis 初始化
//ImageCapture
...
2.在CameraX 1.0.0-beta08 下发现Application不配置CameraXConfig.Provider 也可以使用
原因:在ProcessCameraProvider.getInstance(this)下的CameraX的getOrCreateInstance方法中
CameraXConfig.Provider configProvider = getConfigProvider(context);
在getConfigProvider中,
if (application instanceof CameraXConfig.Provider) {
// Application is a CameraXConfig.Provider, use this directly
configProvider = (CameraXConfig.Provider) application;
} else {
// Try to retrieve the CameraXConfig.Provider through the application's resources
try {
Resources resources = context.getApplicationContext().getResources();
String defaultProviderClassName =
resources.getString(
R.string.androidx_camera_default_config_provider);
Class<?> providerClass =
Class.forName(defaultProviderClassName);
configProvider = (CameraXConfig.Provider) providerClass
.getDeclaredConstructor()
.newInstance();
}...
如果application不是CameraXConfig.Provider的子类,则会反射构造一个
Camera2Config.DefaultProvider实例,如下:
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY)
public static final class DefaultProvider implements CameraXConfig.Provider {
@NonNull
@Override
public CameraXConfig getCameraXConfig() {
return defaultConfig();
}
}
其实,Google 也想到了这点,有兴趣的可以看一下 ProcessCameraProvider.getInstance()的注释
Simple Demo:CameraXSampleJava