Long time, no blog posts. Four days short of two years, actually. It almost feels bad to ruin the imaginary anniversary party but luckily it's not illegal to have cake anyway.
So, today I was typing a IM and decided to deliver a mediocre joke in form of a hasty sketch. For the canvas I though I could take black photo by covering the camera of my phone with my palm. However, it turned out to be more difficult than I thought since the camera adjusted its light sensitivity so much that it picked up the light scattering from the inside of the palm making the picture always reddish in color.
This gave me an idea to test whether I can measure my heart rate by recording video from my palm. I put on the flashlight on my trusty Honor 6 and recorded a short video with the camera pressed against my palm. What I got was hundreds of frames looking like this:
Representative example frame of the raw video data |
Although one doesn't see much just looking at the video, a more careful analysis with Python reveals that there is in fact some interesting time behaviour hidden under all that redness. By taking the mean value of the red channel (with 8-bit value ranging from 0-255) over all 1920x1080 pixels, the following signal is obtained:
Mathematically every signal can be constructed by summing up a suitable set of sine waves. To find the multipliers and the phases of the waves, we use something called the Fourier transform. The Internet is full of excellent explanations what it is and how it works, for example this Youtube video by 3Blue1Brown, so I'm not going to go it through here. The important part here is to know that taking the absolute value of the Fourier transform and squaring it gives tells us how the power of the signal is distributed between the different frequency components of the signal.
To get of rid of the constant background which would cause a huge zero frequency component to the power spectrum, the mean value is subtracted from the red channel signal before taking the Fourier transform. The resulting power spectrum looks like this:
The first larger peak is now the one we are interested in, for it is the base frequency of the signal and thus corresponds to the heart rate. The peak value is close to 85 bpm (beats per minute) which actually matches very well the value measured by my (in)activity bracelet. Not bad, eh?
We also see another peak at 170 bpm which is twice the base frequency. The integer multiples of the base frequency, called harmonics, usually appear in the power spectrum of periodic signals, unless they are pure sine waves. However, interesting fact to me is that there is very little contribution to the signal from higher harmonics. The signal is thus mainly composed of the base frequency and its first harmonic with twice the base frequency. To me this makes sense since the blood pressure is mainly affected by ventricular chambers which either contract (systole) or relax (diastole). However, don't take this too seriously since I'm not a physician but a physicist, so there's a somewhat realistic chance that I might be oversimplifying the cardiovascular system a bit too much..
After a long break it was fun to write something to the blog again. I hope that the next update won't take quite as long. Lenk's quest is not dead either, and has even progressed a bit since the last time. If the stars align right, we might see the next part soon..
Below you find the Python code I used in the analysis of the video data:
from __future__ import division, print_function import numpy as np import matplotlib.pyplot as plt from moviepy.editor import VideoFileClip clip = VideoFileClip('source.mp4',audio=False) fps = clip.fps data = [] #Compute the mean value of red channel per frame for frame in clip.iter_frames(): data.append(np.mean(np.mean(frame[:,:,0],axis=1))) data = np.array(data) data = data[700:826] time = np.arange(data.size)/fps #time relative to frame number 700 #Calculate the Fourier power spectrum #The mean value is substracted in order to remove the zero frequency component fourier = np.abs(np.fft.fft(data - np.mean(data)))#**2 freq = np.fft.fftfreq(data.size,1/fps) plt.plot(time,data) plt.xlabel("Time (s)") plt.ylabel("Mean red channel signal") plt.figure() plt.axvline(x=85,label='Heart rate sensor (85 bpm)',color='red') plt.axvline(x=2*85,label='2x Heart rate sensor (170 bpm)',color='orange') plt.axvline(x=3*85,label='3x Heart rate sensor (255 bpm)',color='green') plt.plot(freq[:freq.size//2]*60,fourier[:freq.size//2],label="Fourier transform",color='blue',linewidth=2) plt.legend() plt.xlabel("Frequency (1/min)") plt.ylabel("Fourier power spectrum |FFT|$^2$") plt.show()