Android-Camera X拍照

173 阅读9分钟

1.camera x使用库

def camerax_version = "1.3.3"//1.0.0-alpha01
    implementation "androidx.camera:camera-core:${camerax_version}"//Camera核心库,设计架构的实现
    implementation "androidx.camera:camera-camera2:${camerax_version}"//Camera2支持库,相机API的实现
    implementation "androidx.camera:camera-lifecycle:${camerax_version}"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"//协程库
    def camerax_view_version = "1.0.0-alpha03"
    def camerax_ext_version = "1.0.0-alpha03"
// If you to use the Camera View class
    implementation "androidx.camera:camera-view:$camerax_view_version"//自定义的 CameraView 组件
// If you to use Camera Extensions  Camera的扩展,用于访问设备专属供应商效果(例如散景、HDR 及其他功能)的 API
    implementation "androidx.camera:camera-extensions:$camerax_ext_version"

2.布局方面主要用androidx.camera.view.PreviewView 来显示预览camera画面

 <androidx.camera.view.PreviewView
        android:id="@+id/viewFinder"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

3.实现预览画面的主要code

一、获取权限
<!--    摄像头权限¬-->
<uses-permission android:name="android.permission.CAMERA" />

// 检查相机权限
		if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { // 请求相机权限
			ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), 1005)
		} else { // 初始化线程池
			cameraExecutor = Executors.newSingleThreadExecutor() // 初始化WindowManager以检索显示指标
			manager = getSystemService(WINDOW_SERVICE)as WindowManager
			updateCameraUi()
			lifecycleScope.launch {
				setUpCamera()
			}
		}
二、初始化CameraX
/** 初始化CameraX,并准备绑定相机用例  */
	@SuppressLint("NewApi")
	private suspend fun setUpCamera() {
	var	lsicameraProvider: ListenableFuture<ProcessCameraProvider> = ProcessCameraProvider.getInstance(this)
		cameraProvider=lsicameraProvider.get()

		lensFacing = when {
			hasBackCamera() -> CameraSelector.LENS_FACING_BACK
			hasFrontCamera() -> CameraSelector.LENS_FACING_FRONT
			else -> throw IllegalStateException("Back and front camera are unavailable")
		}        // 启用或禁用摄像头之间的切换
		updateCameraSwitchButton()        // 构建并绑定相机用例
		bindCameraUseCases()
	}

	/**
	 * 切换摄像头button
	 */
	private fun updateCameraSwitchButton() {
		try {
			cameraSwitchButton.isEnabled = hasBackCamera() && hasFrontCamera()
		} catch (exception : CameraInfoUnavailableException) {
			cameraSwitchButton.isEnabled = false
		}
	}
