新闻详情

新闻详情

首页 / 资讯中心 / 详情

Touch AE与Face AE冲突排查:基于Camera2的自动曝光区域管理实践

发布时间:2026/9/30 9:02:54来源:尧图网络
Touch AE与Face AE冲突排查:基于Camera2的自动曝光区域管理实践
1. 问题现象点了一下屏幕人脸就“黑”了先交代一下背景。前阵子在做一个三方相机项目就是那种在系统相机之外、自己实现取景、对焦、曝光、拍照全流程的App。功能做到人脸追踪阶段时碰到一个非常典型的坑用户在取景界面点击屏幕任意位置后人脸框还在但人脸区域的曝光明显不正常了——要么过曝要么压暗反正不是你想要的“以人脸为准”的效果。用行业术语说就是自动触发了touch ae导致后续face ae全部失效。这个现象在系统相机里几乎遇不到因为系统相机会帮你做策略协调。但到了三方相机所有策略都得自己写问题就暴露了。我起初以为是芯片平台的bug后来定位了一圈才发现问题出在AEAuto Exposure自动曝光的region权重管理和人脸检测回调的时序冲突上。这篇文章就把整个排查过程、根因分析和最终的解决方案完整记录下来希望能帮到正在做Camera开发、尤其是做三方相机人脸相关功能的同学。先说清楚两个概念不然下面的内容容易绕。touch ae指的是用户触摸屏幕后相机将测光区域Metering Region锁定到触摸点附近让曝光以那个区域为基准。face ae指的是相机检测到人脸后自动将测光区域切换到人脸区域让人脸曝光始终处于理想状态。正常逻辑下这两者应该是互斥且可恢复的关系——用户摸了屏幕touch ae生效人脸重新出现或用户没再触摸时系统应该恢复face ae。但实际做的时候很多三方相机把这两件事做成了“一次触摸、永久覆盖”于是就有了标题里这个case。这个case的典型表现有三个第一触摸对焦后人脸框仍然检测到但曝光不再跟随人脸第二人脸从画面边缘移到中央曝光也不跟着变第三切后台再回前台曝光恢复正常但一旦再触摸又失效。这三个表象看似无关其实指向同一个根因测光区域的更新被touch ae的注册信息占住了face ae的更新请求根本提交不进去。下面我从底层原理开始拆再给可落地的修复方案。2. 自动曝光的底层机制AE Region是怎么工作的2.1 从Camera2的AE三要素说起现在做三方相机基本都基于Camera2 API或者厂商扩展的CameraX、自有HAL接口。不管哪套底层都离不开三个关键控制项CONTROL_AE_MODE曝光模式分为ON自动曝光、OFF手动、ON_AUTO_FLASH自动闪光等。做face ae和touch ae前提是AE模式处于ON状态。CONTROL_AE_REGIONS测光区域是一个整数数组格式为[x1, y1, x2, y2, weight]。前四个值是相对于传感器输出坐标系的矩形范围范围是0到10000Camera2的normalized坐标最后一位是权重取值范围1到1000。CONTROL_AE_LOCK是否锁定当前曝光值。touch ae的做法就是用户点击屏幕后App把触摸点对应的坐标换算成上述region格式然后设置到CONTROL_AE_REGIONS里同时把CONTROL_AF_MODE切到CONTROL_AF_MODE_AUTO或CONTROL_AF_MODE_CONTINUOUS_PICTURE并同样下发CONTROL_AF_REGIONS。face ae的做法类似只不过region的坐标来源不是触摸点而是人脸检测回调CameraCaptureSession的CaptureCallback里通过CaptureResult.STATISTICS_FACE_DETECTOR_MODE拿到的人脸矩形。这里有一个非常关键的底层事实AE Registers测光区域是会被HAL层合并计算的。当你同时设置多个region时HAL会按照权重进行加权测光。如果某个region的权重设置成1000区域内而另一个只有1那么前者几乎完全主导最终的曝光值计算。2.2 touch ae为什么会“压过”face ae很多人以为只要我在触摸之后继续做人脸检测并且检测到人脸就重新下发face ae的region就能恢复。实际操作中你会发现下发是下发了但曝光值纹丝不动。原因在于第一你下发的新region和touch ae的region是叠加关系不是覆盖关系。如果touch ae的region权重是1000而face ae的region权重也是1000HAL会同时计算这两个区域。人脸这时候如果不在触摸点附近曝光就会在“人脸区域”和“触摸点区域”之间取折中最终结果是两者都不讨好。第二很多三方相机的代码在触摸回调里会主动设置mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false)或者干脆用了CONTROL_AE_PRECAPTURE_TRIGGER这些操作会强制HAL重新收敛曝光。而face ae的更新如果发生在这个收敛窗口内极容易被丢弃。第三也是最重要的一点触摸事件处理线程和人脸检测回调线程不是同一个。触摸回调通常运行在UI线程或单独的手势处理线程人脸检测回调运行在Camera的Callback线程。两者并发写入同一个CaptureRequest.Builder如果没有加锁后写入的region很可能被先写入的request覆盖——但你以为你写入成功了。这种“你以为写了其实没写进去”的竞态是这类bug最常见的幕后黑手。2.3 坐标换算里的隐性坑再补充一个容易踩的坑坐标换算。触摸点坐标是屏幕坐标比如1080x2400而AE Region需要的是传感器坐标0到10000的归一化坐标。中间要经过预览画面的裁剪比例换算和传感器旋转/镜像映射。大多数三方相机用TextureView 自定义裁剪屏幕坐标和传感器坐标不是简单的等比关系。如果换算错了一个维度touch ae的region会落在人脸区域之外的某个奇怪位置这时候脸一动曝光立刻跳变。下面这个公式是标准的换算思路以竖屏、传感器旋转90度为例// 屏幕坐标 - 传感器归一化坐标 val sensorX (screenX / previewViewWidth) * 10000f val sensorY (screenY / previewViewHeight) * 10000f // 如果传感器方向是90度需要交换并反转 val sensorNormalizedX sensorY // 旋转90度后x来自原y val sensorNormalizedY 10000f - sensorX // 旋转后y需要反转这个换算每个平台略有差异高通、联发科、三星的ISP对region的坐标系解释也不完全一致。后面我会讲怎么用实拍图验证你的换算对不对。3. 根因定位一次完整的链路追踪3.1 抓Log的“三大件”拿到这个bug之后我没有急着改代码而是先搭了一套可观测的日志系统。重点抓三类数据触摸事件流包括触摸坐标、换算后的sensor坐标、下发时间戳。人脸检测事件流包括人脸框坐标、检测置信度、回调时间戳。AE状态流通过CaptureResult.CONTROL_AE_STATE和CaptureResult.CONTROL_AE_REGIONS回读实际生效的region。抓取CONTROL_AE_REGIONS的回读值非常关键。很多开发者只盯着写出去的request不看HAL实际采纳的result。实际上写出去的request和最终生效的result之间可能隔了2到3帧。你写了face ae的region但HAL可能还在用上一个touch ae的region做曝光计算。只有回读CaptureResult.CONTROL_AE_REGIONS才能确认HAL到底在执行哪套参数。我在这个case里抓到的典型日志如下脱敏后12:30:01.234 TouchEvent: x540, y1200, sensorRegion[2500, 4000, 3500, 4500, 1000] 12:30:01.240 RequestSubmit: AE_REGIONS[[2500, 4000, 3500, 4500, 1000]], AE_MODEON 12:30:01.512 FaceDetected: rect(0.2, 0.3, 0.5, 0.8), region[2000, 3000, 5000, 8000, 1000] 12:30:01.520 RequestSubmit: AE_REGIONS[[2000, 3000, 5000, 8000, 1000]] // face ae尝试覆盖 12:30:01.780 CaptureResult: AE_REGIONS[[2500, 4000, 3500, 4500, 1000]] // HAL还是touch ae 12:30:02.100 CaptureResult: AE_REGIONS[[2500, 4000, 3500, 4500, 1000]] // 始终未切换注意上面倒数第二行——App在01.520的时候确实写入了face ae的region但HAL在01.780回读的仍然是touch ae的region。这说明写入被HAL拒绝或忽略了。为什么会被忽略这才是我真正要查的方向。3.2 排查方向一AE Region的合法性与合并策略先检查写入的region是否合法。Camera2对CONTROL_AE_REGIONS有个隐含约束region数量上限通常为10个部分平台只有5个且每个region的坐标必须在0到10000范围内weight必须在1到1000之间。如果写入的region越界HAL会静默丢弃整个region列表而不是丢弃单个非法region。我当时检查了换算代码发现一个很容易忽略的场景当用户触摸屏幕边缘时触摸点换算出的region矩形可能超出10000的边界。比如手指点在屏幕最左侧换算后x1可能是负数。我在代码里做了clamp但clamp之后x2 - x1可能变成0或者负数——一个宽度为零的regionHAL会认为它非法然后丢弃整个AE_REGIONS更新。这就能解释为什么触摸屏幕边缘后face ae再也拉不回来。3.3 排查方向二AE状态的机内状态机冲突第二个排查方向是CONTROL_AE_STATE。Camera2的AE状态机有几个关键状态CONVERGED已收敛、SEARCHING搜索中、FLASH_REQUIRED、PRECAPTURE等。当你手动设置AE_REGIONS时如果当前状态处于PRECAPTURE或SEARCHING新的region请求会被HAL排队处理而不是立即生效。问题出在touch ae的触发时机。三方相机通常会在触摸后立刻执行一次CONTROL_AE_PRECAPTURE_TRIGGER让HAL重新计算曝光。这个过程需要几帧时间。如果在这个窗口内face ae的region更新到达HAL会认为这是一个新的AE收敛请求于是抛弃当前正在进行的precapture重新开始。但重新开始之后它读到的region可能是旧的因为request queue里还有上一帧的touch ae数据于是face ae的更新就“被吞了”。这个问题的本质是你发region更新的频率超过了HAL的收敛速度导致新region一直在排队而旧region一直在生效。3.4 排查方向三线程竞态——最常见的真凶最后排查到线程问题。我当时的代码结构是触摸回调在主线程通过Handler发送RepeatingRequest。人脸检测回调在CameraCaptureSession的CaptureCallback里也就是Camera的线程。两个线程都在修改同一个CaptureRequest.Builder对象。我的代码里没有对这个Builder做同步于是出现了下面的场景线程A触摸builder.set(CONTROL_AE_REGIONS, touchRegion) 线程B人脸builder.set(CONTROL_AE_REGIONS, faceRegion) // 覆盖了A的写入 线程A触摸session.setRepeatingRequest(builder.build()) // 提交了A的旧值这里的关键是Builder对象的set操作和build()操作之间没有原子性保证。线程B在Abuild()之前覆盖了region但A提交的request已经是B覆盖后的值——但因为时序交错A后续还会再次提交而且它可能用的是自己缓存的一个副本导致最终提交的永远是touch ae的region。说白了这个bug不是“face ae的逻辑错了”而是“face ae的更新动作根本没在正确的时机执行”。这是我最终定位到的核心问题。4. 解决方案落地从策略设计到代码实现4.1 整体策略引入“曝光模式优先级管理器”修复思路很清晰不能让touch ae和face ae无序竞争必须由一个统一的管理器来决定当前应该使用哪个region。我做了一个ExposureModeManager核心职责有三块第一维护一个状态枚举FACE_AE、TOUCH_AE、NONE。默认状态是FACE_AE用户触摸后状态切换为TOUCH_AE当满足一定条件人脸重新检测到、且用户超过N秒未触摸、且触摸点与人脸区域重叠度较高时自动切回FACE_AE。第二所有region的写入请求都经过这个管理器由它统一构造CaptureRequest.Builder再做加锁提交。任何线程都不能直接动Builder。第三管理器内部维护一个“最近一次触摸时间戳”。如果用户连续触摸那么timeout顺延如果在timeout内检测到人脸且人脸框包含了原触摸点或触摸点旁边一定的扩张区域立即切回face ae。这个设计的好处是把“什么时候用谁的region”这个策略问题从“谁先抢到锁”变成了“谁符合策略”。策略是可解释、可测试的而不是靠运气。4.2 核心代码实现先看ExposureModeManager的核心骨架Kotlin基于Camera2enum class ExposurePriority { FACE_AE, TOUCH_AE, NONE } class ExposureModeManager( private val cameraDevice: CameraDevice, private val session: CameraCaptureSession, private val sensorOrientation: Int ) { private val lock ReentrantLock() private val callbackHandler HandlerThread(exposure-manager).apply { start() }.looper.let { Handler(it) } Volatile var currentPriority: ExposurePriority ExposurePriority.FACE_AE private set Volatile private var lastTouchTimestamp: Long 0L Volatile private var lastFaceRegion: Rect? null private val touchAeTimeoutMs 3000L private val pendingBuilder LinkedBlockingQueueCaptureRequest.Builder() fun onTouchEvent(screenX: Float, screenY: Float, previewWidth: Int, previewHeight: Int) { lock.withLock { val sensorRegion convertScreenToSensorRegion(screenX, screenY, previewWidth, previewHeight) currentPriority ExposurePriority.TOUCH_AE lastTouchTimestamp SystemClock.elapsedRealtime() submitRequest { builder - builder.set(CaptureRequest.CONTROL_AE_REGIONS, arrayOf(sensorRegion)) builder.set(CaptureRequest.CONTROL_AF_REGIONS, arrayOf(sensorRegion)) builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON) builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO) } } } fun onFaceDetected(faceRect: Rect) { lock.withLock { lastFaceRegion faceRect // 关键如果当前是TOUCH_AE但已超时或人脸框包含触摸区域切回FACE_AE val now SystemClock.elapsedRealtime() val touchExpired (now - lastTouchTimestamp) touchAeTimeoutMs val faceCoversTouch isFaceCoveringTouch(faceRect) if (currentPriority ExposurePriority.TOUCH_AE (touchExpired || faceCoversTouch)) { currentPriority ExposurePriority.FACE_AE submitRequest { builder - builder.set(CaptureRequest.CONTROL_AE_REGIONS, arrayOf(faceRect.toSensorRegion())) builder.set(CaptureRequest.CONTROL_AF_REGIONS, arrayOf(faceRect.toSensorRegion())) builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON) builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) } } } } private fun submitRequest(action: (CaptureRequest.Builder) - Unit) { callbackHandler.post { val builder cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) action(builder) session.setRepeatingRequest(builder.build(), null, callbackHandler) } } }这里有几个关键设计点要解释。第一提交请求走独立的Handler线程。我在submitRequest里用了callbackHandler.post所有region更新都串行化避免UI线程和Camera回调线程竞争同一个Builder。ReentrantLock只是保护状态变量的读写真正的request提交顺序靠Handler的串行队列来保证。第二face ae的恢复条件做了一个“双保险”。不光是超时切回还判断了“人脸框是否覆盖触摸点”。这个覆盖判断的代码如下private fun isFaceCoveringTouch(faceRect: Rect): Boolean { val touchSensor lastTouchSensorRegion ?: return false // 取人脸框的几何中心 val faceCenterX (faceRect.left faceRect.right) / 2 val faceCenterY (faceRect.top faceRect.bottom) / 2 // 触摸点附近500单位传感器坐标的容差 val tolerance 500 return faceCenterX in (touchSensor.centerX() - tolerance)..(touchSensor.centerX() tolerance) faceCenterY in (touchSensor.centerY() - tolerance)..(touchSensor.centerY() tolerance) }这个判断解决了一个很实际的产品问题用户触摸屏幕后如果人脸恰好也在触摸点附近那这时用户的本意可能就是想让人脸曝光而不是锁定触摸点。所以没必要等3秒超时直接切回face ae更符合直觉。第三超时时长的选择。我试过1秒、2秒、3秒、5秒。1秒太短用户刚摸完屏幕还没看清曝光变化就被切回去了体验很怪5秒太长人脸从暗处走到亮处曝光一直锁在旧位置人脸会过曝。3秒是个折中跟系统相机的“触摸后自动恢复连续自动对焦”的时长体感一致。但这个值建议做成可配置项Face detection频率高的场景可以适当缩短。4.3 坐标换算的正确姿势坐标换算是这个方案里最容易出错的地方单独拿出来说。Camera2的坐标系定义AE Region的坐标范围是0到10000表示的是传感器输出图像的坐标且不受旋转和镜像影响。也就是说你看到的预览画面通常已经经过旋转而AE Region需要的是传感器原始方向上的坐标。我踩过的坑是直接用getTransform()或者TextureView.getTransform()的结果去换算结果发现方向是对的但镜像反了。最终的可靠换算是用官方推荐的公式在onPreviewSizeChanged时计算一次变换矩阵fun createSensorCoordinateTransformer( previewWidth: Int, previewHeight: Int, sensorWidth: Int, sensorHeight: Int, sensorOrientation: Int ): (Float, Float) - FloatArray { // 归一化到0~10000的传感器坐标 // 注意preview可能做了scaleTypecenterCrop需要先计算裁剪偏移 val previewScale max(sensorWidth.toFloat() / previewWidth, sensorHeight.toFloat() / previewHeight) val cropWidth previewWidth * previewScale val cropHeight previewHeight * previewScale val offsetX (sensorWidth - cropWidth) / 2f val offsetY (sensorHeight - cropHeight) / 2f return { screenX, screenY - val sensorX screenX * previewScale offsetX val sensorY screenY * previewScale offsetY val normalizedX sensorX / sensorWidth * 10000f val normalizedY sensorY / sensorHeight * 10000f // 根据传感器方向做旋转映射 when (sensorOrientation) { 90 - floatArrayOf(normalizedY, 10000f - normalizedX) 180 - floatArrayOf(10000f - normalizedX, 10000f - normalizedY) 270 - floatArrayOf(10000f - normalizedY, normalizedX) else - floatArrayOf(normalizedX, normalizedY) } } }这里要注意很多平台在竖屏时sensorOrientation是90但你看到的预览画面实际已经通过Surface旋转过了。如果你的预览TextureView是竖屏布局而传感器是横屏输出那么在触摸回调里拿到的screenX, screenY是竖屏坐标必须先映射到横屏传感器坐标再做归一化否则x和y是反的。验证坐标换算对不对的方法很土但很有效取一个纯色场景把touch ae的region设成一个小矩形然后观察预览画面里曝光最亮/最暗的位置是否就是你触摸的位置。如果不是用这个方法打印出换算后的region坐标和画面的实际位置对比就能快速定位是旋转、镜像还是缩放的问题。4.4 兼容不同平台的region权重策略最后是region权重。不同平台的ISP对多region合并的算法有差异。高通倾向取“所有region的加权平均”联发科某些平台则直接取“权重最大的region”。这意味着同样的参数在不同手机上表现可能完全不同。我的建议是触摸后不要保留两个region而是直接替换。touch ae生效时face ae的region不要追加进去而是清空。这样不管什么平台HAL都只会看到一套region不会出现“两套region打架”的情况。具体的Builder操作// touch ae生效时清空face region builder.set(CaptureRequest.CONTROL_AE_REGIONS, arrayOf(touchRegionOnly)) builder.set(CaptureRequest.CONTROL_AF_REGIONS, arrayOf(touchRegionOnly)) // 不要调用add每次都是全新的set如果产品需求确实希望“触摸对焦后人脸仍然参与测光”那就需要把两个region同时放到数组里并调整权重让触摸点占主导比如touch weight800face weight200。但这种方案在部分平台上有坑——人脸一旦移动旧的face region不会自动更新如果你不重新下发HAL会一直拿着旧人脸位置的region参与测光导致曝光偏差。所以做多region方案时必须保证人脸位置变化后立即重新下发整套region数组。5. 验证方法怎么确认face ae真的恢复了5.1 三个维度的量化验证修复完代码不能只凭肉眼确认“好像正常了”要做量化验证。我分了三个维度维度一AE_REGIONS回读确认。在CaptureCallback里回读CaptureResult.CONTROL_AE_REGIONS确认触摸后它是touch ae的region超时或人脸覆盖后它切换回face ae的region。写一个自动测试脚本模拟触控事件记录每次result的region变化时间点可以精确算出切换延迟。override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) { val aeRegions result.get(CaptureResult.CONTROL_AE_REGIONS) aeRegions?.let { val center calculateRegionCenter(it[0]) Log.d(AE_VERIFY, center$center, priority${manager.currentPriority}) } }维度二曝光值稳定性。让手机对准一个匀速转动的灯光场景记录touch ae超时前后的CONTROL_AE_EXPOSURE_COMPENSATION和SENSOR_EXPOSURE_TIME。如果face ae恢复成功曝光值会平滑过渡到以人脸为准的数值如果恢复失败曝光值会卡在触摸点的数值上。我当时的测试数据显示修复前曝光值在触摸后一直保持不变偏移约1.2EV修复后在3秒超时点开始变化约5帧内收敛到人脸区域的最优值。维度三人脸移动跟随。让人脸从触摸点位置横向移动到画面另一端验证曝光是否跟着人脸走。修复前人在左边和右边曝光值几乎一样说明还是touch ae的region在起作用修复后人脸移动时曝光值会随之调整虽然存在约2到3帧的滞后但整体趋势是跟随的。5.2 重点回归场景清单除了主流程我还整理了一份回归测试清单专门覆盖容易出问题的边缘场景触摸屏幕边缘左、右、上、下四个边界后人脸从边角出现确认region合法且能正常切回。快速连点屏幕多次确认不会出现region数组越界或者竞态。人脸离开画面再回来确认face ae能重新接管此时不应依赖touch ae超时而是靠人脸重新检测事件。相机前后摄切换后传感器方向变化确认坐标换算仍然正确。灭屏再亮屏、切后台再回前台确认状态管理器重置合理如果App杀了进程管理器会重新初始化如果只是切后台需要确认touch ae的timeout没有被后台时间影响。其中“切后台再回前台”这个case很隐蔽。如果App在后台停留了10秒回到前台时SystemClock.elapsedRealtime()是持续走的所以touch ae早已超时会正确切回face ae。但如果用的是System.currentTimeMillis()用户改了系统时间就会出问题。建议所有时间戳统一用elapsedRealtime()不要用wall clock。6. 常见问题与排查技巧实录6.1 为什么写了face ae的region曝光还是不对这个问题的答案有四种可能按优先级排查线程竞态导致face ae的region根本没提交成功——用回读值确认。HAL还在AE收敛中新region排队未生效——等3到5帧再观察。region坐标换算错误face的region落在了画面上其他位置——用我上面提到的“纯色场景验证法”排查。曝光补偿值被其他逻辑干扰——检查是否有其他地方在修改CONTROL_AE_EXPOSURE_COMPENSATION。6.2 为什么人脸框还在但face ae就是不肯接管最常见的原因是touch ae的timeout策略没写好。很多人的实现是“触摸后永远切不回face ae”因为他们只在触摸时更新了状态没有在检测到人脸时再次评估状态。我的方案里onFaceDetected中主动判断并切换就是为了解决这个问题。另一种可能是人脸检测回调的置信度太低一直被过滤掉。看看你的onFaceDetected是否被人脸检测的置信度阈值挡住了。当时我调试时就发现有些低光场景下人脸检测置信度降到0.3以下回调根本不会触发face ae自然就断了。这种情况下可以放宽置信度阈值或者在没有face ae时退回到“环境测光”而非“保持上一个触摸点测光”。6.3 触摸后画面频繁闪烁、曝光来回跳这是另一个我遇到的衍生问题。根因是我在touch ae和face ae之间切换时CONTROL_AF_MODE也跟着切换了导致对焦马达反复拉动曝光也跟着小幅跳动。后来我调整了策略触摸后只切AE_REGIONSAF_MODE保持不变只在真正需要重新对焦时才动AF相关参数。这样曝光不会因为对焦搜索而抖动。另外CONTROL_AE_PRECAPTURE_TRIGGER别乱用。这个trigger是给闪光灯场景用的正常拍照流程里不该在三方相机的预览阶段频繁触发。我一度用它来强制HAL重新收敛AE结果导致每次切换region都经历一次完整的precapture流程屏幕会先暗一下再亮回来。去掉之后切换平滑多了。6.4 代码层面容易忽略的三个细节第一request的repeating模式。如果用了setRepeatingRequest需要确认你提交的是修改后的builder的build结果而不是同一个builder对象反复build。Builder一旦build之后如果再次使用需要重新创建否则可能带上旧的状态。第二region的weight值。我见过有人把weight设成0想表示“不参与测光”结果HAL直接忽略整个region。weight合法范围是1到1000想不参与就干脆从数组里移除不要用0凑数。第三多摄场景下要指定正确的cameraId。如果是超广角镜头做face ae它的传感器坐标和主摄完全不同坐标换算必须按对应镜头的SENSOR_INFO_ACTIVE_ARRAY_SIZE来做。当时我们在做超广角人像模式时就踩到了这个坑表现为“主摄一切正常切超广角后人脸曝光立刻错乱”——其实都是坐标没换。6.5 排查工具的最终推荐做这个case期间我积累了一套比较顺手的排查工具链。Log层面强烈建议把CaptureResult里的关键字段做成周期性的抽样日志不要每个回调都打否则一秒钟几十条日志容易把关键信息冲掉。我一般用SystemClock.elapsedRealtime()加上序号每10帧打一次当前AE_REGIONS的center和曝光时间。图像层面可以开一个debug开关把当前生效的AE_REGIONS画到预览画面上一个半透明的矩形框。这样能直观地看到touch ae和face ae切换时region位置的变化。这个功能虽然简单但比纯看日志高效得多——毕竟日志里一串数字很难想象它对应画面上的哪个位置。最后说一点体会。这类AE策略问题表面上是“touch ae导致face ae失败”本质上是状态管理混乱。Camera开发里很多东西都是这样底层能力开放了但策略得自己拼。拼的时候只要有一个状态没想清楚就会在某个不常见的时序上炸出来。这次case的教训就是所有涉及状态切换的地方先画清楚状态机确认每个状态的进入和退出条件再写代码。touch ae和face ae之间的关系本来不复杂复杂的是你让它们无序竞争的那些瞬间。如果你也在做三方相机的人脸相关功能建议先把region回读验证机制搭好再谈策略优化。没有可靠的观测手段一切优化都是盲调。这个case做完之后我把ExposureModeManager沉淀成了项目里的通用组件后续做美颜、人像虚化、人脸追焦等功能时都能直接复用这套优先级切换逻辑。希望这篇文章能帮你少走几步弯路。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

