Camera2设置中间区域AE/AF/AWB后预览模糊,求实现方案
Camera2实现中间区域对焦锁定+AE/AWB基于中间区域计算的正确流程
你遇到的预览模糊问题核心在于AF模式选择错误以及区域坐标转换逻辑不准确,以下是完整的实现步骤和修正后的代码:
一、核心问题分析
- AF模式问题:你使用
CONTROL_AF_MODE_AUTO是单次自动对焦模式,不会锁定对焦状态,且未主动触发对焦动作,导致相机可能未完成对焦就进入预览,或后续重新对焦偏离中间区域。 - 区域坐标错误:
MeteringRectangle的坐标基于传感器原始方向(通常为横向,与设备屏幕方向无关),直接交换left和top的逻辑无法正确匹配屏幕预览的中间区域,需根据传感器方向和预览方向做完整坐标映射。
二、正确实现流程
1. 确认设备支持性(必须步骤,规避兼容性问题)
在使用区域测光/对焦前,先检查设备是否支持对应功能:
// 检查AF区域支持 val afRegionsSupported = characteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF) ?: 0 > 0 // 检查AE区域支持 val aeRegionsSupported = characteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE) ?: 0 > 0 // 检查AWB区域支持 val awbRegionsSupported = characteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AWB) ?: 0 > 0
2. 正确计算中间区域的MeteringRectangle
修正坐标转换逻辑,需考虑预览旋转方向(设备屏幕方向与传感器方向的差值),以下是正确的区域计算函数:
fun getCenterMeteringRegion(characteristics: CameraCharacteristics, previewWidth: Int, previewHeight: Int): MeteringRectangle { val sensorRect = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE)!! val sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!! // 计算预览与传感器区域的比例(适配预览裁剪/缩放) val ratioX = sensorRect.width().toFloat() / previewWidth.toFloat() val ratioY = sensorRect.height().toFloat() / previewHeight.toFloat() // 预览画面的中间区域(尺寸可按需调整) val previewRegionWidth = 200 val previewRegionHeight = 200 val previewLeft = (previewWidth - previewRegionWidth) / 2 val previewTop = (previewHeight - previewRegionHeight) / 2 // 将预览坐标转换为传感器坐标,并处理旋转 val (sensorLeft, sensorTop, sensorRegionWidth, sensorRegionHeight) = when (sensorOrientation) { 90 -> { // 传感器顺时针转90度(竖屏预览) val rotatedLeft = previewTop * ratioY val rotatedTop = (previewWidth - (previewLeft + previewRegionWidth)) * ratioX val rotatedWidth = previewRegionHeight * ratioY val rotatedHeight = previewRegionWidth * ratioX Quad(rotatedLeft.toInt(), rotatedTop.toInt(), rotatedWidth.toInt(), rotatedHeight.toInt()) } 270 -> { // 传感器顺时针转270度 val rotatedLeft = (previewHeight - (previewTop + previewRegionHeight)) * ratioY val rotatedTop = previewLeft * ratioX val rotatedWidth = previewRegionHeight * ratioY val rotatedHeight = previewRegionWidth * ratioX Quad(rotatedLeft.toInt(), rotatedTop.toInt(), rotatedWidth.toInt(), rotatedHeight.toInt()) } 180 -> { // 传感器顺时针转180度 val rotatedLeft = (previewWidth - (previewLeft + previewRegionWidth)) * ratioX val rotatedTop = (previewHeight - (previewTop + previewRegionHeight)) * ratioY Quad(rotatedLeft.toInt(), rotatedTop.toInt(), (previewRegionWidth * ratioX).toInt(), (previewRegionHeight * ratioY).toInt()) } else -> { // 0度(传感器横向) Quad((previewLeft * ratioX).toInt(), (previewTop * ratioY).toInt(), (previewRegionWidth * ratioX).toInt(), (previewRegionHeight * ratioY).toInt()) } } return MeteringRectangle( sensorLeft, sensorTop, sensorRegionWidth, sensorRegionHeight, MeteringRectangle.METERING_WEIGHT_MAX // 最大权重确保区域优先级最高 ) } // 辅助数据类,简化坐标返回 data class Quad(val left: Int, val top: Int, val width: Int, val height: Int)
3. 配置CaptureRequest并实现对焦锁定
需先触发对焦,完成后锁定AF状态,同时设置AE/AWB区域:
private fun setupPreviewAndLockFocus() { val previewSurface = Surface(viewFinder.surfaceTexture) val previewWidth = viewFinder.width val previewHeight = viewFinder.height val meteringRegion = getCenterMeteringRegion(characteristics, previewWidth, previewHeight) // 1. 初始化预览请求模板 val previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).apply { addTarget(previewSurface) // 设置AE/AWB区域 set(CaptureRequest.CONTROL_AE_REGIONS, arrayOf(meteringRegion)) set(CaptureRequest.CONTROL_AWB_REGIONS, arrayOf(meteringRegion)) set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_AUTO) set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON) set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH) } // 2. 触发单次对焦请求 val afRequestBuilder = previewRequestBuilder.clone().apply { set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO) set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START) set(CaptureRequest.CONTROL_AF_REGIONS, arrayOf(meteringRegion)) } // 发送对焦请求,在回调中处理锁定逻辑 cameraCaptureSession.capture(afRequestBuilder.build(), object : CameraCaptureSession.CaptureCallback() { override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) { super.onCaptureCompleted(session, request, result) // 检查对焦是否完成 val afState = result.get(CaptureResult.CONTROL_AF_STATE) if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED || afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) { // 3. 创建锁定对焦的重复预览请求 val lockedPreviewRequest = previewRequestBuilder.clone().apply { set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_LOCKED) set(CaptureRequest.CONTROL_AF_TRIGGER, null) // 清除触发信号 } // 设置重复预览,此时对焦已锁定在中间区域 cameraCaptureSession.setRepeatingRequest(lockedPreviewRequest.build(), captureCallBack, cameraHandler) } else { // 对焦失败,降级使用普通预览 cameraCaptureSession.setRepeatingRequest(previewRequestBuilder.build(), captureCallBack, cameraHandler) } } }, cameraHandler) }
4. 关键注意事项
- 权重设置:
MeteringRectangle使用METERING_WEIGHT_MAX可确保该区域测光/对焦优先级最高,避免其他区域干扰。 - AF触发与锁定:必须先发送带
CONTROL_AF_TRIGGER_START的请求触发单次对焦,完成后切换到CONTROL_AF_MODE_LOCKED锁定状态,否则无法固定对焦。 - 预览尺寸匹配:计算区域时必须使用当前预览Surface的实际尺寸,避免预览缩放导致区域偏移。
内容的提问来源于stack exchange,提问作者kallis
相关产品推荐
相关产品推荐

