用androidx.camera拍摄景深合成照片
用androidx.camera拍摄景深合成照片
androidx.camera的不断完善,使得原来复杂繁琐的安卓相机开发容易了许多。很多传统相机上有称之为景深包围的拍照功能,一次拍摄完成多张不同焦距的照片,后期用软件把多张照片合成为一张大景深或全景深照片,这种拍摄方式在安卓系统中也可以实现。
项目概述
DeepCamera 是一个基于 Android 平台的相机应用项目,采用 Kotlin 语言和 Jetpack Compose 框架开发,集成 CameraX 库实现相机功能。模块app用CameraControl+Camera2CameraControl实现对相机的控制。
一次拍摄可以得到多张不同焦距的照片,后期可以用软件(例如photoshop)合成一张大景深照片。
系统结构
整体结构如下:
language
DeepCamera/app/
├── theme/ #主题
├── CameraScreen #相机预览和拍照界面
├── FocusDistanceInfo #相机焦距信息
├── FocusItem #焦距项
├── MainActivity #主界面
├── MainSurface #相机页面
├── SettingsSurface #焦距设置页面
├── ShutterSound #快门声效类
└── Util #静态子程序类
核心组件包括:
- UI 层:使用 Jetpack Compose 构建响应式界面
- 相机控制层:基于 CameraX 实现相机硬件交互
- 数据层:处理图片存储与系统媒体库交互
相机控制基础
androidX用抽象类UseCase(用例)管理相机的应用。UseCase目前只有ImageAnalysis, ImageCapture, Preview, VideoCapture四种子类,对应四种应用:图像分析、拍照、预览、拍视频。在程序中需要把UseCase绑定到CameraProvider,以实现对相机应用场景的控制。本程序只用到UseCase的两种应用场景:ImageCapture和Preview。
androidX对于相机的控制有三种途径
- CameraController
CameraController是androidx.camera.view中的抽象类,它的实现是LifecycleCameraController,这是一个高级控制器,在单个类中提供了大多数 CameraX 核心功能。它负责相机初始化,创建并配置用例,并在准备就绪时将它们绑定到生命周期所有者。它还会监听设备运动传感器,并为UseCase用例设置目标旋转角度。 - CameraControl
CameraControl是androidx.camera.core中的接口,它提供各种异步操作,如变焦、对焦和测光,这些操作会影响当前绑定到该相机的所有UseCase用例的输出。CameraControl 的每种方法都会返回一个 ListenableFuture,应用程序可以用它来检查异步结果。 - Camera2CameraControl
Camera2CameraControl是androidx.camera.camera2.interop中的不可重写类,它提供与 android.hardware.camera2 API 的互操作能力,可以实现CameraControl所不能提供的相机底层操作,例如手动设定焦距。
本程序使用CameraControl+Camera2CameraControl实现对相机的控制,没有使用CameraController。
获取CameraControl和Camera2CameraControl的步骤如下:
kotlin
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
// 初始化CameraProvider的监听器
val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) }
// 初始化CameraProvider
var cameraProvider by remember { mutableStateOf<ProcessCameraProvider?>(null) }
// 初始化PreviewView
val previewView = remember {
PreviewView(context).apply {
implementationMode = PreviewView.ImplementationMode.PERFORMANCE
}
}
// 初始化相机
var camera by remember { mutableStateOf<Camera?>(null) }
// 创建拍照用例
val imageCapture = remember { Util.getImageCapture() }
// 创建预览用例
val preview = remember { Util.getPreview() }
// 使用previewView的SurfaceProvider来配置预览用例
preview.setSurfaceProvider(previewView.surfaceProvider)
// 监听CameraProvider的变化,从监听器中获取CameraProvider实例
cameraProvider = cameraProviderFuture.get()
//定义后摄像头的选择器
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
// 解除既有绑定
cameraProvider.unbindAll()
// 绑定新的用例
camera = cameraProvider.bindToLifecycle(lifecycleOwner,cameraSelector, imageCapture, preview)
// 取得cameraControl
val cameraControl = camera!!.cameraControl
// 取得camera2CameraControl
val camera2CameraControl = Camera2CameraControl.from(cameraControl)
核心实现方法
- 获取Preview
kotlin
fun getPreview(): Preview {
// 定义ResolutionStrategy
val resolutionStrategy = ResolutionStrategy(Size(1920, 1080), FALLBACK_RULE_CLOSEST_LOWER)
// 定义AspectRatioStrategy
val aspectRatioStrategy =
AspectRatioStrategy(AspectRatio.RATIO_16_9, AspectRatioStrategy.FALLBACK_RULE_AUTO)
// 定义ResolutionSelector
val resolutionSelector =
ResolutionSelector.Builder().setAspectRatioStrategy(aspectRatioStrategy)
.setResolutionStrategy(resolutionStrategy).build()
return Preview.Builder().setResolutionSelector(resolutionSelector).build()
}
- 获取ImageCapture
kotlin
fun getImageCapture(): ImageCapture {
// 定义ResolutionStrategy
val resolutionStrategy = ResolutionStrategy.HIGHEST_AVAILABLE_STRATEGY
// 定义AspectRatioStrategy
val aspectRatioStrategy =
AspectRatioStrategy(AspectRatio.RATIO_16_9, AspectRatioStrategy.FALLBACK_RULE_AUTO)
// 定义ResolutionSelector
val resolutionSelector =
ResolutionSelector.Builder().setAspectRatioStrategy(aspectRatioStrategy)
.setResolutionStrategy(resolutionStrategy).build()
return ImageCapture.Builder().setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.setResolutionSelector(resolutionSelector).build()
}
- 获取ImageCapture的输出文件选项
kotlin
private fun getOutputFileOptions(context: Context): ImageCapture.OutputFileOptions {
val contentValues = ContentValues().apply {
val simpleDateFormat = SimpleDateFormat("yyyyMMdd_HHmmss_SSS", Locale.getDefault())
val currentDateTime = simpleDateFormat.format(Date())
put(MediaStore.MediaColumns.DISPLAY_NAME, "${currentDateTime}.jpg")
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
}
return ImageCapture.OutputFileOptions.Builder(
context.contentResolver, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues
).build()
}
- 设置缩放比例
kotlin
fun setZoomRatio(
cameraControl: CameraControl, zoomRatio: Float?
): ListenableFuture<Void?> {
val clampedZoomRatio = zoomRatio?.coerceIn(0f, 1f) ?: 0f
return cameraControl.setLinearZoom(clampedZoomRatio)
}
- 设置焦距
kotlin
private fun setFocusDistance(
cameraControl: CameraControl, focusDistance: Float
): ListenableFuture<Void?> {
val camera2CameraControl = Camera2CameraControl.from(cameraControl)
val captureRequestOptions = CaptureRequestOptions.Builder().setCaptureRequestOption(
CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF
).setCaptureRequestOption(CaptureRequest.LENS_FOCUS_DISTANCE, focusDistance).build()
return camera2CameraControl.setCaptureRequestOptions(captureRequestOptions)
}
- 获取相机的对焦信息
kotlin
fun getFocusDistanceInfo(context: Context): FocusDistanceInfo {
val cameraManager =
context.getSystemService(Context.CAMERA_SERVICE) as android.hardware.camera2.CameraManager
val cameraId = cameraManager.cameraIdList.firstOrNull {
val characteristics = cameraManager.getCameraCharacteristics(it)
characteristics.get(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_BACK
}
if (cameraId == null) {
Log.e("getMinFocusDistance", "No back camera found")
return FocusDistanceInfo(0f, 0f, "no-camera-found")
}
val characteristic = cameraManager.getCameraCharacteristics(cameraId)
// 检查是否支持手动对焦
val afAvailability = characteristic.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES)
if (afAvailability == null || !afAvailability.contains(CameraMetadata.CONTROL_AF_MODE_OFF)) {
Log.e("getMinFocusDistance", "Camera does not support manual focus")
return FocusDistanceInfo(0f, 0f, "non-focus-support")
}
// 获取镜头是否校准
val focusDistanceCalibration =
when (characteristic.get(CameraCharacteristics.LENS_INFO_FOCUS_DISTANCE_CALIBRATION)) {
CameraCharacteristics.LENS_INFO_FOCUS_DISTANCE_CALIBRATION_UNCALIBRATED -> "uncalibrated"
CameraCharacteristics.LENS_INFO_FOCUS_DISTANCE_CALIBRATION_CALIBRATED -> "calibrated"
CameraCharacteristics.LENS_INFO_FOCUS_DISTANCE_CALIBRATION_APPROXIMATE -> "approximate"
else -> "unknown"
}
Log.i("getMinFocusDistance", "isLensCalibrated: $focusDistanceCalibration")
// 获取焦点范围
val minFocusDistance = characteristic.get(
CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE
) ?: 0f
Log.i("getMinFocusDistance", "minFocusDistance: $minFocusDistance")
// 获取超焦距
val hyperFocalDistance = characteristic.get(
CameraCharacteristics.LENS_INFO_HYPERFOCAL_DISTANCE
) ?: 0f
Log.i("getMinFocusDistance", "hyperFocalDistance: $hyperFocalDistance")
return FocusDistanceInfo(minFocusDistance, hyperFocalDistance, focusDistanceCalibration)
}
技术栈
- 开发语言:Kotlin
- UI 框架:Jetpack Compose
- 相机库:CameraX
- 架构组件:Lifecycle、Navigation
- 生命周期处理:Composable Effect
- 图片存储:MediaStore