俄罗斯网安巨头开源「AI黑客」整个Github 都炸锅了,挖洞只用一句话 2026/9/30 15:34:21

俄罗斯网安巨头开源「AI黑客」整个Github 都炸锅了,挖洞只用一句话

俄罗斯网安巨头开源「AI黑客」整个Github 都炸锅了,挖洞只用一句话 AI Agent 智能体 俄罗斯网安巨头开源「AI黑客」整个Github 都炸锅了,挖洞只用一句话 💡 核心摘要 你只输入一句人话,AI 就自己打开扫描器、自己跑检测、自己…

阅读更多 →
从灵魂到身体:在Clowder AI上构建第一个插件的完整开发指南 2026/9/30 15:34:20

从灵魂到身体:在Clowder AI上构建第一个插件的完整开发指南

从灵魂到身体:在Clowder AI上构建第一个插件的完整开发指南 【免费下载链接】clowder-ai Build AI teams, not just agents. Hard rails, soft power, shared mission. 项目地址: https://gitcode.com/gh_mirrors/cl/clowder-ai Clowder AI 是一个 AI 团队协…

阅读更多 →
8K 长距离视频传输方案探讨,GSV5600@ACP Serdes 芯片硬件选型笔记 2026/9/30 15:34:20

8K 长距离视频传输方案探讨,GSV5600@ACP Serdes 芯片硬件选型笔记

