compose camera 相机预览

本文介绍了如何在Android应用中使用AndroidXCamera库进行摄像头预览,并展示了如何动态请求权限、处理图像分析(如人脸识别)以及保存处理后的图像。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

添加依赖

    implementation 'androidx.camera:camera-core:1.2.2'
    implementation 'androidx.camera:camera-camera2:1.2.2'
    implementation 'androidx.camera:camera-lifecycle:1.2.2'
    implementation 'androidx.camera:camera-view:1.2.2'
    implementation 'androidx.camera:camera-extensions:1.2.2'
    implementation 'com.google.accompanist:accompanist-permissions:0.24.7-alpha'

权限申请

清单中添加

<uses-feature
        android:name="android.hardware.camera"
        android:required="false" />

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

动态申请权限

            val permissionState =
                rememberPermissionState(android.Manifest.permission.CAMERA)
            if (permissionState.status.isGranted) {
                val cameraState = rememberCameraState()

                CameraPreviewCard(modifier = Modifier.fillMaxSize(), cameraState)
            } else {
                Button(onClick = {
                    permissionState.launchPermissionRequest()
                }) {
                    Text(text = "请求授权")
                }
            }

相机预览

@androidx.camera.core.ExperimentalGetImage
@Composable
fun CameraPreviewCard(
    modifier: Modifier = Modifier,
    cameraState: CameraState,
) {


    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current
    var preview by remember { mutableStateOf<Preview?>(null) }



        AndroidView(modifier = modifier, factory = { ctx ->
            PreviewView(ctx).apply {
                layoutParams = ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
                )
                controller = cameraState.controller.apply {
                    bindToLifecycle(lifecycleOwner)

                }

                previewStreamState.observe(lifecycleOwner) { state ->
                    cameraState.isStreaming = state == PreviewView.StreamState.STREAMING
                }
            }
        }, update = { previewView ->

            val cameraSelector: CameraSelector =
                CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()



            val cameraProviderFuture: ListenableFuture<ProcessCameraProvider> =
                ProcessCameraProvider.getInstance(context)
            cameraProviderFuture.addListener(
                {
                    val rotate = Surface.ROTATION_180
                    preview = Preview.Builder().setTargetRotation(rotate).build().also {
                        it.setSurfaceProvider(previewView.surfaceProvider)
                    }
                    val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
                    try {
                        cameraProvider.unbindAll()

                        cameraProvider.bindToLifecycle(lifecycleOwner,cameraSelector,preview)

                    } catch (e: Exception) {
                        Log.d("CameraPreview", "CameraPreview: ${e.localizedMessage}")
                    }
                }, ContextCompat.getMainExecutor(context)
            )
        })
}


class CameraState internal constructor(context: Context) {


    /**
     * Check if camera is streaming or not.
     * */
    var isStreaming: Boolean by mutableStateOf(false)
        internal set

    /**
     * Main controller from CameraX. useful in cases that haven't been release some feature yet.
     * */
    internal val controller: LifecycleCameraController = LifecycleCameraController(context)


}

这样在主页就可以对相机进行预览了。

图像分析

如果要对camera的图像数据进行分析处理,可添加ImageAnalysis.Analyzer,

class FaceAnalyser :
    ImageAnalysis.Analyzer {


    override fun analyze(image: ImageProxy) {
        try {
            if (image.format == ImageFormat.YUV_420_888) {
                val yuV420toNV21 = YUV420toNV21(image.image!!) //这里只做简单的图像转换然后存储

                var rotateNv21: ByteArray? = yuV420toNV21
                Nv21Data.data = rotateNv21
                Nv21Data.width = image.width
                Nv21Data.height = image.height
            }
            image.close()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}

fun saveYuv(byteArray: ByteArray, width: Int, height: Int) {
    val yuvImage = YuvImage(byteArray, ImageFormat.NV21, width, height, null)
    val yFile = File(
        Environment.getExternalStorageDirectory().path + "/test/",
        "y_" + System.currentTimeMillis() + ".jpg"
    )
    Log.d("linlian", "${yFile.absolutePath}")
    saveYuvToFile(yFile, width, height, yuvImage)
}

fun saveYuvToFile(file: File, wid: Int, height: Int, yuvImage: YuvImage) {
    try {
        val c = file.createNewFile()
        Log.d("test", "$file created: $c")
        val fos = FileOutputStream(file)
        yuvImage.compressToJpeg(Rect(0, 0, wid, height), 100, fos)
        fos.close()
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

fun YUV420toNV21(image: Image): ByteArray? {
    val crop = image.cropRect
    val format = image.format
    val width = crop.width()
    val height = crop.height()
    val planes = image.planes
    val data = ByteArray(width * height * ImageFormat.getBitsPerPixel(format) / 8)
    val rowData = ByteArray(planes[0].rowStride)
    var channelOffset = 0
    var outputStride = 1
    for (i in planes.indices) {
        when (i) {
            0 -> {
                channelOffset = 0
                outputStride = 1
            }

            1 -> {
                channelOffset = width * height + 1
                outputStride = 2
            }

            2 -> {
                channelOffset = width * height
                outputStride = 2
            }
        }
        val buffer = planes[i].buffer
        val rowStride = planes[i].rowStride
        val pixelStride = planes[i].pixelStride
        val shift = if (i == 0) 0 else 1
        val w = width shr shift
        val h = height shr shift
        buffer.position(rowStride * (crop.top shr shift) + pixelStride * (crop.left shr shift))
        for (row in 0 until h) {
            var length: Int
            if (pixelStride == 1 && outputStride == 1) {
                length = w
                buffer[data, channelOffset, length]
                channelOffset += length
            } else {
                length = (w - 1) * pixelStride + 1
                buffer[rowData, 0, length]
                for (col in 0 until w) {
                    data[channelOffset] = rowData[col * pixelStride]
                    channelOffset += outputStride
                }
            }
            if (row < h - 1) {
                buffer.position(buffer.position() + rowStride - length)
            }
        }
    }
    return data
}

object Nv21Data {

    var data: ByteArray? = null
    var width = 0
    var height = 0
}

camera中添加分析器

Camera preview中添加

 val cameraProviderFuture: ListenableFuture<ProcessCameraProvider> =
                ProcessCameraProvider.getInstance(context)
            cameraProviderFuture.addListener(
                {
                    val rotate = Surface.ROTATION_180
                    preview = Preview.Builder().setTargetRotation(rotate).build().also {
                        it.setSurfaceProvider(previewView.surfaceProvider)
                    }

                    val cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor() //分析数据的线程
                    //图像分析
                    val faceAnalyser = FaceAnalyser()
                    val imageAnalysis: ImageAnalysis = ImageAnalysis.Builder()
                        .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                        .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888)//default OUTPUT_IMAGE_FORMAT_YUV_420_888
                        .build().also {
                            it.setAnalyzer(cameraExecutor, faceAnalyser)
                        }




                    val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
                    try {
                        cameraProvider.unbindAll()

                        cameraProvider.bindToLifecycle(lifecycleOwner,cameraSelector,preview,imageAnalysis)//绑定

                    } catch (e: Exception) {
                        Log.d("CameraPreview", "CameraPreview: ${e.localizedMessage}")
                    }
                }, ContextCompat.getMainExecutor(context)
            )

添加保存图片的按钮

Button(onClick = {
                        Nv21Data.data?.let {
                            saveYuv(it, Nv21Data.width, Nv21Data.height)

                        }
                    }) {
                        Text(text = "save")
                    }

点击 save就可以在sdcard上得到当前图像的文件,可以观察数据格式,图像方向是否正确。
保存文件,记得需要提前申请文件存储权限

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值