PPG-ECG信号预处理与特征提取:可穿戴健康监测的数据管道 简介面向需要处理同步脉搏与心电信号的生物医学工程学习者这份压缩包提供了可直接运行的PPG与ECG预处理及特征提取方案。代码基于NeuroKit2库完成去噪并计算潮波幅值比h2/h1、重搏波幅值比h4/h1、收缩/舒张面积比S1/S、S2/S及主波高度、波形周期面积、脉搏传导时间PWTT等指标其中PWTT通过ECG与PPG之间渡越时间计算适合开展无创血压、动脉硬化等方向的前期研究。包内共43个文件主体为29个txt信号数据集另含2个Python脚本、8个ini配置与4个bak备份文件压缩后仅4.31MB轻量易部署。目前该资源已有744人学习下载适合希望在真实数据上复现预处理流程、快速提取多类脉搏特征的研究者参考。1. PPg-ECG预处理及特征提取可穿戴健康监测里最难啃的一段数据管道在可穿戴设备、临床监护和运动健康研究里PPG和ECG经常成对出现ECG给出精准的心脏电活动PPG给出外周血流灌注信息两者结合能算出脉搏波传导时间、心输出量趋势、血管弹性这类单信号拿不到的指标。但这个组合的落地成本远比想象中高——PPG对运动伪影和皮肤接触压力极其敏感ECG又容易被肌电和工频干扰污染两路信号在采集端的噪声机制完全不同拿到原始波形直接提特征特征里一半是“设备的脾气”而不是人体的生理状态。这篇文章适合那些手里已经有一批同步采集的PPG-ECG数据、下一步要做心率变异性分析或训练分类模型的人把从原始波形到干净特征之间的每一道工序讲透包括滤波器参数怎么定、时间对齐怎么做、哪些特征真的稳定可复现。2. 两个信号的物理本质不同决定了预处理思路不能共用2.1 PPG是光学信号ECG是电学信号噪声源完全不一样PPG的原理是光电容积脉搏波描记LED发出的光穿透或反射出组织后由光电二极管接收血容量的脉动变化会调制光的吸收强度。这个原理决定了它对探头压紧程度、皮肤颜色深浅、组织灌注状态、甚至手部微动作都高度敏感。常见可穿戴设备里PPG采样率一般在25到100 Hz之间信号主要能量集中在0.5到5 Hz但运动伪影的能量往往也落在这个频段这给后面的去噪埋了一个大坑。ECG则是记录心肌细胞去极化和复极化的电偶极子在体表产生的电位差信号幅度只有0.5到4 mV频谱范围从0.05 Hz到100 Hz以上。ECG的问题在于工频干扰50/60 Hz、肌电噪声20到500 Hz频谱和QRS波有重叠以及电极松动导致的基线漂移。临床诊断级ECG通常需要500 Hz采样率以保证QRS波的时限测量和ST段分析有足够的时间分辨率。这两个信号在预处理阶段必须分开对待PPG的核心矛盾是“运动伪影与真实脉搏信号频段重叠”ECG的核心矛盾是“工频干扰和肌电污染叠加在低频心电成分上”。我见过有人图省事把ECG那套带通滤波参数直接套到PPG上结果PPG的舒张期波峰被削平算出来的脉搏波传导时间整体偏移了几十毫秒。2.2 联合采集时的时间对齐采样率差异和硬件延迟比想象中麻烦同步采集设备通常会让PPG和ECG使用同一个时钟源但模拟前端的抗混叠滤波器和ADC转换会引入群延迟这个延迟对不同频率成分还不一样。如果设备手册没有给出明确的通道延迟补偿参数拿到数据后第一步就要做对齐校验。一个实用的对齐方法是用心跳事件做基准ECG的R波峰值和PPG的波峰之间有生理上固有的时间差脉搏波传导时间一般在50到200 ms之间但二者应当是逐拍对应的。先分别做R波检测和PPG峰值检测得到两列心跳时间戳计算相邻心跳间隔的相关性。如果相关系数低于0.9说明存在拍级错位如果两条心率曲线延迟了固定样本数则需要做固定偏移补偿。2.3 信号质量评估先把坏段挑出来再谈预处理很多预处理流程一上来就滤波这是顺序上的错误。滤波只能抑制频带外噪声对于接触不良、剧烈运动、传感器脱落造成的平坦段或饱和段任何滤波器都无能为力。正确的做法是在滤波前先做信号质量评估SQI把不可用的数据段标记出来后续只在质量合格的片段上做特征提取。我一般用三类SQI一是时域幅度范围检查PPG信号幅度在有效时段内应当有周期性波动若超过5秒没有明显的脉动波峰直接标记为坏段二是模板相关性检查将最近10秒信号和自适应更新的标准脉搏波模板做相关系数计算低于0.6的片段视为受运动干扰三是相邻心跳间隔合理性检查基于心率生理范围设置上下限比如30到200 bpm超出范围的时间戳需要复核。提示信号质量评估的值不要直接丢弃它本身是一类重要特征。很多睡眠分期模型里SQI的分布特征对区分清醒/浅睡/深睡有直接的判别力。3. 预处理流水线从原始波形到干净曲线的完整步骤与参数3.1 第一步滤波和去噪的参数整定分通道差异化处理ECG通道的重点是保留QRS波和ST段信息同时压掉肌电和工频。常见做法是零相位带通滤波带宽选0.5到45 Hz对50/60 Hz再加一个窄带陷波器。这里有一个容易被忽视的细节如果用IIR滤波器必须用filtfilt做零相位滤波否则群延迟会造成QRS波位置偏移几毫秒心率变异性分析对时间精度要求极高。0.5 Hz低切是底线再高会压低T波和ST段。PPG通道的滤波逻辑完全不同它的有用频段是0.5到5 Hz之间。低切0.1 Hz加上高切8到10 Hz的带通是常见选择但运动伪影往往恰好落在0.5到2 Hz区间这导致单纯滤波并不能彻底解决问题。先看一个基础的PPG滤波实现import numpy as np from scipy.signal import butter, filtfilt, iirnotch def bandpass_filter(data, fs, lowcut, highcut, order4): 零相位带通滤波器避免IIR滤波器造成的时间偏移 if lowcut 0: raise ValueError(低切频率必须大于0直流分量不能通过高通) nyquist 0.5 * fs low lowcut / nyquist high highcut / nyquist b, a butter(order, [low, high], btypeband) # 用 padlen 和 filtfilt 消除相位延迟 padlen min(3 * max(len(a), len(b)), data.shape[-1] - 1) filtered filtfilt(b, a, data, padlenpadlen) return filtered fs 125 # 常见可穿戴设备PPG采样率 ppg_raw np.random.randn(fs * 10) # 示例数据 # 实际处理时用真实采集数据替代 ppg_filtered bandpass_filter(ppg_raw, fs, lowcut0.5, highcut8.0, order4)这里有一个血泪经验order不要超过4。滤波器的阶数越高过渡带越窄但通带纹波和相位非线性越严重PPG波形的波峰位置会被非线性拉伸导致脉搏波传导时间计算出系统性偏差。滤波器的输出要和原始信号做一次差值对比正常情况下差值应在一定范围内如果出现明显的高频振荡说明截止频率设定过严或滤器阶数过高。3.2 第二步PPG运动伪影去除用自适应滤波补偿频段重叠问题PPG运动伪影去除是这个领域的研究重点工程上可落地、不依赖外部设备的情况下最可靠的方案是自适应滤波。自适应滤波的原理是利用一个与运动噪声相关的参考信号可穿戴设备里的加速度计信号或者通过独立成分分析从多路PPG中分离出的噪声分量不断调整滤波系数让参考信号经过加权后逼近噪声再从原始信号中扣除。最常用的自适应算法是LMS最小均方和RLS递归最小二乘。LMS计算量小适合嵌入式部署RLS收敛更快但计算复杂度高适合离线处理。实现时需要注意学习步长的选择过大会导致滤波器发散过小则收敛缓慢。一个简化版的LMS自适应滤波实现如下import numpy as np def lms_adaptive_noise_canceller(signal, reference, mu0.01, taps16): 信号清理signal - reference * w signal: 含噪的PPG信号 reference: 加速度计信号或噪声参考 mu: 步长取值过大容易发散实践中从0.01开始调 n_samples len(signal) w np.zeros(taps) output np.zeros(n_samples) for i in range(taps, n_samples): ref_block reference[i-taps:i] # 误差计算从含噪信号中减掉参考信号对噪声的估计 estimated_noise np.dot(w, ref_block) error signal[i] - estimated_noise output[i] error # LMS权重更新 w w 2 * mu * error * ref_block # 权重约束防止滤波器系数漂移过大 w np.clip(w, -1.0, 1.0) return output这段代码的关键是mu的选取。mu太大滤波器的权重更新就会震荡输出的波形里会出现周期性的噪声脉冲mu太小滤波器需要很长时间才能跟上运动状态的变化。我的经验是先用一段人为标注了运动干扰的数据做测试从0.01开始调观察输出波形的平滑度和心率检测的准确率找到合适的值后再固定下来。另外说一个容易忽略的点参考信号的质量直接决定自适应滤波的上限。如果参考信号本身和噪声相关性很弱比如加速度计没贴紧外壳测不到真实的运动频谱那么自适应滤波不仅去不掉噪声反而会把有用的脉搏波成分当作噪声减掉。离线分析的时候我会先用互相关计算参考信号和目标噪声的相关系数低于0.4就换一种去噪方案。3.3 第三步重采样与插值统一时间轴是特征提取的前提PPG和ECG即使在同一设备中采集采样率也可能不同——很多可穿戴设备PPG是25 HzECG是250 Hz。特征提取前必须统一时间轴。常见做法是以高采样率的ECG时间轴为基准把PPG重采样到和ECG相同的采样率。重采样的过程中要考虑抗混叠直接用线性插值会引入高频噪声先用低通滤波器把PPG限制在奈奎斯特频率以下再插值。对于数据缺失的片段传感器瞬间脱落不要用线性插值去填补因为线性插值会在波形上制造尖角后续的频谱分析会被这些尖角污染。我一般用三次样条插值并且在插值后的片段开头和结尾各留出一段过渡区标记为低置信度区域特征提取时跳过这些区域。下面是重采样和插值的参考实现from scipy.interpolate import CubicSpline def resample_ppg_to_ecg(ppg_signal, ppg_fs, ecg_fs, missing_maskNone): 将PPG信号重采样到ECG采样率 missing_mask: 标记坏段的布尔数组True表示该样本缺失 old_time np.arange(len(ppg_signal)) / ppg_fs new_time np.arange(int(len(ppg_signal) * ecg_fs / ppg_fs)) / ecg_fs if missing_mask is None: missing_mask np.zeros(len(ppg_signal), dtypebool) # 缺失段标记为NaN插值后这些位置数值不可信 ppg_work ppg_signal.copy().astype(float) ppg_work[missing_mask] np.nan cs CubicSpline(old_time[~missing_mask], ppg_work[~missing_mask]) ppg_resampled cs(new_time) # 重采样后的缺失区域重新标记 new_missing_mask np.interp(new_time, old_time, missing_mask.astype(float)) 0.5 return ppg_resampled, new_missing_mask有一个重要原则重采样的核心不是提高采样率本身而是保证PPG和ECG峰值检测的时间戳精度对齐。如果你打算做脉搏波传导时间计算两个信号的时间轴误差必须控制在1 ms以内这意味着重采样时不能简单四舍五入取整要保留浮点时间戳。225 Hz的PPG重采样后时间戳精度大约在4.4 ms这个精度对于PTT计算是不够的我一般会用插值到至少500 Hz再做峰值检测。3.4 第四步逐跳分割和峰值检测把连续波形切成单个周期完成信号清理和时间对齐后下一步是逐跳分割。ECG端用Pan-Tompkins算法或更简单的自适应阈值法检测R波PPG端则检测每个周期的波峰和舒张谷。峰值检测的精度决定了后续所有特征的质量——心率变异性指标里相邻心跳间隔的误差只要超过10 msSDNN的高频分量就会被显著低估。PPG的峰值检测比ECG麻烦得多。因为PPG波形受呼吸调制、外周血管张力和运动残留等因素影响波峰形态会受到干扰一个最常见的问题是反射波干扰——当血管弹性好时反射波可能接近甚至超过主波峰造成误检。解决方法是加一个最小的峰间隔约束并做模板匹配取最近20个心跳的平均形态作为模板每次检测结果和模板的相关系数低于0.7就认为该峰可疑标记为待复核。4. 特征提取从干净波形里拿出能放进模型的那组数4.1 时域特征均值、方差、和那些容易被忽略的波形形态参数时域特征是最容易想到的一类但不同特征的稳定性差别很大。均值心率、心率标准差这类统计量适合做长时段分析而逐拍的RR间期和PPG峰值间期则适合做短时变异性分析。这里有一个坑直接用原始波形的均值方差作为特征会隐含信号质量的干扰。如果某段数据的噪声水平较高处理后残余的伪影方差会压过生理方差。推荐一组对质量不敏感时域特征RR间期标准差SDNN、相邻RR间期差值的均方根RMSSD、RR间期差值大于50 ms的百分比PNN50、以及PPG波形的主波峰幅度变异系数。前三个是HRV时域分析的经典指标反映的是自主神经张力和绝对波形幅度无关。第四个反映外周血流灌注的稳定性但受传感器佩戴压力影响较大只适合在固定佩戴条件下比较同一个体的纵向变化。4.2 频域特征功率谱密度、频带能量和LF/HF比值频域特征需要先估计功率谱密度。常用的方法是Welch法它通过对分段加窗的信号做FFT再把各段结果平均能有效降低频谱估计的方差。分段长度和窗函数的选择会直接影响结果分段太长低频分辨率高但时间分辨率差分段太短低频段的估计方差大。我一般用4秒窗长、50%重叠频率分辨率大约0.25 Hz这对LF0.04-0.15 Hz和HF0.15-0.4 Hz的区分是足够的。频带能量的计算要归一化否则不同受试者的总功率差异会掩盖频段比例的变化。计算LF功率占LFHF总功率的比例比单独用LF的绝对值更稳定原因在于心率变异性的总功率受个体基础心率影响很大而频段比例的生理意义更明确LF/HF比值被大量研究用来反映交感/迷走神经张力平衡。4.3 非线性特征和HRV指标样本熵、近似熵的适用边界非线性特征这几年很受欢迎尤其在睡眠分期、压力识别和疲劳状态检测场景中。样本熵Sample Entropy是最常用的一个它度量时间序列的规则性数值越高表示信号越不规则。计算样本熵有两个关键参数嵌入维数m和相似容限r。m一般取2r取信号标准差的0.1到0.25倍。这两个参数对结果影响非常大不同研究中取值不统一导致结果可比性差因此在使用时必须在论文或报告里注明参数。样本熵对信号质量极其敏感运动伪影产生的尖峰会让熵值急剧升高从而掩盖真实的生理变化。所以非线性特征不应该单独使用而要和SQI特征配合只对高质量片段计算并且计算前建议做一次精细的去脉冲处理——用中值滤波剔除单个异常采样点这些点对熵值的贡献远大于对统计均值的影响。特征提取的一个关键思路是不要只提取单尺度的特征不同生理状态的信息分布在不同的时间尺度上。心率变异性的超低频分量0.003-0.04 Hz反映体温调节和肾素-血管紧张素系统的活动这部分信息包含在超过5分钟的长时段记录里。如果数据长度不够明确放弃超低频特征而不是强行用加长窗长的Welch估计去拼凑后者会产生大量不可靠的估计值。5. 避坑指南PPG-ECG预处理和特征提取的五个常见翻车点5.1 滤波造成波形畸变R波和PPG波峰位置系统性偏移现象滤波后的信号看上去很平滑但做峰值检测时R波位置和原始信号里的明显尖峰位置有几十毫秒的偏移心率变异性指标计算出来后与实测不符。原因使用了非零相位的IIR滤波器或者滤波器阶数过高。IIR滤波器是递归结构相位响应是非线性的不同频率成分在时间轴上被延迟的量不同这导致波形形状改变的同时峰位移动。解决滤波器一律使用scipy.signal.filtfilt做零相位滤波降低滤波器阶数到4阶以内对比滤波前后的峰值位置差如果超过一个采样周期说明相位畸变未消除。5.2 时间对齐只做了固定样本平移忽视了频率相关的群延迟现象ECG和PPG的心跳事件能对上但脉搏波传导时间的计算结果在心率变化时出现阶梯状跳变。原因模拟前端的抗混叠滤波器对不同频率成分的延迟不同固定样本数平移只能补偿某个频率点的延迟。心率为60 bpm时PPG峰值的能量集中在1 Hz附近心率为120 bpm时移到2 Hz附近群延迟随之变化。解决用已知频率的正弦波测试信号对采集链做延迟标定建立延迟-频率曲线或者逐拍计算PPG峰值与对应R波的相对时间差并检查该差值是否随心率变化若变化则采用基于插值的时变对齐。5.3 直接在坏段上提取特征把伪影当成了生理信号现象某些受试者的特征显著偏离生理范围比如LF/HF比值高达几十核查发现是有运动伪影片段混入后频谱被污染。原因预处理阶段没有提前标记坏段滤波后部分伪影仍然保留而这些片段恰恰产生极端的特征值。一个时长为总数据5%的坏段可以让SDNN抬高20%以上。解决在滤波前运行SQI评估生成一个坏段掩码特征提取循环遍历有效段并跳过掩码区域同时记录坏段占比作为独立特征坏段比例超过50%的样本在建模时直接剔除。5.4 HRV特征对窗口长度极其敏感但很多人用一个窗长打天下现象用30秒窗口算SDNN得到15 ms换5分钟窗口算同一段数据SDNN变成45 ms模型在不同窗口配置下结论相反。原因SDNN本身受记录时长影响——超低频分量需要长时间记录才能完整展现在方差里。30秒的短窗只有3到5个心拍SDNN估计方差极大。解决根据目标生理成分选择窗长。分析高频变异性呼吸性窦性心律不齐可以用1到2分钟窗口分析低频和超低频成分至少用5分钟理想情况是24小时。在发表的结论里明确标注窗长和采样率。5.5 特征之间的共线性没被处理一路带病进入模型现象随机森林模型的特征重要性输出里高度相关的两个特征重要性都虚高用A特征替换B特征后A的重要性暴涨模型可解释性完全丧失。原因PPG和ECG共同反映心血管系统时域特征和频域特征之间存在天然相关比如SDNN与总功率谱密度高度正相关RMSSD与HF频带功率几乎表述同一个生理过程。解决特征提取后对特征矩阵做相关性分析两两相关系数超过0.95的特征只保留一个或者改用PCA / PLS这类压缩方法把共线特征投影到正交空间后再作为模型输入。6. 上线前先验证用模拟数据和交叉验证保住特征可靠性特征提取方案跑通了以后别急着一股脑训练模型。先做三件事验证特征的可靠性。第一件事是模拟信号验证用已知频率和幅值生成一段合成PPG叠加呼吸调制和ECG标准QRS形态对比提取出的心率、呼吸频率与真值的误差。如果误差超过0.5%说明峰值检测和频谱估计环节仍有系统性问题需要排查。第二件事是数据切分验证将生理数据按时间段切分成前一半和后一半分别在两个子集上提取特征计算同一受试者特征对数值的组内相关系数。以SDNN为例长时HRV特征的组内相关系数应该在0.8以上。如果同一受试者前后两个时段的特征差异过大原因通常不是生理波动而是采集环境发生了变化比如佩戴松紧度差异大。这种情况下要对特征做佩戴不敏感化处理比如把时域特征和信号幅度特征合并成一个比值。第三件事是坏段注入测试从干净数据中截取一段MAP平均动脉压已知的信号人为叠加不同强度、不同频率的运动伪影观察特征在伪影强度梯度下的变化轨迹。一个有用的做法是把特征输出作为伪影强度的函数曲线如果曲线斜率在早期就剧烈变化说明特征对伪影的鲁棒性不足如果斜率平缓、只在高强度时才偏移说明预处理流水线的冗余度是够的。我个人的习惯是在每次特征提取跑完以后额外输出一份质量报告包含每段信号的SQI分布、峰值检测错误率、坏段占比、滤波前后的频谱能量比。这些附加信息在模型上线出现性能漂移的时候能省掉大量排查时间。以前做过一个疲劳监测项目模型在部署后第两周开始出现心率误判翻遍算法逻辑都没找到问题最后回头看质量报告发现是使用者换了佩戴位置导致PPG信号质量全面下降——质量报告里的SQI分布图一眼就定位了根因。从那以后任何特征提取流程我都默认附带质量报告这不是可选项是必需品。特征提取的方向本质上做的是“信息压缩”把冗长的波形压缩成少量稳定、可解释、可跨个体比较的数值。判断这条流水线是否合格不看处理了多少数据而看压缩后的信息有多少能在真实场景中复现。希望这套流程能帮你少走几段弯路。本文还有配套的精品资源点击获取