前言近期行业内 Serdes 高速互连技术持续迭代,车载域控、AI 工业视觉、专业视听领域,都面临高清视频远距离布线难题。传统 HDMI/DP 原生信号传输距离短,长线缆环境下信号衰减严重;采用光纤或者 CAT6 网线做 Serdes 串行传输&#…

阅读更多 →
【从0到1学习JVM · 01】JVM 本身根本不跨平台?拆解“一次编译到处运行”的假象与字节码存在的真正理由 2026/9/30 15:34:12

【从0到1学习JVM · 01】JVM 本身根本不跨平台?拆解“一次编译到处运行”的假象与字节码存在的真正理由

前言 学 JVM 的第一课,先得搞懂代码到底是怎么在机器上跑起来的。这篇文章拆解 Java“一次编译,到处运行”的执行过程,聊聊不同系统里 JVM 扮演的角色,以及为什么非要多一步编译出字节码。 文章目录前言一、Java 跨平台&#xff…

阅读更多 →
跨境电商主要模式有哪些?2026 四大主流模式全景盘点 2026/9/30 15:34:12

跨境电商主要模式有哪些?2026 四大主流模式全景盘点

摘要:跨境电商主要模式可归纳为 B2C、D2C、F2C、B2B 四大类型。本文逐一拆解这四种模式的载体、门槛、利润与风险,并给出 2026 年不同卖家的切入建议,帮你避开盲目入场的坑。 所谓跨境电商主要模式,本质上是「货从哪里来、卖给谁…

阅读更多 →
2026土耳其出境游新风向:从“走马观花”到深度体验,宜事旅游服务升级 2026/9/30 15:34:05

2026土耳其出境游新风向:从“走马观花”到深度体验,宜事旅游服务升级

2026年的出境游,正在从“去过哪里”逐渐转向“怎么去、怎么玩”。对于第一次前往土耳其的游客来说,伊斯坦布尔、卡帕多奇亚、安塔利亚、费特希耶、棉花堡等目的地分散在不同区域,城市之间涉及航班、用车、酒店以及当地体验的多重衔接。比起单…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