You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于CameraX与Jetpack Compose的相机APP实时滤镜及照片查看功能实现求助

基于CameraX与Jetpack Compose的相机APP实时滤镜及照片查看功能实现求助

你好!看了你基于CameraX和Jetpack Compose写的相机代码,我来给你梳理下实时AR滤镜和照片查看功能的实现思路,帮你解决找不到合适文档的问题。


一、实时AR滤镜(人脸+背景)实现方案

CameraX本身没有直接提供AR滤镜的API,但可以结合ML Kit和自定义帧处理来实现,下面分人脸AR和背景滤镜分别说明:

1. 实时人脸AR滤镜

核心思路是用ML Kit检测人脸关键点,然后在Camera预览帧上叠加AR元素(比如贴纸、表情特效):

  • 第一步:集成ML Kit人脸检测库,在build.gradle(Module level)中添加依赖:
    dependencies {
        // ML Kit人脸检测
        implementation "com.google.mlkit:face-detection:16.1.5"
    }
    
  • 第二步:用CameraX的ImageAnalysis分析预览帧,获取人脸数据
    在你的CameraContent函数中,新增ImageAnalysis实例并绑定到cameraController:
    // 初始化ML Kit人脸检测器
    val faceDetector = FaceDetection.getClient(
        FaceDetectorOptions.Builder()
            .setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_FAST)
            .setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL)
            .setContourMode(FaceDetectorOptions.CONTOUR_MODE_ALL)
            .build()
    )
    
    // 创建ImageAnalysis实例处理预览帧
    val imageAnalysis = ImageAnalysis.Builder()
        .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
        .build()
        .apply {
            setAnalyzer(ContextCompat.getMainExecutor(context), ImageAnalysis.Analyzer { imageProxy ->
                val mediaImage = imageProxy.image ?: return@Analyzer
                val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
                
                faceDetector.process(inputImage)
                    .addOnSuccessListener { faces ->
                        // 遍历检测到的人脸,获取关键点(眼睛、嘴巴、脸颊等)
                        for (face in faces) {
                            val leftEye = face.getLandmark(FaceLandmark.LEFT_EYE)
                            val rightEye = face.getLandmark(FaceLandmark.RIGHT_EYE)
                            val mouth = face.getLandmark(FaceLandmark.MOUTH_BOTTOM)
                            // 拿到关键点后,可在预览层上层绘制AR元素(比如贴纸)
                        }
                    }
                    .addOnCompleteListener {
                        // 必须关闭imageProxy,否则会导致帧堆积
                        imageProxy.close()
                    }
            })
        }
    
    // 将ImageAnalysis绑定到cameraController
    cameraController.bindToLifecycle(lifecycleOwner, cameraController.cameraSelector, imageAnalysis)
    
  • 第三步:在Compose中叠加AR元素
    因为你用AndroidView包裹了PreviewView,所以可以在PreviewView的上层用Composable元素(比如Box、Image)绘制AR贴纸。需要注意把相机帧的坐标转换成Compose的坐标,要结合相机的旋转角度调整位置。

2. 实时背景滤镜

背景滤镜分两种实现方式:

  • 简单滤镜(模糊/灰度):直接在ImageAnalysis中对帧的像素进行处理,然后用自定义的SurfaceView或TextureView替换PreviewView来渲染处理后的帧(PreviewView默认是硬件加速,自定义渲染更灵活)。
  • 智能抠图换背景:用ML Kit的人像分割模型,把人像从背景中分离出来,再叠加自定义背景。先添加分割库依赖:
    implementation "com.google.mlkit:segmentation-selfie:16.0.0-beta5"
    
    然后在ImageAnalysis中处理帧,分割出人像区域后叠加新背景即可。

二、照片查看功能实现

你已经把照片保存到MediaStore的Pictures/AirPic目录,只需查询该目录下的图片,再用Compose展示即可:

1. 查询本地AirPic目录照片

创建一个工具函数或ViewModel方法,查询MediaStore中的目标图片:

fun getAirPicPhotos(contentResolver: ContentResolver): List<Uri> {
    val photos = mutableListOf<Uri>()
    // 查询所需字段
    val projection = arrayOf(MediaStore.Images.Media._ID)
    // 指定查询AirPic目录
    val selection = "${MediaStore.Images.Media.RELATIVE_PATH} LIKE ?"
    val selectionArgs = arrayOf("%Pictures/AirPic%")
    // 按拍摄时间倒序排列
    val sortOrder = "${MediaStore.Images.Media.DATE_ADDED} DESC"

    contentResolver.query(
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        projection,
        selection,
        selectionArgs,
        sortOrder
    )?.use { cursor ->
        val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
        while (cursor.moveToNext()) {
            val id = cursor.getLong(idColumn)
            val contentUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
            photos.add(contentUri)
        }
    }
    return photos
}

2. 用Compose展示照片列表

创建一个新的GalleryScreen页面,用LazyColumn展示照片(建议用Coil/Glide的Compose扩展加载图片):

@Composable
fun GalleryScreen(contentResolver: ContentResolver) {
    // 用remember缓存照片列表,避免重复查询
    val photos by remember { mutableStateOf(getAirPicPhotos(contentResolver)) }
    
    LazyColumn(modifier = Modifier.fillMaxSize()) {
        items(photos) { photoUri ->
            Image(
                painter = rememberAsyncImagePainter(photoUri),
                contentDescription = "Captured photo",
                modifier = Modifier
                    .fillMaxWidth()
                    .height(200.dp)
                    .padding(4.dp)
                    .clip(RoundedCornerShape(8.dp))
            )
        }
    }
}

然后在相机页面的Gallery按钮点击事件中,用Jetpack Navigation跳转到该页面即可。


额外提示

  • 处理相机帧时,务必使用STRATEGY_KEEP_ONLY_LATEST策略,避免帧堆积导致性能下降
  • 人脸关键点的坐标转换要考虑相机的旋转角度,参考imageProxy.imageInfo.rotationDegrees调整
  • 照片加载建议用Coil(io.coil-kt:coil-compose)或Glide的Compose扩展,提升加载效率和体验

备注:内容来源于stack exchange,提问作者Jayashre

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.21 13:39:44