手指检测的第一个朴素版本很诱人:画面变红,就说明手指在。这个判断在桌面上测试五分钟没问题,但手机角度一变就露馅。红色物体、暗房间、边缘漏光,都可能骗过单一阈值。

所以问题要收窄:一帧可用于测量的指尖画面到底长什么样?我们怎样拒绝那些只是看起来有点红的画面?
不再相信单个数字
真正的手指覆盖画面不是只有红这一个特征。它应该有合适亮度,不能过暗也不能过曝;红色通道要领先绿色和蓝色;而且要有足够比例的像素呈现红色占优。MonoPump 把这些线索合起来判断。
This diagram could not be rendered. Its source is still available below.
View diagram source
flowchart TD
A[采样画面中心区域] --> B[计算平均 RGB]
B --> C[亮度是否在可用范围内]
C -- 否 --> D[太暗或太亮]
C -- 是 --> E[红色比例是否足够]
E -- 否 --> F[没有手指]
E -- 是 --> G[红色占优是否稳定]
G -- 较弱 --> H[覆盖较弱]
G -- 较强 --> I[覆盖良好]
只采样中心区域是有意为之。预览边缘更容易受到漏光、手指弧度和移动影响。采中心区域,更像是在模拟一个小型接触式传感器。
真正有用的几个指标
MonoPump 直接从 BGRA 像素缓冲区计算几个低成本指标,因为这件事每一帧都要做。
brightness = (avgRed + avgGreen + avgBlue) / 3
redRatio = avgRed / max((avgGreen + avgBlue) / 2, 1)
redDominance = avgRed / max(avgGreen, avgBlue)
dominantRedPixelFraction = redDominantPixels / sampledPixels
亮度负责拦截明显失败。红色比例用来确认指尖带来的颜色偏移。红色占优像素比例则防止少数亮红点欺骗检测器。
良好、较弱、没有,比是和否更有用
界面需要的不只是布尔值。如果 App 只说未检测到,用户不知道该按紧一点、放松一点、遮住闪光灯,还是等一等。MonoPump 把覆盖分成几个可解释状态:没有手指、太暗、太亮、覆盖较弱、覆盖良好。
This diagram could not be rendered. Its source is still available below.
View diagram source
stateDiagram-v2
[*] --> none
none --> weakCoverage: 出现红色但信号弱
weakCoverage --> good: 红色占优稳定
good --> weakCoverage: 指压或位置变化
weakCoverage --> none: 红色比例下降
none --> tooDark: 亮度低于下限
none --> tooBright: 亮度高于上限
最后还要做连续帧平滑。一帧坏数据不应该让整个测量重置,一帧好数据也不应该立刻开始。阈值放松会提高成功率,但坏信号也更容易混进来;阈值严格会提高置信度,但真实用户更容易失败。更好的边界,是只有当接下来的几秒大概率能产出可用波形时才开始。