Source code for neurokit2.video.video_ppg

import numpy as np

from ..misc import progress_bar
from .video_face import video_face
from .video_skin import video_skin


[docs] def video_ppg(video, sampling_rate=30, verbose=True): """**Remote Photoplethysmography (rPPG) from Video** Extracts the photoplethysmogram (PPG) from a webcam video using the Plane-Orthogonal-to-Skin (POS) algorithm. .. note:: This function is experimental and does NOT seem to work at all (https://github.com/DominiqueMakowski/RemotePhysiology). If you are interested in helping us improve that aspect of NeuroKit (e.g., by adding more detection algorithms), please get in touch! Parameters ---------- video : np.ndarray A video data numpy array of the shape (frame, channel, height, width). sampling_rate : int The sampling rate of the video, by default 30 fps (a common sampling rate for commercial webcams). verbose : bool Whether to print the progress bar. Returns ------- np.ndarray A PPG signal. Examples -------- .. ipython:: python import neurokit2 as nk # video, sampling_rate = nk.read_video("video.mp4") # ppg = nk.video_ppg(video) References ---------- * Wang, W., Den Brinker, A. C., Stuijk, S., & De Haan, G. (2016). Algorithmic principles of remote PPG. IEEE Transactions on Biomedical Engineering, 64(7), 1479-1491. """ # Initialize heart rate ppg = np.full((len(video)), np.nan) # Chunk into 8 second segments (5 * 1.6 which is the temporal smoothing window) chunk_size = int(sampling_rate * 8) for _, start in progress_bar(np.arange(0, len(video), chunk_size), verbose=verbose): end = start + chunk_size if end > len(video): end = len(video) ppg[start:end] = _video_ppg(video[start:end, :, :, :], sampling_rate, window=1.6) return ppg
# ============================================================================== # Internals # ============================================================================== def _video_ppg(video, sampling_rate=30, window=1.6): # 1. Extract faces faces = video_face(video, verbose=False) rgb = np.full((len(faces), 3), np.nan) for i, face in enumerate(faces): # 2. Extract skin mask, masked_face = video_skin(face) # Extract color r = np.sum(masked_face[:, :, 0]) / np.sum(mask > 0) g = np.sum(masked_face[:, :, 1]) / np.sum(mask > 0) b = np.sum(masked_face[:, :, 2]) / np.sum(mask > 0) rgb[i, :] = [r, g, b] # Plane-Orthogonal-to-Skin (POS) # ============================== # Calculating window (l) window = int(sampling_rate * window) H = np.full(len(rgb), 0) for t in range(0, (rgb.shape[0] - window)): # 4. Spatial averaging C = rgb[t : t + window - 1, :].T # 5. Temporal normalization mean_color = np.mean(C, axis=1) try: Cn = np.matmul(np.linalg.inv(np.diag(mean_color)), C) except np.linalg.LinAlgError: # Singular matrix continue # 6. Projection S = np.matmul(np.array([[0, 1, -1], [-2, 1, 1]]), Cn) # 7. Tuning (2D signal to 1D signal) std = np.array([1, np.std(S[0, :]) / np.std(S[1, :])]) P = np.matmul(std, S) # 8. Overlap-Adding H[t : t + window - 1] = H[t : t + window - 1] + (P - np.mean(P)) / np.std(P) return H