This document describes where VitalSense is today, why the current BPM pipeline can fail, and which upgrades would most improve reliability and accuracy.
The goal is to move VitalSense from a basic webcam-based pulse estimator to a quality-aware rPPG system that can better tolerate lighting changes, motion, and weak facial signal conditions.
- Python
- Core code is organized into:
lighting.pysignal_extractor.pydsp_pipeline.py
From requirements.txt, the relevant stack includes:
opencv-python==4.13.0.92opencv-contrib-python==4.13.0.92mediapipe==0.10.32numpy==2.4.4scipy==1.17.1matplotlib==3.10.8sounddevice==0.5.5
Supporting packages also present:
requests==2.33.1pydantic==2.13.3google-genai==1.73.1websockets==16.0
For the BPM pipeline itself, the most important dependencies are OpenCV, MediaPipe, NumPy, SciPy, and Matplotlib.
This module acts as a signal quality gatekeeper before BPM extraction.
It currently checks:
- Mean luminance
- Brightness standard deviation
- Temporal luminance variance
It classifies frames as:
goodlow_lightbacklitflicker
This is the live webcam pipeline.
Current flow:
- Capture webcam frame
- Flip frame horizontally
- Classify lighting
- Run MediaPipe Face Landmarker
- Use forehead landmarks to build a ROI
- Compute the mean green channel from the ROI
- Store samples only when lighting and distance are acceptable
- After a 15-second window:
- bandpass filter the signal
- compute BPM using FFT peak selection
- calculate confidence
- update session min/max BPM
This module does the signal processing.
Current steps:
- Butterworth bandpass filtering in SOS form
- Zero-phase filtering with
sosfiltfilt - FFT-based BPM estimation
- Confidence based on dominant spectral peak vs average noise
- Static filter response plotting
The current pipeline is functional, but it is still a baseline rPPG implementation. Its main weaknesses are:
VitalSense currently extracts a simple mean green value from the forehead ROI.
- Sensitive to head motion
- Sensitive to ROI drift
- Sensitive to lighting changes
- Sensitive to skin tone variation
- Sensitive to camera auto-exposure and auto-gain
The pulse component can be overwhelmed by noise, especially when the subject moves slightly or the room lighting changes.
lighting.py uses fixed thresholds for:
- mean luminance
- luminance standard deviation
- temporal variance
- Thresholds are static
- They may not generalize across cameras, rooms, and skin tones
- They detect bad conditions, but they do not recover signal quality
The module can prevent bad frames from entering the pipeline, but it cannot make a weak pulse signal stronger.
The BPM estimator uses a dominant FFT peak in the cardiac band.
- Motion artifacts can create false peaks
- Harmonics can be mistaken for heart rate
- Short windows have poor frequency resolution
- A single spectral peak can be unstable
BPM may jump between windows or lock onto an incorrect frequency.
Current confidence is derived from a basic peak-to-noise ratio.
- Does not include motion quality
- Does not include ROI stability
- Does not include lighting quality
- Does not include consistency over time
A signal may appear confident even when the estimate is unstable.
Current output is window-by-window.
- No smoothing across windows
- No history-aware correction
- No suppression of sudden outliers
The BPM display can look noisy even when the true pulse is stable.
This section lists the most useful upgrades, in order of practical impact.
- Mean green-channel intensity from the forehead ROI
- POS (Plane-Orthogonal-to-Skin)
- CHROM
- ICA-based rPPG
These methods separate pulse-related color variation from motion and illumination artifacts much better than raw green-channel averaging.
- POS for practical webcam-based use
- CHROM as a strong alternative
- ICA if you want a separation-based method and can handle more complexity
- Better motion robustness
- Better lighting robustness
- Better BPM stability
- Forehead ROI from face landmarks
- Bounding rectangle over landmark points
- Track a more stable facial region over time
- Use forehead plus cheek ROIs
- Smooth landmark positions across frames
- Reject frames when ROI size or position changes too much
- Add motion quality scoring
The rPPG signal is very small. Even tiny ROI shifts can dominate the biological signal.
- Less noise from face movement
- More stable pulse extraction
- Better window consistency
- FFT peak picking in the cardiac band
- Welch Power Spectral Density
- Autocorrelation-based BPM estimation
- Peak tracking across time
- Harmonic suppression logic
- Spectral fusion of FFT + autocorrelation
Welch PSD reduces variance compared with a single FFT, especially in noisy signals.
Autocorrelation can detect periodicity even when the frequency spectrum is messy.
- Use Welch PSD as the primary estimator
- Optionally compare it with autocorrelation for consistency
- More stable BPM
- Better resistance to noisy windows
- Less sensitivity to a single bad spectral bin
- No history-aware BPM smoothing
- Median filter over the last N BPM values
- Exponential moving average
- Kalman filter for state tracking
True heart rate does not jump wildly from window to window. Smoothing helps reject isolated outliers.
- Start with a moving median
- Upgrade to a Kalman filter if you want stronger tracking
- Cleaner BPM display
- Less jitter
- Better user trust
- Lighting status
- Distance check
- FFT peak-to-noise confidence
- ROI stability
- Motion level
- Lighting quality
- Spectral peak dominance
- BPM consistency with previous windows
- Signal-to-noise ratio in the cardiac band
A real quality score should reflect both the environment and the signal itself.
- Better rejection of bad measurements
- More trustworthy BPM values
- Fewer false positives
- Output BPM if confidence exceeds a threshold
- Return BPM only when quality is high enough
- Otherwise:
- keep the previous trusted BPM
- or show “unreliable measurement”
It is better to show no value than to show a false value.
- Higher trust
- Better user experience
- Safer interpretation of measurements
- Keep
lighting.pyas a gatekeeper - Replace green-channel-only extraction with POS
- Keep the current bandpass filter
- Add a stronger confidence rule
- Replace raw FFT peak picking with Welch PSD
- Add harmonic rejection
- Add BPM smoothing across windows
- Add ROI stability metrics
- Add motion rejection
- Add a combined signal-quality score
- Calibrate thresholds using recorded sessions
- Build fallback behavior for poor-quality windows
- Log quality metrics for later tuning
| Upgrade | Problem Solved | Why It Helps |
|---|---|---|
| POS / CHROM | Motion and lighting noise | Extracts pulse more robustly than raw green |
| ROI stabilization | Landmark drift | Keeps the signal source consistent |
| Welch PSD | FFT instability | Reduces variance and noise sensitivity |
| Autocorrelation | Weak spectral peaks | Detects periodicity even when spectrum is messy |
| Temporal smoothing | BPM jitter | Prevents wild frame-to-frame changes |
| Quality scoring | False confidence | Combines multiple reliability signals |
| Quality-aware rejection | Bad readings | Avoids forcing an incorrect BPM |
Below are useful papers and references for rPPG and BPM estimation.
-
Verkruysse, Svaasand, Nelson (2008)
Remote plethysmographic imaging using ambient light- Early foundational work showing pulse can be measured from video.
-
Poh, McDuff, Picard (2010)
Non-contact, automated cardiac pulse measurements using video imaging and blind source separation- Introduced practical remote pulse estimation from facial video.
-
de Haan and Jeanne (2013)
Robust pulse rate from chrominance-based rPPG- Basis for the CHROM method.
-
Wang et al. (2017)
Algorithmic Principles of Remote PPG / POS-related work- Strong foundation for the POS method and rPPG signal separation.
-
de Haan and van Leest (2014)
Improved motion robustness of remote-PPG by using the blood volume pulse signal from the skin- Useful for motion robustness ideas.
-
Signal Quality Index / SQI literature for PPG
- Useful for building a confidence score beyond simple peak ratios.
-
Welch (1967)
The use of the fast Fourier transform for the estimation of power spectra- Basis for Welch PSD.
-
Kalman (1960)
- Useful for temporal tracking and smoothing of BPM estimates.
If you continue researching, search for:
- “POS rPPG implementation”
- “CHROM remote photoplethysmography”
- “Welch PSD heart rate estimation”
- “remote PPG motion artifact rejection”
- “rPPG signal quality index”
- “Kalman filter heart rate tracking”
The best practical path is:
-
Keep
lighting.py- It is valuable as a gatekeeper.
-
Replace raw green averaging
- Use POS or CHROM.
-
Improve BPM estimation
- Use Welch PSD instead of a single FFT peak.
-
Add temporal smoothing
- Use a median filter or Kalman filter.
-
Use quality-aware rejection
- Do not output BPM when the signal is not trustworthy.
This combination gives the best balance of:
- accuracy
- reliability
- implementation complexity
- maintainability
VitalSense currently has a solid baseline pipeline, but the biggest weakness is that it relies on a simple green-channel signal and a single FFT-based BPM estimate.
The most important improvements are:
- better rPPG extraction
- better spectral estimation
- better quality scoring
- better temporal smoothing
If those are implemented, VitalSense should become noticeably more reliable in real-world webcam conditions.