三、绑定CameraX到PreviewView
/**
	 * 绑定Camera
	 */
	@RequiresApi(Build.VERSION_CODES.R)
	private fun bindCameraUseCases() {// 获取用于设置相机全屏分辨率的屏幕指标
//		// 获取用于设置相机全屏分辨率的屏幕指标【Android11方法】
//		val metrics = windowManager.getCurrentWindowMetrics().bounds
//		Log.d(TAG, "Screen metrics: ${metrics.width()} x ${metrics.height()}")
//		// 计算宽高比
//		val screenAspectRatio = aspectRatio(metrics.width(), metrics.height())

		val display = windowManager.defaultDisplay//【老版本方法】
		val size = Point()
		display.getSize(size)
		val width = size.x
		val height = size.y
		val screenAspectRatio = aspectRatio(width, height)
		Log.d(TAG, "Preview aspect ratio: $screenAspectRatio")

		//获取 PreviewView方向
		val rotation = windowManager.defaultDisplay.rotation

		// CameraProvider
		val cameraProvider = cameraProvider
			?: throw IllegalStateException("Camera initialization failed.")

		// CameraSelector
		val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()

		// Preview
		preview = Preview.Builder()                // 设置宽高比
				.setTargetAspectRatio(screenAspectRatio)                // 设置方向
                                
				.setTargetRotation(rotation).build()

		//  ImageCapture.Metadata 设置输出文件名和其他相关参数
		val metadata = ImageCapture.Metadata().apply {            // 使用前置摄像头时,处理镜像
			isReversedHorizontal = lensFacing == CameraSelector.LENS_FACING_FRONT
		}        // imageCapture 用于拍摄照片
		imageCapture = ImageCapture.Builder()                // CAPTURE_MODE_MINIMIZE_LATENCY:捕获模式为最小化延迟模式,适用于快速响应和低延迟的拍摄场景。
				.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)   // 设定宽高比
				.setTargetAspectRatio(screenAspectRatio)     //设置方向
				.setTargetRotation(rotation)   //.setMetadata(metadata)
				.build()

		// ImageAnalysis:分析相机捕获的实时预览图像数据
		imageAnalyzer = ImageAnalysis.Builder()     // 设定宽高比
				.setTargetAspectRatio(screenAspectRatio)                // 设置方向
                               .setOutputImageFormat(OUTPUT_IMAGE_FORMAT_YUV_420_888)//设置编码方式   质量优先
                                //OUTPUT_IMAGE_FORMAT_RGBA_8888  速度优先
				.setTargetRotation(rotation).build()                // 将分析器分配给实例
				.also {                    // 在别的线程做分析 需要传入一个线程对象,和一个分析对象
					it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma ->
						Log.d(TAG, "Average luminosity: $luma")
					})
				}        // 在重新绑定用例之前必须解除绑定
		cameraProvider.unbindAll()
		if (camera != null) {            // 必须从之前的相机实例中移除观察者
			removeCameraStateObservers(camera!!.cameraInfo)
		}
		try {            // 通过 cameraProvider 绑定生命周期得到Camera对象
			camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture, imageAnalyzer)

			// 设置 surfaceProvider,这样数据就会源源不断流到 viewFinder(PreviewView)控件上了,这样就完成了预览了
			preview?.setSurfaceProvider(viewFinder.surfaceProvider)            // 观察相机状态
			observeCameraState(camera?.cameraInfo!!)
		} catch (exc : Exception) {
			Log.e(TAG, "Use case binding failed", exc)
		}
	}

	private fun aspectRatio(width : Int, height : Int) : Int {
		val previewRatio = max(width, height).toDouble() / min(width, height)
		if (abs(previewRatio - RATIO_4_3_VALUE) <= abs(previewRatio - RATIO_16_9_VALUE)) {
			return AspectRatio.RATIO_4_3
		}
		return AspectRatio.RATIO_16_9
	}

	private fun hasBackCamera() : Boolean {
		return cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?: false
	}

	private fun hasFrontCamera() : Boolean {
		return cameraProvider?.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) ?: false
	}

	private fun removeCameraStateObservers(cameraInfo : CameraInfo) {
		cameraInfo.cameraState.removeObservers(this)
	}

通过 cameraProvider 绑定生命周期得到Camera对象,设置 surfaceProvider,这样数据就会源源不断流到 viewFinder(PreviewView)控件上了,这样就完成了预览了

4.拍照的实现

拍照主要是通过 ImageCapture 来完成。

/*相机拍照按钮监听*/
		cameraCaptureButton.setOnClickListener {
			// ?.let  用于在imageCapture非null时执行一个闭包,否则不执行任何操作。
			// ImageCapture 是用于拍摄照片的用例之一。它提供了一个简单、易于使用的 API,用于控制相机硬件以进行图像捕获操作。
			imageCapture?.let { imageCapture ->

				// 创建带有时间戳的名称和MediaStore
				val name = SimpleDateFormat(FILENAME, Locale.US)
					.format(System.currentTimeMillis())
				val contentValues = ContentValues().apply {
					put(MediaStore.MediaColumns.DISPLAY_NAME, name)
					put(MediaStore.MediaColumns.MIME_TYPE, PHOTO_TYPE)
					if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
						val appName = resources.getString(R.string.app_name)
						put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/${appName}")
					}
				}

				// Metadata:图像捕获配置和控制的属性
				val metadata = ImageCapture.Metadata().apply {
					// 使用到前摄像头 镜像值为true
					isReversedHorizontal =
						lensFacing == CameraSelector.LENS_FACING_FRONT
				}
				val outputOptions = ImageCapture.OutputFileOptions
					.Builder(this.contentResolver,
						MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
						contentValues)
					.setMetadata(metadata)
					.build()

				// imageCapture.takePicture() 方法是用于拍摄照片的方法。使用此方法,您可以执行图像捕获操作,并将结果传递给指定的 ImageCapture.OnImageSavedCallback。
				imageCapture.takePicture(
					outputOptions, cameraExecutor, object : ImageCapture.OnImageSavedCallback {
						override fun onError(exc: ImageCaptureException) {
							Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
						}

						/**
						 * 当图像成功保存到指定文件时被调用。您可以在该方法中执行任何后续操作,例如显示保存后的图像或上传到服务器等。
						 */
						override fun onImageSaved(output: ImageCapture.OutputFileResults) {
							val savedUri = output.savedUri
							val message = "拍照成功: ${output.savedUri}"
							Log.d(TAG, message)
							this@MainActivity.runOnUiThread {
								ToastUtils.showMsg(this@MainActivity, message) }

							// Android N 以下的发送一个广播更新相册
							if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
								@Suppress("DEPRECATION")
								sendBroadcast(
									//发送广播更新相册
									Intent(android.hardware.Camera.ACTION_NEW_PICTURE, savedUri)
								)
							}
						}
					})
			}
		}

