Kotlin集成Google ML Kit实现人脸环形录入动作检测卡顿问题
Google ML Kit人脸环形录入检测问题排查
问题背景
- 开发目标:基于Google ML Kit实现类iOS的人脸环形转动录入效果
- 参考效果:

- 当前进度:除环形动作检测逻辑外,其余功能均开发完成
- 现存异常:现有角度判断逻辑存在检测卡顿问题,仅能识别部分左转、右转动作,整体检测流畅度远低于iOS Face ID效果
- 现有判断代码:
private fun Direction(): Boolean { if (angley in 20f..25f && anglez in 2.0f..6.0f) { return true } return false }
- 参数来源:
angley取自ML Kit返回的face.headEulerAngleY字段,anglez取自ML Kit返回的face.headEulerAngleZ字段
当前方案核心缺失点
- 角度区间设置不合理
写死的20°25°(Y轴)、2°6°(Z轴)区间范围过窄,ML Kit实时返回的欧拉角本身存在±3°左右的检测抖动,加上不同用户握持手机的姿态、转动幅度存在差异,窄区间很容易出现角度短暂超出范围的情况,直接导致检测卡顿、漏识别。 - 检测维度缺失
环形转动是头部沿圆周方向的连续动作,需要同时覆盖左转/右转(Y轴)、抬头/低头(X轴)、左右倾斜(Z轴)三个维度的角度变化,现有逻辑仅判断Y、Z轴的单段固定角度,完全没有接入headEulerAngleX的抬头低头角度判断,无法覆盖完整环形路径。 - 缺少噪声处理
视频流逐帧返回的原始角度值存在高频跳变噪声,直接用原始值做判断会导致检测状态反复横跳,视觉上表现为卡顿、识别不跟手。 - 无连续动作校验逻辑
现有逻辑仅做单帧角度匹配,没有记录动作流转状态,既无法判断用户是否沿环形路径连续转动,也无法避免非预期动作的误触发,本质上只能识别固定角度的单次偏头,完全不满足环形录入的检测需求。
可落地优化方向
- 放宽角度阈值容差
取消硬编码的窄区间限制,每个动作节点设置10°以上的容差范围,比如左右转Y轴阈值设为18°32°,Z轴倾斜容差放宽到-10°10°,适配不同用户的使用习惯和检测抖动。 - 拆分环形检测节点
将完整环形路径拆分为正视、左转、左下低头、正低头、右下低头、右转、抬头回到正视共7个连续检测节点,每个节点配置X/Y/Z三轴对应的角度阈值,覆盖完整转动路径。 - 增加角度平滑滤波
维护一个长度为3~5帧的滑动窗口,对窗口内的三轴角度做加权平均计算,过滤单帧异常跳变的噪声值,保证检测状态稳定。 - 实现状态机流转逻辑
维护当前检测进度的状态变量,只有当平滑后的角度匹配当前节点的阈值要求时,才将进度流转到下一个节点,直到走完所有节点判定环形录入完成,从根本上解决漏判、误判问题。最简状态机实现参考:
// 环形检测节点枚举 enum class CircleDetectNode { INIT_FRONT, LEFT_TURN, LEFT_LOWER, HEAD_LOWER, RIGHT_LOWER, RIGHT_TURN, FINISH } // 当前检测节点,初始为正视校验 private var currentNode = CircleDetectNode.INIT_FRONT // 角度滑动窗口缓存,用于平滑计算 private val angleWindowX = mutableListOf<Float>() private val angleWindowY = mutableListOf<Float>() private val angleWindowZ = mutableListOf<Float>() private const val WINDOW_SIZE = 4 private fun checkDetectProgress(angleX: Float, angleY: Float, angleZ: Float): Boolean { // 维护滑动窗口 angleWindowX.add(angleX) angleWindowY.add(angleY) angleWindowZ.add(angleZ) if (angleWindowX.size > WINDOW_SIZE) { angleWindowX.removeFirst() angleWindowY.removeFirst() angleWindowZ.removeFirst() } // 计算平滑后的角度值 val smoothX = angleWindowX.average().toFloat() val smoothY = angleWindowY.average().toFloat() val smoothZ = angleWindowZ.average().toFloat() // 按当前节点匹配阈值,匹配成功则流转到下一节点 return when(currentNode) { CircleDetectNode.INIT_FRONT -> { if (kotlin.math.abs(smoothX) < 6 && kotlin.math.abs(smoothY) < 6) { currentNode = CircleDetectNode.LEFT_TURN true } else false } CircleDetectNode.LEFT_TURN -> { if (smoothY in 18f..32f && kotlin.math.abs(smoothZ) < 10f) { currentNode = CircleDetectNode.LEFT_LOWER true } else false } // 剩余节点按对应角度阈值依次实现流转逻辑即可 else -> false } }
内容的提问来源于stack exchange,提问作者EMT
相关产品推荐
相关产品推荐

