全部文章

【TECH】如何把摄像头帧变成心跳信号

MonoPump 如何通过时间戳、去趋势、轻度平滑和缓冲窗口,把红色通道序列变成可用的 PPG 波形。

信号处理PPGSwift摄像头帧
【TECH】如何把摄像头帧变成心跳信号

当手指覆盖稳定之后,最容易犯的错就是立刻算 BPM。我最初也这么试过,确实能得到数字,但数字很便宜。真正有价值的是一条值得被测量的信号。

一串红色摄像头帧逐步变成脉搏波形
每个通过检测的帧,都会贡献一个带时间戳的红色强度样本。

每一帧贡献一个红色强度值和一个时间戳,这一层后面的处理,都是为了让这条序列更诚实。

Flow diagram Preparing diagram
View diagram source
flowchart LR
    A[通过覆盖检测的相机帧] --> B[红色强度样本]
    B --> C[带时间戳的缓冲区]
    C --> D[去除基线漂移]
    D --> E[平滑高频噪声]
    E --> F[归一化波形]
    F --> G[峰值检测]

原始红色值不能直接用

指尖不是固定在实验夹具上的。用户会稍微按紧,又稍微放松;闪光灯会让皮肤变热;曝光会逐渐稳定;手会移动一毫米。这些都会让红色值产生慢变化。如果直接在原始值上找峰,找到的可能是姿势和曝光,而不是心跳。

用真实时间,不要只相信 30fps

默认 30fps 再用样本下标换算时间很简单,但真实手机会丢帧,处理队列也会丢弃迟到帧。MonoPump 保存每个样本的 presentation timestamp,并用实际窗口估算采样率。

Sequence diagram Preparing diagram
View diagram source
sequenceDiagram
    participant C as Camera
    participant B as Buffer
    participant P as Preprocessor
    C->>B: t0 时刻的值
    C->>B: t1 时刻的值
    C->>B: t2 时刻的值
    B->>P: 值 + 时间戳
    P->>P: 估算真实采样率

BPM 是时间测量,不是数组下标测量。这个选择能消掉一类很隐蔽的错误。

去趋势和轻度平滑

脉搏是相对快的振荡,手指压力和曝光漂移更慢。MonoPump 用局部移动平均作为基线,从原始信号里减掉它,再用很小的移动平均压掉高频像素噪声。

detrended[i] = signal[i] - average(signal around i)
Flow diagram Preparing diagram
View diagram source
flowchart TD
    R[原始红色序列] --> D[减去局部基线]
    D --> S[3 点移动平均]
    S --> N[归一化]
    N --> W[显示给用户的波形]
    N --> P[峰值检测输入]

漂亮波形不等于有用波形。目标不是动画好看,而是保住时间信息。最终这一层会产出给界面看的波形、给峰值检测用的清洗信号,以及质量分。坏信号不应该变成一个看起来很确定的数字。

来自 MonoWare

MonoPump 背后还有更多上下文。

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

通讯

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