ImageCapture.takePicture() 方法是用于拍摄照片的方法。使用此方法,您可以执行图像捕获操作,并将结果传递给指定的 ImageCapture.OnImageSavedCallback。onError()此callback接收拍照成功失败的信息 onImageSaved()接收拍照成功后的信息,例如保存的uri地址。

5.切换前后像头

/*前后像头切换*/
		cameraSwitchButton.setOnClickListener {
			lensFacing = if (CameraSelector.LENS_FACING_FRONT == lensFacing) {
				CameraSelector.LENS_FACING_BACK
			} else {
				CameraSelector.LENS_FACING_FRONT
			}
			// 更新配置后,重新初始化
			bindCameraUseCases()
		}

ProcessCameraProvider

ProcessCameraProvider 是 Android Jetpack 中提供的用于相机功能的重要组件之一。它提供了一种简单而强大的方式来管理相机的生命周期、配置和图像捕获操作。其主要功能包括:

  • 生命周期管理:ProcessCameraProvider 提供了与生命周期相关的方法,可以在适当的生命周期阶段,例如 onCreate()、onStart() 或 onDestroy() 中创建、启动或释放相机资源。这确保了相机资源的正确管理和释放,避免了内存泄漏和资源浪费。与 Android Jetpack 的生命周期组件整合,能够很好地适配应用程序的生命周期,确保在合适的时机启动和关闭相机。
  • 相机配置:允许开发者对相机进行各种配置,包括分辨率、帧率、曝光补偿、白平衡等参数的设置,以满足不同的应用需求。
  • 图像捕获:ProcessCameraProvider 提供了接口来进行图像捕获操作,包括拍照、录制视频、实时图像处理等功能。
  • 多摄像头支持:支持同时管理设备上的多个摄像头,可以方便地切换前后摄像头,并进行并行捕获。
  • 图像分析:集成了 ImageAnalysis 功能,可以方便地进行实时图像分析和处理。
  • 相机状态监听:提供相机状态的监听器,可以获取相机启动、错误、配置变化等事件的通知。
  • 权限管理:封装了相机权限的处理,简化了相机权限请求和处理逻辑。
  • 相机绑定:ProcessCameraProvider 可以将相机绑定到指定的预览视图上,从而实现实时预览。可以通过调用 bindToLifecycle() 方法将相机绑定到指定的生命周期所有者(LifecycleOwner)上,并指定预览视图(如 TextureView、SurfaceView 等),从而在界面上显示相机预览。
  • 组合多个用例:ProcessCameraProvider 支持多个用例的组合。可以同时使用预览、图像捕获、图像分析等用例,并将它们一起配置和绑定到相机提供者上。这样,您可以在一个应用程序中实现多种相机功能,并灵活地进行配置。

ImageCapture

ImageCapture 是 Android Jetpack CameraX 中的一个用例,用于拍摄照片。它提供了一个简单、易于使用的 API,用于控制相机硬件并执行图像捕获操作。 ImageCapture 用例的主要功能如下:

  • 拍摄照片:使用 ImageCapture 可以轻松地拍摄照片。可以调用 takePicture() 方法来触发照片捕获操作,并指定保存图像的文件和其他参数。
  • 录像:除了拍照,ImageCapture 还可以录制视频,捕获连续的图像帧并保存为视频文件。
  • 配置捕获设置:ImageCapture 提供了一系列可配置的设置,例如图片格式、闪光灯模式、焦点模式等。可以根据需求来自定义这些设置,以满足特定的应用场景。
  • 多媒体存储:将捕获的图像或视频数据保存到设备的存储介质,如硬盘、内存卡或云存储。
  • 实时滤镜:对捕获的图像进行实时滤镜处理,例如黑白化、模糊、边缘检测等。
  • 多摄像头支持:支持同时使用多个摄像头进行图像捕获,例如前置摄像头和后置摄像头切换。
  • 摄像头控制:允许应用程序对摄像头进行控制,例如切换摄像头、调整摄像头方向等。
  • 监听拍摄结果:ImageCapture 提供了一个回调接口,通过实现 ImageCapture.OnImageSavedCallback 接口,可以处理图像保存成功或失败时的结果。这样,可以根据需要对保存的图像进行进一步处理或处理可能发生的错误。
  • 集成图像分析:ImageCapture 可以与图像分析用例结合使用,实现更复杂的图像处理和分析功能。例如,可以在拍摄照片后,使用图像实时分析来检测人脸或识别物体等。

