如何用Jetpack Compose结合CameraX自定义相机预览并处理帧
Jetpack Compose + CameraX 自定义预览组件实现方案
核心思路
放弃CameraX自带的PreviewView,改用ImageAnalysis用例抓取相机原始帧,在帧分析器中完成自定义图像处理(比如滤镜、裁剪、灰度化等),再将处理后的图像数据传递到Composable组件中展示。全程基于Compose实现,无需依赖XML布局。
步骤1:添加依赖
在app/build.gradle.kts中引入CameraX核心及Compose扩展库:
dependencies { // CameraX 核心库 implementation("androidx.camera:camera-core:1.3.2") implementation("androidx.camera:camera-camera2:1.3.2") // CameraX Compose 适配扩展 implementation("androidx.camera:camera-compose:1.3.2") // Kotlin 核心扩展(处理图像转换) implementation("androidx.core:core-ktx:1.12.0") }
步骤2:相机权限处理
使用Compose的权限API请求相机权限:
val cameraPermissionState = rememberPermissionState(android.Manifest.permission.CAMERA) LaunchedEffect(Unit) { if (!cameraPermissionState.hasPermission) { cameraPermissionState.launchPermissionRequest() } }
步骤3:自定义帧处理与预览组件
3.1 定义状态存储处理后的图像
用MutableState保存处理后的Bitmap,供Composable实时渲染:
val processedBitmap = remember { mutableStateOf<Bitmap?>(null) }
3.2 初始化CameraX并绑定图像分析用例
通过DisposableEffect管理CameraX生命周期,确保组件销毁时释放资源:
val context = LocalContext.current val lifecycleOwner = LocalLifecycleOwner.current val coroutineScope = rememberCoroutineScope() DisposableEffect(lifecycleOwner) { val cameraProviderFuture = ProcessCameraProvider.getInstance(context) val imageAnalyzer = ImageAnalysis.Builder() .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) // 仅保留最新帧,避免卡顿 .build() .also { it.setAnalyzer(coroutineScope) { image -> // 1. 将CameraX输出的YUV格式图像转为Bitmap val rawBitmap = image.toBitmap() // 2. 自定义图像处理:示例为灰度化 val handledBitmap = rawBitmap.toGrayScale() // 3. 切换到主线程更新Compose状态 coroutineScope.launch(Dispatchers.Main) { processedBitmap.value = handledBitmap } // 必须释放图像资源,否则CameraX会停止输出新帧 image.close() } } cameraProviderFuture.addListener({ val cameraProvider = cameraProviderFuture.get() try { // 先解绑所有已绑定的用例 cameraProvider.unbindAll() // 绑定相机与图像分析用例 cameraProvider.bindToLifecycle( lifecycleOwner, CameraSelector.DEFAULT_BACK_CAMERA, imageAnalyzer ) } catch (e: Exception) { e.printStackTrace() } }, ContextCompat.getMainExecutor(context)) // 组件销毁时解绑相机资源 onDispose { cameraProviderFuture.get().unbindAll() } }
3.3 图像转换工具方法
添加扩展函数实现YUV转Bitmap、灰度化处理:
// 将CameraX的YUV图像转为Bitmap fun Image.toBitmap(): Bitmap { val yuvImage = YuvImage( planes[0].buffer, ImageFormat.NV21, width, height, null ) val outputStream = ByteArrayOutputStream() yuvImage.compressToJpeg(Rect(0, 0, width, height), 100, outputStream) val imageBytes = outputStream.toByteArray() return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) } // Bitmap灰度化处理 fun Bitmap.toGrayScale(): Bitmap { val grayBitmap = Bitmap.createBitmap(width, height, config) val canvas = Canvas(grayBitmap) val paint = Paint().apply { colorFilter = ColorMatrixColorFilter(ColorMatrix().apply { setSaturation(0f) }) } canvas.drawBitmap(this, 0f, 0f, paint) return grayBitmap }
3.4 自定义预览Composable
用Image组件展示处理后的图像,同时添加加载状态提示:
@Composable fun CustomCameraPreview(processedBitmap: State<Bitmap?>) { Box(modifier = Modifier.fillMaxSize()) { processedBitmap.value?.let { bitmap -> Image( bitmap = bitmap.asImageBitmap(), contentDescription = "自定义相机预览", modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop ) } ?: run { Text( text = "加载相机预览中...", modifier = Modifier.align(Alignment.Center), color = Color.White ) } } }
步骤4:整合所有代码
将上述模块整合到主Composable中:
@Composable fun CameraScreen() { val cameraPermissionState = rememberPermissionState(android.Manifest.permission.CAMERA) val processedBitmap = remember { mutableStateOf<Bitmap?>(null) } val context = LocalContext.current val lifecycleOwner = LocalLifecycleOwner.current val coroutineScope = rememberCoroutineScope() // 请求相机权限 LaunchedEffect(Unit) { if (!cameraPermissionState.hasPermission) { cameraPermissionState.launchPermissionRequest() } } if (cameraPermissionState.hasPermission) { // 初始化CameraX DisposableEffect(lifecycleOwner) { val cameraProviderFuture = ProcessCameraProvider.getInstance(context) val imageAnalyzer = ImageAnalysis.Builder() .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .build() .also { it.setAnalyzer(coroutineScope) { image -> val rawBitmap = image.toBitmap() val handledBitmap = rawBitmap.toGrayScale() coroutineScope.launch(Dispatchers.Main) { processedBitmap.value = handledBitmap } image.close() } } cameraProviderFuture.addListener({ val cameraProvider = cameraProviderFuture.get() try { cameraProvider.unbindAll() cameraProvider.bindToLifecycle( lifecycleOwner, CameraSelector.DEFAULT_BACK_CAMERA, imageAnalyzer ) } catch (e: Exception) { e.printStackTrace() } }, ContextCompat.getMainExecutor(context)) onDispose { cameraProviderFuture.get().unbindAll() } } // 展示自定义预览 CustomCameraPreview(processedBitmap) } else { // 权限被拒绝提示 Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text(text = "需要相机权限才能使用", color = Color.Red) } } }
关键注意事项
- 帧资源释放:必须调用
image.close(),否则CameraX会停止输出新帧,导致预览卡顿。 - 线程切换:ImageAnalysis的分析器运行在后台线程,更新Compose状态必须切换到主线程。
- 生命周期绑定:用
DisposableEffect将CameraX与LifecycleOwner绑定,避免组件销毁后相机资源泄漏。 - 性能优化:示例中的YUV转Bitmap是基础实现,若需高性能可考虑使用RenderScript或GPU加速处理图像。
内容的提问来源于stack exchange,提问作者shawnlg
相关产品推荐
相关产品推荐

