全部文章

【TECH】摄像头 PPG:为什么手机能看见脉搏

从工程角度解释 MonoPump 如何把闪光灯、摄像头帧和血容量变化转成一条可计算的脉搏信号。

PPG摄像头iOS心率
【TECH】摄像头 PPG:为什么手机能看见脉搏

第一次用手机摄像头测心率时,我真正意外的不是算法,而是信号居然已经在画面里了。手指盖住镜头和闪光灯后,预览画面只是一片柔和的红色,但连续看几百帧,会发现红色强度在轻微起伏。

手指覆盖手机摄像头和闪光灯并产生柔和红光
这里的摄像头更像一个小型光学传感器,而不是普通相机。

MonoPump 的起点就是这个现象。它不是在照片里识别心脏,而是把摄像头当作光学传感器:用闪光灯照亮指尖,观察血液流动带来的红色通道变化,再从变化周期估算 BPM。

先看到现象,再谈算法

当手指覆盖摄像头时,闪光灯的光进入指尖。每一次心跳都会带来血容量变化,光的吸收和散射也随之变化。单帧没有意义,连续帧才会组成波形。

Flow diagram Preparing diagram
View diagram source
flowchart LR
    A[闪光灯照亮指尖] --> B[血容量随心跳变化]
    B --> C[红色通道逐帧轻微波动]
    C --> D[形成时间序列信号]
    D --> E[检测峰值并估算 BPM]

我一开始以为难点会是复杂 DSP。后来发现,更早的难点是信号到底有没有资格被拿来算。如果手指没盖严、漏光、用力不稳定,或者相机曝光一直自动调整,最终得到的就不是脉搏,而是相机和手指姿势的混合噪声。

先稳住相机,再处理信号

MonoPump 的采集链路刻意保持简单:后置广角摄像头、BGRA 像素帧、30 fps、打开闪光灯,并在光线稳定后锁定曝光。锁曝光很关键,因为自动曝光如果持续变化,会把我们要观察的微小亮度波动补偿掉。

Sequence diagram Preparing diagram
View diagram source
sequenceDiagram
    participant UI as 测量界面
    participant C as CameraManager
    participant F as 帧分析器
    UI->>C: 请求相机帧
    C->>C: 配置 30 fps BGRA 输出
    C->>C: 打开闪光灯并锁定曝光
    C-->>F: 发送 CMSampleBuffer
    F-->>UI: 覆盖状态、波形、置信度

一帧图像本身不是心率样本。MonoPump 采样画面中心区域,计算平均 R/G/B、亮度、红色比例、红色占优程度,以及红色明显领先的像素比例。

redRatio = avgRed / max((avgGreen + avgBlue) / 2, 1)
redDominance = avgRed / max(avgGreen, avgBlue)
redIntensity = redRatio * (avgRed / 255.0)

这里最重要的是拆开两个问题:这一帧像不像手指覆盖镜头的画面;如果像,它应该贡献什么信号值。早期把这两个问题混在一起,坏帧也会进入波形,后面再怎么滤波都很被动。

摄像头 PPG 有价值,但它不是魔法。肤色、指压、温度、相机硬件、闪光灯发热、环境漏光都会影响结果。所以 MonoPump 把结果定位为带质量分和置信度的描述性估算,而不是医疗诊断。

来自 MonoWare

MonoPump 背后还有更多上下文。

打开产品网站,或继续阅读按 MonoPump 筛选的笔记。

通讯

隐私与产品工程笔记,偶尔发送。