ImageAnalysis

mageAnalysis 是 Android Jetpack CameraX 库中的一个用例,用于实时分析和处理相机捕获的图像数据。它提供了一种方便的方式来执行实时图像处理、计算和分析的操作。 ImageAnalysis 的功能包括:

  • 特征提取:从图像中提取关键特征,如边缘、角点、纹理等,以便后续的分析和处理。
  • 图像分割:将图像分割成不同的区域或对象,通常用于目标检测、医学图像分析等领域。
  • 目标跟踪:追踪图像中的目标或运动物体,可以用于视频监控、自动驾驶等应用中。
  • 图像特征匹配:用于在图像中寻找相似的特征点或模式,通常用于图像配准、拼接和三维重建等任务。
  • 图像质量评估:对图像的清晰度、对比度、噪声等进行评估,以确定图像的质量和适用性。
  • 图像增强:对图像进行滤波、去噪、增强对比度等处理,以改善图像质量或使特定特征更加突出。
  • 图像分析:对图像进行统计、分析和建模,以从中提取有用的信息或进行预测
  • 实时图像分析:ImageAnalysis 允许对相机捕获的实时预览图像数据进行实时分析。可以使用图像分析算法来处理和提取图像中的特征、对象或信息。 也可用于识别图像中的对象、特征或模式,包括物体检测、人脸识别、文字识别等。 也可通过对图像进行特征提取和模式识别,可以对图像进行分类,例如将图像分为不同的类别或标签。从而实现图像分类功能。
  • 分析器回调:通过设置分析器(Analyzer),可以定义自己的图像分析逻辑,并在每次有新的图像可用时接收回调。在回调中,可以获取图像数据,并对其进行处理、计算和分析。这使得可以实时地获取和处理相机捕获的图像,并根据需要执行相应的操作。
  • 图像处理和计算:ImageAnalysis 提供了一个用于处理和计算图像数据的接口。可以使用 OpenCV、TensorFlow 等库进行图像处理、计算和机器学习操作。例如,可以执行人脸检测、图像识别、条码扫描等操作,以实现各种应用场景。
  • 背压策略和图像队列:ImageAnalysis 还提供了背压策略和图像队列的支持。背压策略定义了当图像处理速度慢于图像生成速度时的处理方式,而图像队列用于存储等待分析的图像数据。可以根据实际需求配置背压策略和图像队列深度,以平衡性能和系统资源的使用。

CameraState.Type

在 Android 相机开发中,CameraState.Type 是一个枚举类,表示相机的状态类型。以下是 CameraState.Type 的一些常见类型及其含义:

  • PENDING_OPEN: 相机正在等待信号以尝试打开相机设备的状态。(别的应用已打开相机)
  • OPENED:表示相机已经被打开并且处于活动状态。可以开始预览、拍照或录制视频。
  • OPEN: 表示相机设备处于打开状态。
  • CLOSED:表示相机已经关闭。不再接收预览请求或拍照请求。

ImageCapture.Metadata

ImageCapture.Metadata 是用于捕获图像时附加元数据的类。它是 CameraX 相机库中的一部分。 ImageCapture.Metadata 类提供了一些可用于图像捕获配置和控制的属性,例如闪光灯模式、曝光补偿、对焦模式、白平衡等。可以通过创建一个 ImageCapture.Metadata 对象,并通过设置相应的属性来自定义图像捕获的行为。 以下是 ImageCapture.Metadata 类的一些常见属性:

  • FlashMode:闪光灯模式,指定在图像捕获期间是否使用闪光灯。可选值有自动、关闭、打开等。
  • ExposureCompensation:曝光补偿值,用于调整图像的亮度。可以通过设置一个浮点数值来进行曝光补偿。
  • FocusMode:对焦模式,用于控制相机的对焦方式。可选值有自动对焦、连续对焦、固定对焦等。
  • WhiteBalance:白平衡模式,用于校正图像中的色温。可选值包括自动、日光、阴影、白炽灯等。 除了上述属性外,还可以使用 ImageCapture.Metadata 类来设置其他一些属性,例如图像质量、图像方向等。