All notes

【TECH】From camera frames to a heartbeat signal

How a stream of red-channel measurements becomes a usable PPG waveform through timing, detrending, smoothing, and buffering.

signal processingPPGSwiftcamera frames
【TECH】From camera frames to a heartbeat signal

Once finger coverage is stable, the next temptation is to calculate BPM immediately. I tried that direction first. It produced numbers, but numbers are cheap. The useful thing is a signal that deserves to be measured.

A stream of red camera frames transforming into a pulse waveform
Each accepted frame contributes one timed red-intensity sample.

Every accepted frame contributes one red-intensity value and one timestamp. Everything else in this stage is about keeping that series honest.

Flow diagram Preparing diagram
View diagram source
flowchart LR
    A[Accepted camera frame] --> B[Red intensity sample]
    B --> C[Timestamped buffer]
    C --> D[Detrend baseline drift]
    D --> E[Smooth high-frequency noise]
    E --> F[Normalized waveform]
    F --> G[Peak detector]

Why raw red values are not enough

A fingertip is not held by a laboratory clamp. The user presses harder, then relaxes. The flash warms the skin. The camera exposure settles. The hand moves by a millimeter. All of those create slow changes in the red value. If we detect peaks directly on raw values, we end up detecting posture and exposure drift.

Keep time with the frame

It is easy to assume 30 fps and convert sample indexes into time. That works until the device drops frames or the processing queue skips late frames. MonoPump keeps presentation timestamps with samples and computes the effective sampling rate from the actual window.

Sequence diagram Preparing diagram
View diagram source
sequenceDiagram
    participant C as Camera
    participant B as Buffer
    participant P as Preprocessor
    C->>B: value at t0
    C->>B: value at t1
    C->>B: value at t2
    B->>P: values + timestamps
    P->>P: estimate real sample rate

BPM is a time measurement, not an array-index measurement. That one decision removes a class of subtle errors.

Detrending and light smoothing

The pulse is a relatively fast oscillation. Finger pressure and exposure drift are slower. MonoPump subtracts a local moving average from the signal, then applies a small moving average to reduce high-frequency pixel noise.

detrended[i] = signal[i] - average(signal around i)
Flow diagram Preparing diagram
View diagram source
flowchart TD
    R[Raw red series] --> D[Subtract local baseline]
    D --> S[3-point moving average]
    S --> N[Normalize for display and detection]
    N --> W[Waveform shown to user]
    N --> P[Peak detection input]

A beautiful waveform is not automatically a useful waveform. The goal is not a nice animation; the goal is preserving timing. At the end of this stage, MonoPump has a waveform for the UI, a cleaned signal for peak detection, and a quality score. A bad signal should not become a confident number.

From MonoWare

MonoPump has more context behind this note.

Open the product site or keep reading notes filtered to MonoPump.

Newsletter

Privacy and product engineering notes, sent occasionally.