All notes

【TECH】Camera PPG: why a phone camera can see a pulse

A practical explanation of how MonoPump turns camera frames, flash light, and blood-volume changes into a pulse signal.

PPGcameraiOSheart rate
【TECH】Camera PPG: why a phone camera can see a pulse

The first time I tried to measure heart rate with a phone camera, the surprising part was not the algorithm. It was how visible the signal already was once the finger covered the lens and the flash. The preview looked like a soft red blur, but a tiny rhythm was hiding inside it.

A fingertip covering a phone camera and flash with a soft red glow
The camera is used as a small optical sensor, not as a normal camera.

MonoPump starts from that rhythm. The app is not recognizing a heart from a photograph. It uses the camera as a cheap optical sensor: shine light into the fingertip, watch how the reflected red channel changes, and estimate the beat interval from that change.

The observation that made it useful

When the flash is on and a fingertip covers the camera, blood absorbs and scatters light differently as vessels expand and relax. One frame is not meaningful. A few hundred frames form a waveform.

Flow diagram Preparing diagram
View diagram source
flowchart LR
    A[Flash illuminates fingertip] --> B[Blood volume changes with each beat]
    B --> C[Red-channel intensity shifts frame by frame]
    C --> D[Build a time-series signal]
    D --> E[Detect peaks and estimate BPM]

I expected the hard problem to be advanced signal processing. In practice, the earlier problem was more basic: the signal only exists when the finger covers the lens in a usable way. If the user floats the finger, presses too hard, leaks light from the side, or lets auto-exposure keep hunting, the waveform becomes a story about the camera, not the pulse.

Camera first, algorithm second

The capture path is deliberately plain: back wide-angle camera, BGRA frames, 30 fps, torch on at a moderate level, then exposure locked after the light settles. If exposure keeps adapting during the measurement, it can erase the small brightness changes we are trying to measure.

Sequence diagram Preparing diagram
View diagram source
sequenceDiagram
    participant UI as Measurement UI
    participant C as CameraManager
    participant F as Frame Analyzer
    UI->>C: request camera frames
    C->>C: configure 30 fps BGRA output
    C->>C: enable torch and lock exposure
    C-->>F: CMSampleBuffer stream
    F-->>UI: coverage, waveform, confidence

A frame is not a heart-rate sample by itself. MonoPump samples the center region and computes average red, green, blue, brightness, red ratio, red dominance, and the fraction of pixels where red clearly leads.

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

The important trick is to separate two questions. Is this a plausible fingertip frame? If it is, what scalar value should join the pulse signal? Mixing those questions lets bad coverage frames poison the waveform.

The boundary

Camera PPG is useful, but it is not magic. Skin tone, finger pressure, temperature, camera hardware, flash heat, motion, and ambient leakage all affect the signal. MonoPump treats the result as a descriptive estimate with quality and confidence, not as a medical conclusion.

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.