心率提取,FFT
rPPG 信号提取: 从面部视频中提取 rPPG 信号,通常是通过对视频帧中的面部区域进行颜色通道分析,提取出反映血液容积变化的信号。
信号预处理: 对提取的 rPPG 信号进行滤波、归一化等预处理操作,以去除噪声和干扰。
心率计算: 使用频域分析方法(如快速傅里叶变换)或时域分析方法(如峰值检测)从预处理后的信号中计算心率。
注意点:
- 计算心率时,同时计算最大峰和次大峰,处理信号中可能存在的谐波干扰。
- 谐波干扰: 当心脏跳动时,会产生一个基频信号,对应着实际的心率。然而,由于身体的生理结构、测量设备的特性或者环境干扰等因素,信号中可能会出现基频信号的整数倍频率成分,即谐波。
- 在某些情况下,谐波的幅值可能会比基频信号的幅值更大。故说明最大峰对应的频率很可能是次大峰对应频率的谐波,此时次大峰对应的频率更有可能是实际心率对应的基频。
实现代码:
def hr_fft(sig, fs, harmonics_removal=True):
"""
计算心率时,同时计算最大峰和次大峰,处理信号中可能存在的谐波干扰。
谐波干扰:当心脏跳动时,会产生一个基频信号,对应着实际的心率。然而,由于身体的生理结构、测量设备的特性或者环境干扰等因素,信号中可能会出现基频信号的整数倍频率成分,即谐波。
"""
# get heart rate by FFT
# return both heart rate and PSD
# 信号展平
sig = sig.reshape(-1)
# 汉宁窗对信号加窗处理,减少频谱泄露
sig = sig * signal.windows.hann(sig.shape[0])
# 对加窗后信号应用fft
sig_f = np.abs(fft(sig))
# 计算心率可能出现的低频高频截止频率对应的索引
low_idx = np.round(0.6 / fs * sig.shape[0]).astype('int')
high_idx = np.round(4 / fs * sig.shape[0]).astype('int')
# 保存原始频谱幅值
sig_f_original = sig_f.copy()
# 将低于低频截止频率和高于高频截至频率幅值归0
sig_f[:low_idx] = 0
sig_f[high_idx:] = 0
# 查找频谱中的峰值
peak_idx, _ = signal.find_peaks(sig_f)
# 对峰值对应的频谱幅值排序
sort_idx = np.argsort(sig_f[peak_idx])
sort_idx = sort_idx[::-1]
# 获取最大峰和次大峰的对应索引
peak_idx1 = peak_idx[sort_idx[0]]
peak_idx2 = peak_idx[sort_idx[1]]
# 计算最大峰对应频率并转化为心率
f_hr1 = peak_idx1 / sig.shape[0] * fs
hr1 = f_hr1 * 60
# 计算次大峰对应频率并转化为心率
f_hr2 = peak_idx2 / sig.shape[0] * fs
hr2 = f_hr2 * 60
# 若进行谐波去除,则需要检查最大心率是否接近次大心率的二倍
if harmonics_removal:
if np.abs(hr1 - 2 * hr2) < 10:
hr = hr2
else:
hr = hr1
else:
hr = hr1
x_hr = np.arange(len(sig)) / len(sig) * fs * 60
return hr, sig_f_original, x_hr