信息速览
腕带式心率设备的光学原理很简单:发光二极管照进皮肤,光电二极管接收反射光,血管随心跳充盈与排空,反射光量随之起伏。静止时这条曲线干净得像教科书插图,但人一开始走路、跑步、蹬车,手腕每一下摆动都会把它扯得面目全非。真正决定一块手表准不准的,不是静止时有多准,而是动起来之后还能不能找回那个周期。这个数据集就是为这件事准备的:8 名健康成年人在跑步机和健身车上运动,左腕戴光电容积脉搏波传感器与惯性测量单元,胸口贴两片电极记录心电,两者同步下来,作为判断腕部算法对错的参考答案。
它的价值不在规模。19 条记录、约两小时,放在深度学习语境里算不上大;价值在于配置完整:同一时刻腕部有 PPG、三轴低噪声加速度计、三轴宽量程加速度计、三轴陀螺仪与三轴磁力计,胸前有单导联 ECG 与人工逐拍标注的 R 峰时间,四种运动强度从悠闲散步铺到高阻力蹬车。这种「光学 + 惯性 + 电生理」三线并行的录制在 2017 年前后是可数的。
需要先把两件事说清楚。第一,它不是加速度计数据集,主信号是 PPG;第二,它不涉及帕金森或其他运动障碍,8 名参与者都是 22–32 岁的健康志愿者,页面没有疾病标签,也没有任何临床评分。一屏速览见下表。
| 维度 | 内容 |
|---|---|
| 官方全名 | Wrist PPG During Exercise |
| 发布方 | PhysioNet,版本 v1.0.0,2017 年 10 月 20 日上线 |
| 采集方 | 曼彻斯特大学电子与电气工程学院,Alexander J. Casson 课题组 |
| 配套论文 | Jarchi & Casson, Data 2017, 2(1), 1 |
| 受试者 | 8 名健康成年人,3 男 5 女,年龄 22–32 岁,均值 26.5 |
| 记录数 | 19 条 WFDB 记录,按受试者编号与活动类型命名 |
| 记录总时长 | 约一百一十三分钟,单条 3 分 40 秒至 9 分 49 秒 |
| 腕部设备 | Shimmer 3 GSR+ 模块,PPG 传感器与惯性单元刚性粘接后戴于左腕 |
| 腕部信号 | PPG 1 路 + 陀螺仪 3 路 + 低噪声加速度计 3 路 + 宽量程加速度计 3 路 + 磁力计 3 路 + 时间戳 1 路 |
| 胸部参考 | Actiwave 单通道双电极 ECG,附带人工标注 R 峰时间 |
| 采样率 | 全部信号 256 Hz,腕部实际平均约 255.7 Hz |
| 运动类型 | 步行、跑步、低阻力骑行、高阻力骑行四种 |
| 数据格式 | WFDB,每条记录 .hea + .dat + .atr 三件套 |
| 许可 | ODC-BY 1.0,署名即可商用 |
| DOI | 10.13026/C2PQ1X |
§1 它要解决的具体麻烦
1.1 光学心率在静止时近乎无懈可击,动起来就散架
把 PPG 的困境讲清楚,才谈得上判断这个库值不值得拿。腕部反射式 PPG 的信号幅度本来就小,血液容积变化带来的交流分量通常只有直流偏置的百分之一量级。传感器一旦随手腕晃动,皮肤与光源之间的耦合距离改变、静脉血被挤压回流、组织散射路径改变,这些效应叠加起来,产生的干扰幅度往往数倍于真实的脉搏波。更糟的是干扰不是随机噪声,而是有结构的:走路的步频大约在 1.6 至 2 Hz 之间,跑步可以到 2.4 Hz 以上,恰好落在心率频段(约 1 至 3 Hz)的中段。用带通滤波器把运动干扰滤掉,等于连心跳一起滤掉。
这就是为什么运动伪影问题在工程上拖了十几年。单纯看 PPG 一路信号,很难区分「这是心跳还是脚步」;必须引入一个独立描述运动的通道,告诉算法此刻手腕到底怎么动。这个库的设计逻辑正是如此:给 PPG 配上一个三轴陀螺仪与两组量程不同的加速度计,让去伪影算法有据可依。论文摘要里点明了这一层的意义——有了陀螺仪,才第一次能把「重力方向变化造成的表观加速度」与「真正的手臂运动加速度」分离开。
1.2 三条常见的错误期待
第一类期待,是把它当成腕戴加速度计数据集。这个误会相当普遍,因为库名里的 wrist 与 exercise 都指向可穿戴运动监测,而穿戴运动监测的默认想象是计步。实际打开头文件就会看到,腕部 12 路信号里只有 6 路是加速度计,主信号 wrist_ppg 排在胸部 ECG 之后第二行,是全库唯一的光学通道。加速度计在这里是辅助角色,不是研究对象。
第二类期待,是把它当成帕金森或运动障碍队列。这个方向的误读可能来自两类来源:一是「腕部 + 运动」的组合容易联想到震颤监测,二是若干震颤研究确实用过腕部惯性传感器。但本库的参与者是健康年轻人,活动是跑步机步行、跑步与健身车骑行,没有任何病理标签、UPDRS 评分或症状日记。想把腕部惯性信号用于帕金森步态分析,应当去找带临床标注的专病队列,本库给不了。
第三类期待,是把它当成心率估计的排行榜基准。它确实可以用来做心率估计,但因为只有 8 个人、每人活动不齐、骑行与步行滤波口径不同,直接拿它的平均绝对误差去和 PPG-DaLiA 或其他大库的公开数字相比,是没有意义的。它更适合被当作验证集、压力测试集,或者是算法在「极端运动强度」区间是否崩掉的体检样本。
1.3 它能给到的与给不到的
| 你的需求 | 它给的 | 够不够 |
|---|---|---|
| 腕部 PPG 在步行、跑步、骑行下的原始波形 | 19 条记录,四类活动覆盖 | 够,样本量小但覆盖全 |
| 逐拍心率参考 | 胸部 ECG + 人工 R 峰时间 | 够,可算瞬时心率与 HRV |
| 运动状态描述 | 陀螺仪 + 双量程加速度计 + 磁力计 | 够,三线冗余配置少见 |
| 按人群分层的泛化验证 | 8 名健康年轻人,无分层 | 不够 |
| 静息或夜间睡眠场景 | 只有运动段,设置期与休息期被剔除 | 不够 |
| 临床诊断级心电 | 单导联、非诊断配置 | 不够 |
| 现成的每分钟心率标签列 | 只有 R 峰时间戳 | 需自行换算 |
| 公开的评测划分与基线分数 | 论文建议留一法,未给固定划分文件 | 需自建 |
1.4 你的处境与它能帮到的事
| 你的处境 | 它能帮你什么 | 下一步该做什么 |
|---|---|---|
| 刚做完一个腕部 PPG 去伪影算法,需要一个不参与训练的外部验证集 | 提供四类运动中强度最高的一段,作为压力测试 | 只用它做泛化验证,训练仍放在大库上,报告时标明受试者数 |
| 想验证「陀螺仪到底有没有用」 | 同一段记录里同时有陀螺仪与双量程加速度计,可做消融对照 | 设计三组输入(仅 PPG、PPG+加速度、PPG+加速度+陀螺),在同一批记录上比 |
| 要教一节课讲运动伪影是怎么产生的 | 波形直观,跑步段的漂移肉眼可见,无需预处理即可演示 | 取 s4_run 做示范,配合头文件注释解释速度差异 |
| 在做腕部能量消耗或活动识别 | 有四类活动标签与惯性信号,但无代谢真值 | 仅作活动分类的补充样本,能耗方向另找带气体分析仪的库 |
| 想找帕金森或震颤相关的腕部数据 | 给不了,人群与协议都不对 | 转向带临床量表的专病可穿戴队列 |
| 需要大规模腕部 PPG 做深度学习预训练 | 规模不够,只有 8 人 | 用 PPG-DaLiA 或更大规模库预训练,本库留作外部测试 |
| 研究不同滤波口径对结果的影响 | 骑行已滤波、步行未滤波,本身就是现成的对照案例 | 在方法章节明确交代两类记录的处理差异,避免混淆误差来源 |
1.5 一句话判断
这是一个小而配置齐全的运动伪影基准库:8 个人、19 条记录、四类运动、三线运动感知加一条胸部心电参考,用来验证腕部 PPG 算法「人动起来之后还准不准」,而不是训练一个通用心率模型。
1.6 不适合谁:四条边界
第一条边界,是规模驱动的研究。只有 8 名受试者,任何依赖大量样本的深度模型在此都会立刻过拟合;把它当训练集等于用一百多分钟数据去拟合一个需要几千小时的问题。
第二条边界,是人群泛化与公平性研究。8 名受试者全是 22–32 岁的健康成人,3 男 5 女,没有肤色记录、没有年龄跨度、没有基础疾病。腕部光学测量对皮肤色素、皮下脂肪厚度、腕围都敏感,而这个库在这些维度上几乎是单点取样,用它讨论误差是否随肤色或年龄变化,样本量根本不支撑。
第三条边界,是临床心律判读。胸部 ECG 是单导联、双电极、非诊断级配置,采集目的是给出心率参考而非诊断心律。它上面的 ST 段、T 波形态都不具备诊断价值,拿它做心律失常检测会得到无法解释的结果。
第四条边界,是静息与睡眠场景。录制时刻意剔除了设置期与休息期,只保留运动段。这意味着所有记录都从「已经开始运动」的状态起算,没有从静息过渡到运动的完整过程,也没有运动后恢复段的回落曲线。做静息心率、睡眠心率或恢复期心率动力学的研究,需要另找数据库。
§2 数据构成与规格
2.1 一次录制里发生了什么
实验的物理布置可以拆成两条并行的链路。腕部链路的硬件是 Shimmer 3 GSR+,一个集成了陀螺仪、低噪声加速度计、宽量程加速度计与磁力计的模块。关键的设计细节是 PPG 传感器被粘在这个模块本体上,目的是让惯性单元与光学传感器之间保持刚性连接,从而让惯性单元记录的确实是光学探头本身的运动,而不是手腕上另一处近似位置的估计。这个模块以表带形式戴在左腕,位置约等于一块标准手表的佩戴处。
胸部链路是 Actiwave 记录仪配两片预涂凝胶的自粘式银/氯化银电极,分别贴在胸部两侧,构成单通道双电极导联。这个信号的作用不是诊断,而是提供心率参考:把每次心搏发生的确切时刻记录下来,任何时段都能算出「此刻真实心率是多少」,再与腕部算法给出的估计相比较。
实验协议给了四种活动选项:跑步机正常步速步行、跑步机轻慢跑或跑步、健身车低阻力蹬车、健身车较高阻力蹬车,每项最长 10 分钟。值得注意的是,协议明确说明目标不是执行一套标准运动处方,而是往心电与脉搏里注入有代表性的运动干扰,因此配速由参与者自定,可以随时调整或停止。多数人在每项活动上花 4 到 6 分钟,每次都从静息开始。这个安排直接决定了库的形态:强度不标准化,但真实度较高,记录长度参差。
论文还披露了一个头文件里看不到的对齐细节。Shimmer 设备用 Unix 时间戳记录每个样本,实际平均采样率是 255.69 Hz,略低于名义的 256 Hz;而 Actiwave 假定均匀的 39 毫秒间隔。两者累计下来,腕部信号与 ECG 的样本数会逐渐错开,最坏情况下时长差 1.21 秒,记录末尾用 NaN 补齐到等长。这个细节对逐拍对齐很关键,稍有疏忽就会把几百毫秒的错位当成算法误差。
2.2 字段与记录的完整字典
每条 WFDB 记录的头文件第一行给出记录名、信号路数、采样率与样本总数,随后每行描述一路信号。以 s1_walk 为例,头文件声明 15 路信号、256 Hz、150529 个样本,换算约 9 分 48 秒。逐路字段如下。
| 序号 | 信号名 | 单位 | 含义 | 角色 |
|---|---|---|---|---|
| 1 | chest_ecg |
mV | 胸部单通道双电极心电 | 心率真值来源 |
| 2 | wrist_ppg |
mV | 左腕反射式光电容积脉搏波 | 主研究对象 |
| 3 | wrist_gyro_x |
deg/s | 腕部绕 X 轴角速度 | 运动描述 |
| 4 | wrist_gyro_y |
deg/s | 腕部绕 Y 轴角速度 | 运动描述 |
| 5 | wrist_gyro_z |
deg/s | 腕部绕 Z 轴角速度 | 运动描述 |
| 6 | wrist_low_noise_accelerometer_x |
m/s² | 低噪声加速度计 ±2 g 量程,X 轴 | 细微运动 |
| 7 | wrist_low_noise_accelerometer_y |
m/s² | 低噪声加速度计,Y 轴 | 细微运动 |
| 8 | wrist_low_noise_accelerometer_z |
m/s² | 低噪声加速度计,Z 轴 | 细微运动 |
| 9 | wrist_wide_range_accelerometer_x |
m/s² | 宽量程加速度计 ±16 g,X 轴 | 剧烈运动 |
| 10 | wrist_wide_range_accelerometer_y |
m/s² | 宽量程加速度计,Y 轴 | 剧烈运动 |
| 11 | wrist_wide_range_accelerometer_z |
m/s² | 宽量程加速度计,Z 轴 | 剧烈运动 |
| 12 | wrist_mag_x |
Gauss | 三轴磁力计,X 轴 | 姿态参考 |
| 13 | wrist_mag_y |
Gauss | 三轴磁力计,Y 轴 | 姿态参考 |
| 14 | wrist_mag_z |
Gauss | 三轴磁力计,Z 轴 | 姿态参考 |
| 15 | sample_times_for_all_signals_apart_from_ecg |
s | 腕部各样本的毫秒级时间戳,0–60 循环 | 时间对齐 |
配对文件方面,每条记录除 .hea 与 .dat 外还有一个 .atr 注释文件,内容是以 ECG 首样本为 0 秒基准的 R 峰时刻。整库另有三个清单文件:RECORDS 列出全部记录名,ANNOTATORS 声明注释类型,SHA256SUMS.txt 提供每个文件的校验和。头文件最后一行是一条以井号开头的注释,记录该次活动的具体参数,例如 #Walking_2 km/h 或 #High resistance biking_level 7,这是全库唯一的强度元数据。
2.3 一条记录的完整解剖
以 s1_walk 为例走一遍从文件到数组的过程。头文件第一行 s1_walk 15 256 150529 意味着这条记录有 15 路信号、采样率 256 Hz、共 150529 个样本;150529 除以 256 得 588 秒,即 9 分 48 秒,与论文中「每项活动最多 10 分钟」的上限一致。信号行里的 16 是每样本位数,紧随其后的数字是 AD 增益与基线,括号内是初始值,末尾 ms^-2、degs^-1、Ga 等是单位字符串,最后一列才是信号名。
#-- 读取一条记录:只取 PPG 与胸部时间戳,避免把 12 路都读进内存
import wfdb
rec = wfdb.rdrecord("s1_walk", pn_dir="wrist/1.0.0",
channel_names=["wrist_ppg", "sample_times_for_all_signals_apart_from_ecg"])
print(rec.fs, rec.sig_len, rec.sig_name)
#-- 256 150529 ['wrist_ppg', 'sample_times_for_all_signals_apart_from_ecg']
print(rec.p_signal[:3])
这里的 pn_dir 参数让 wfdb 直接从 PhysioNet 拉取,不必先下载整包。需要注意的是,sample_times_for_all_signals_apart_from_ecg 这一路的物理单位是秒,但它是对应每个样本的时间戳,数值在 0 到 60 之间循环,并不是成比例的电压量;它的存在是为了纠正 Shimmer 采样不严格均匀的问题,用它可以重建腕部信号的真实时间轴,再与 ECG 对齐。
注释文件同样能直接读。wfdb.rdann 返回的 sample 字段是 R 峰在 ECG 通道上的样本序号,除以 256 即得秒数。
#-- 把 R 峰样本序号换成秒,得到逐拍心搏时刻
import wfdb
ann = wfdb.rdann("s1_walk", "atr", pn_dir="wrist/1.0.0")
print(len(ann.sample)) #-- 这条记录里人工标注的 R 峰个数
print(ann.sample[:5] / 256.0) #-- 前五个心搏发生的秒数
把这两段合起来,就能得到逐拍的瞬时心率序列:相邻 R 峰间隔的倒数乘以 60。这个序列既可作为腕部算法的监督信号,也可直接用于心率变异性分析——因为标注是人工逐拍确认的,而不是从自动化算法导出的,避免了用算法验证算法的循环。
2.4 十九条记录的组织方式
记录名遵循 sX_activity 的固定格式,X 是受试者编号,activity 取 walk、run、low_resistance_bike、high_resistance_bike 四个值之一。受试者编号从 s1 到 s9,但 s7 不存在,实际只有 8 个人,这与官方页面「8 名参与者」的表述一致;缺失原因页面未作说明。
| 受试者 | 记录数 | 具体记录 | 头文件标注的强度 |
|---|---|---|---|
| s1 | 3 | 步行、低阻力骑行、高阻力骑行 | 步行 2 km/h;骑行档位 4 与 6 |
| s2 | 3 | 步行、低阻力骑行、高阻力骑行 | 步行 2.5 km/h;骑行档位 4 与 7 |
| s3 | 4 | 步行、跑步、低阻力骑行、高阻力骑行 | 步行 2.5 km/h;跑步 5.5 km/h;骑行档位 4 与 8 |
| s4 | 1 | 跑步 | 10 km/h |
| s5 | 2 | 跑步、低阻力骑行 | 跑步 5.5 km/h;骑行档位 4 |
| s6 | 3 | 步行、跑步、低阻力骑行 | 三项均标 unknown |
| s8 | 2 | 步行、跑步 | 步行 2.7 km/h;跑步 5.2 km/h |
| s9 | 1 | 步行 | 3 km/h |
按活动汇总,步行 6 条、跑步 5 条、低阻力骑行 5 条、高阻力骑行 3 条,合计 19 条。这个结构本身说明了两件事:第一,四种活动并非每人都做齐,跨活动比较时受试者集合不同;第二,高阻力骑行只有 3 条,是全库最薄的一格,用它单独做结论统计意义有限。
2.5 与同类腕部资源的结构差异
把本库与几个常被放在一起讨论的腕部资源并排看,差异集中在信号配置与标注方式上,而不是数据量。
| 维度 | 本库 wrist | ieeepg(IEEE SPC 2015 封装) | PPG-DaLiA | sleep-accel |
|---|---|---|---|---|
| 受试者 | 8 名健康成人 | 12 训练 + 8 测试(原始竞赛口径) | 15 名健康成人 | 31 名成人 |
| 主信号 | 腕部 PPG + 胸部 ECG | 2 路 PPG + 3 轴加速度 | 腕部 PPG + 胸带 ECG + 呼吸带 | 腕部加速度 + 光学心率 |
| 惯性单元 | 陀螺仪 + 双量程加速度计 + 磁力计 | 仅 3 轴加速度 | 3 轴加速度 | 3 轴加速度 |
| 活动设置 | 跑步机与健身车四种运动 | 跑步机变速 + 臂部混合动作 | 八类日常活动 | 一夜睡眠 |
| 真值形式 | 人工标注 R 峰时间 | 由 ECG 算出的心率序列 | 胸带 ECG 参考 | 多导睡眠判读 |
| 时间戳对齐 | 每样本毫秒时间戳 | 无 | 有 | 有 |
| 滤波状态 | 骑行已滤波、步行跑步未滤波 | 有预处理 | 未统一 | 厂商预处理 |
最显眼的差别是惯性单元的完整度。陀螺仪在腕部 PPG 去伪影中的地位,论文说得直白:有了它才能把重力分量与真实运动分开。这一层信息在仅含三轴加速度的库里是拿不到的,必须靠重力估计或姿态滤波间接推断。另一个差异是时间戳:本库为每个腕部样本单独记录毫秒时间戳,使跨设备对齐可以做到样本级,不必依赖等间隔假设。
§3 采集现场:谁被录、录了什么条件
3.1 八个人是怎么选出来的
官方页面只给了三句话:8 名参与者,3 男 5 女,年龄 22 至 32 岁,均值 26.5。没有写招募渠道,没有写入组排除标准,没有写健康状况的筛查方式,也没有写肤色、体重、腕围、是否吸烟、运动习惯。论文正文对参与者的描述同样简洁。
这个信息密度决定了库的使用姿态。它是一个工程验证样本,样本的定义域是「在曼彻斯特的一批年轻健康成年人」,而不是「一般人群」。如果要谈泛化,能谈的只有「换一个人、换一段运动」这一层的泛化,不能谈「换一种肤色、换一个年龄段、换一种心血管状态」的泛化。
从实验设计的角度,这个取舍说得通:伪影强度主要由运动本身决定,只要运动与心率区间够宽,8 个人就够画出算法在什么条件下失效;需要大样本的是统计推断,而这篇论文的定位是数据库描述。
3.2 四种活动为什么是这四种
步行、跑步、低阻力骑行、高阻力骑行,这四项不是随意挑的,它们对应运动干扰的四个典型工况。
步行是日常生活里出现频率最高的运动,手腕摆幅中等、节律稳定,干扰频率集中在 1.6 至 2 Hz。跑步把摆幅和频率都推高,加速度峰值可到数个重力加速度,是腕部 PPG 最容易失守的场景,论文摘要特意把跑步与骑行并列为「以往公开库没有覆盖」的场景。低阻力骑行时手腕基本固定在车把上,身体姿态稳定,但踏频带来的躯干摆动会以另一种频率污染信号,而且上肢的等长收缩会影响局部血流。高阻力骑行把踏频压低、力矩抬高,心率上升更快,干扰的谱形与低阻力不同。
四项合起来,覆盖了「上肢周期性大摆」「上肢相对静止但躯干周期摆动」「高负荷低频率」三类伪影机理。这个覆盖逻辑比单纯堆活动种类更有价值,也是本库在设计上最经得起推敲的部分。
3.3 强度为什么标得这么松散
头文件注释里的强度信息是不完整的,这一点很容易被忽略。步行记录大多标了速度,2 到 3 km/h 之间;跑步记录大多标了速度,5.2 到 10 km/h;骑行记录标的是健身车的阻力档位,低阻力统一为 4 档,高阻力在 6 到 8 档之间;而 s6 的三条记录全部标注 unknown。
这里有一个必须接受的现实:这些标注是采集者事后凭记忆或笔记补上的,不是仪器测得的。协议本身允许参与者自定配速并随时调整,所以「步行 2.5 km/h」是一个名义值,不是整段记录的恒定速度。要用强度做协变量分析,只能把它当有序等级用(步行 < 跑步,低阻力 < 高阻力),不能当连续测量值。
骑行档位尤其如此。健身车的「档位」在不同机器上对应的功率并不相同,而论文与页面都没有给出任何功率或踏频数值。因此「高阻力骑行」在库内的可比性仅限于同一台机器上的相对高低,跨研究的绝对强度无从核对。
3.4 胸部 ECG 的具体角色
把胸部 ECG 叫「金标准」,容易让人以为是诊断级十二导联。实际上它是 Actiwave 记录仪配两片 Ag/AgCl 电极构成的单通道双电极配置,电极分置心脏两侧。采集软件里施加了 50 Hz 陷波以去除工频干扰,除此之外没有其他处理。
这个配置足以回答「第几秒发生了心搏」这个问题,因为 R 峰在单导联上通常足够突出;但它不足以回答「这个心搏形态是否正常」,因为单导联看不到完整的电轴与各导联间的形态关系。库的用途因此被限定在心率与心率变异性这一层,而不会被误用到心律诊断上——至少在正确使用时不会。
R 峰是由人工逐条标注的,这一点值得强调。自动化 R 峰检测在运动状态下本身就会出错,如果用算法生成真值,再用另一套算法去验证,误差会耦合。人工标注把真值链条切断了,代价是标注成本高,这大概也是记录总时长控制在一百多分钟的现实原因之一。
3.5 场景边界:库外的东西
有几类信息不在库里,但使用前必须知道它们不在。
第一是同步视频或动作捕捉。没有任何影像参考,无法核对某一段高幅度的加速度是否对应手臂的某次特定摆臂,也无法把伪影归因到具体的动作事件。
第二是生理负荷的其他测量。没有气体分析仪、没有血乳酸、没有主观疲劳量表。心率是唯一的心脏负荷指标,无法交叉验证。
第三是设备佩戴的元信息。没有记录表带松紧、佩戴位置的具体偏移、环境温度、皮肤温度。这些因素都会影响 PPG 的耦合质量,而它们在库里完全不可见。
第四是受试者层面的静态属性。除了性别与年龄,没有任何其他人口学或体型信息,也没有肤色分级。这在做误差归因时是硬约束。
§4 质量:它凭什么被引用
4.1 三线运动感知的冗余设计
本库在传感配置上最突出的特点是冗余:同一只手上有陀螺仪、低噪声加速度计、宽量程加速度计、磁力计四类惯性器件。这不是简单堆料,而是各有分工。
低噪声加速度计的满量程较小,噪声底低,适合捕捉步行时的小幅摆动;宽量程加速度计满量程大,适合跑步时不至于削顶;陀螺仪测角速度,不受重力方向影响,能直接反映手腕的旋转运动;磁力计提供绝对航向参考,可与陀螺积分结果互相校正。四路信号的失败模式各不相同:加速度计怕削顶、陀螺仪怕零偏漂移、磁力计怕环境磁场干扰。同时提供,使用方就能根据场景挑最合适的一路,或者做融合。
这种冗余在同期的公开腕部库里并不常见。多数量提供三轴加速度一路,课题组若要研究「哪种运动信息对去伪影最有用」,只能自己去采。本库把这个对照实验的材料一次性给齐了。
4.2 参考心率的标注方式
前文已提,R 峰是人工标注的。这里补充它带来的具体后果。
一是时间基准清晰。注释以 ECG 首样本为 0 秒,所有 R 峰时刻都是相对这个原点的秒数,不存在时区或绝对时间的问题。二是标注粒度是样本级,而非秒级,逐拍间期的分辨率达到 1/256 秒,足够支撑心率变异性分析中的高频成分。三是标注者与信号同源,任何人拿到数据都可以用原始 ECG 波形复核每一个标注点,不存在无法验证的中间层。
需要提醒的是,人工标注也可能有遗漏与误标,尤其在高心率、低信噪比的跑步段。库没有提供第二位标注者的独立结果,也没有提供标注一致性数据,因此单条记录的 R 峰序列不能视为无误。做严谨的逐拍分析时,建议先用简单的峰值检测在 ECG 上做一遍交叉核对,把两者差异较大的位置单独检查。
4.3 「首次」这一claim的边界
论文摘要里有一句明确的表述:包含陀螺仪信息,使得分离重力加速度与真实运动加速度成为可能,并称这是「首次」。评估这类表述要看两件事:一是它指的边界是什么,二是它是否被后续文献普遍接受。
从边界上看,这个 claim 限定在「公开的腕部 PPG 运动数据库」这个范围内,不是「首次有人用陀螺仪去伪影」,也不是「首次做重力分离」。理解这个限定,就不会把它误读为方法学首创。从接受度上看,这句话在后续引用中常被作为本库区别于 IEEE SPC 2015 数据的关键特征被转述,属于被复述而非被质疑的表述。
对使用方而言,这句话最实际的意义是:如果你的方法需要陀螺仪输入,那么这个库是少数能直接拿来验证的公开资源之一,而不是必须自己采集。
4.4 案例:s4_run 为什么是全库最硬的一段
本节的案例用来贯穿全文的几处讨论。选 s4_run 是因为它在几个维度上都处在极端。
它是全库唯一一条速度标注到 10 km/h 的记录,也是 s4 这位受试者唯一的一条记录。10 km/h 在跑步机上是明确的慢跑偏快速度,对应的步频与冲击力都高于其他跑步记录(其余跑步记录标注为 5.2、5.5 km/h,以及一条 unknown)。头文件显示这条记录长 292 秒,接近 5 分钟,与协议里「多数人每项活动 4 到 6 分钟」的描述吻合。
从信号层面看,这条记录的腕部 PPG 在头文件里的 AD 增益与基线与其他记录明显不同,说明采集时的信号幅度区间就不一样——这本身就是一个提醒:不同记录之间的原始数值不可直接比较,任何跨记录的分析都必须先做各自的归一化或增益还原。
之所以选它做贯穿对象,是因为它同时具备「强度最高」「受试者只有这一条」「时长接近上限」三个属性,任何一个环节处理不当,都会在这条记录上最先暴露。
4.5 已知的处理差异:骑行与步行不是同一口径
官方页面写得清楚:步行与跑步记录保留原始 PPG 与运动信号,除 Shimmer 硬件内置滤波外不做额外处理;骑行记录的 PPG 在上传前经过二阶 IIR Butterworth 低通滤波,截止频率 15 Hz,零群延迟;所有 ECG 都做了 50 Hz 陷波。
这个差异对使用的影响不容小视。骑行 PPG 已经被滤掉 15 Hz 以上的成分,而运动伪影的高频成分恰恰在这一段;因此骑行记录看起来「更干净」并不是因为运动更温和,而是因为已经被处理过。若把四类活动混在一起训练,模型很可能学会「识别滤波痕迹」而不是「识别伪影」;若按活动分别报告指标,又要注意骑行那一组的指标不可与步行跑步直接并列。
正确的做法是在方法部分明确写出这个差异,并考虑两种处理:一是把步行与跑步也做同样的 15 Hz 低通,让三组口径一致;二是只比较同类内部,不做跨类聚合。库没有提供滤波前的骑行原始数据,因此无法恢复未滤波版本,这一点是设计上的单向门。
§5 取数:从下载到第一张图
5.1 三条取数路径
路径一,整包下载。官方提供 zip 打包,全部 19 条记录加清单文件,体积在几十兆量级,一次下载即可离线使用。适合需要反复读取、做批量预处理的情况。
路径二,按需拉取。用 wfdb 的 pn_dir 参数直接指定项目与版本,只取需要的通道与记录。适合先探查、再决定是否全量下载的情况,也适合在带宽受限的环境里做快速验证。
路径三,逐文件 HTTP 访问。直接请求 https://physionet.org/files/wrist/1.0.0/ 下的 .hea、.dat、.atr,适合用 curl 或脚本抓取头文件做元数据核查,不必下载波形。核查记录清单、采样率、时长时,这条路径最快。
5.2 清单与头文件先看什么
拿到数据的第一件事不是读波形,而是读清单。RECORDS 给出全部记录名,可以据此确认受试者编号的缺口;头文件第一行给采样率与样本数,可以据此算出单条时长;头文件最后一行给活动参数,可以据此确认强度标注是否完整。这三步加起来不需要下载任何 .dat 文件,几十 KB 的文本就能完成,是所有后续工作的前提。
#-- 只需元数据时,只下载清单与全部头文件,不碰波形
mkdir -p wrist_meta && cd wrist_meta
curl -sO https://physionet.org/files/wrist/1.0.0/RECORDS
curl -sO https://physionet.org/files/wrist/1.0.0/SHA256SUMS.txt
for r in $(cat RECORDS); do curl -s -O "https://physionet.org/files/wrist/1.0.0/${r}.hea"; done
head -1 s4_run.hea && tail -1 s4_run.hea
5.3 逐条汇总时长与信号路数
有了头文件,就能用几行脚本把全库的规模算出来。这不是为了做审计,而是为了在开始设计实验之前,知道每个活动类别各有多少可用秒数,避免把结论建立在只有一条记录的类别上。
#-- 从本地头文件汇总:记录数、总时长、每类活动的记录条数与秒数
import collections, pathlib
acts, per_act_s, total = collections.Counter(), collections.Counter(), 0.0
for p in sorted(pathlib.Path("wrist_meta").glob("*.hea")):
head = p.read_text().splitlines()
name, nsig, fs, nsamp = head[0].split()[:4]
fs, nsamp = int(fs), int(nsamp)
act = name.split("_", 1)[1]
acts[act] += 1
per_act_s[act] += nsamp / fs
total += nsamp / fs
print(len(list(pathlib.Path("wrist_meta").glob("*.hea"))), round(total / 60, 1))
for k in sorted(acts):
print(k, acts[k], round(per_act_s[k] / 60, 1))
跑完会看到四类活动在记录条数与秒数上都不均衡,高阻力骑行是三者最弱的一格。这个结论不看分布表也能得到:条数本身就是结构,而不是分布。
5.4 把 PPG 与参考心率拼到一条时间轴上
这是使用本库时最容易出错的一步,也是最需要写清楚的一步。腕部信号与 ECG 的采样并不严格共时,Shimmer 的实际平均采样率是 255.69 Hz 而名义值是 256 Hz,累计偏差最坏可达 1.21 秒。因此正确的做法是利用腕部那一路时间戳重建时间轴,再与 ECG 的样本序号做插值对齐。
#-- 用毫秒时间戳重建腕部时间轴,再按参考时间点重采样到 ECG 网格
import numpy as np, wfdb
rec = wfdb.rdrecord("s1_walk", pn_dir="wrist/1.0.0")
ppg = rec.p_signal[:, rec.sig_name.index("wrist_ppg")]
ts = rec.p_signal[:, rec.sig_name.index("sample_times_for_all_signals_apart_from_ecg")]
t_wrist = np.unwrap(2 * np.pi * ts / 60.0) * 60.0 / (2 * np.pi)
t_wrist = t_wrist - t_wrist[0]
n_ecg = int(round(t_wrist[-1] * rec.fs)) + 1
t_ecg = np.arange(n_ecg) / rec.fs
ppg_on_ecg = np.interp(t_ecg, t_wrist, ppg)
print(len(ppg), len(ppg_on_ecg), round(t_wrist[-1], 2))
np.unwrap 处理的是时间戳在 0 到 60 之间循环这件事,把它还原成单调递增的相对秒数。这一步做错,后续所有对齐都会错位,而且错得很隐蔽——波形看上去还在,只是与心电差了几百毫秒。
5.5 用胸腔参考算出逐拍心率
有了 R 峰时刻,心率序列是几行代码的事。相邻峰间隔的倒数即瞬时心率,也可以先做中值平滑再报每分钟心率,以抑制单拍间期的正常波动。
#-- 从 R 峰时刻算逐拍瞬时心率,并与腕部 PPG 的峰值做粗对齐
import numpy as np, wfdb
ann = wfdb.rdann("s4_run", "atr", pn_dir="wrist/1.0.0")
ibi = np.diff(ann.sample) / 256.0
hr = 60.0 / ibi
print(ibi.min(), ibi.max(), np.median(hr))
print("标注峰数", len(ann.sample), "记录秒数", ann.sample[-1] / 256.0)
这段代码用 §4.4 的案例 s4_run 做示范,是因为它的强度最高,逐拍间期的变化也最明显,能立刻看出心率在运动过程中是否上升、在何处出现异常短的间期。若换成步行记录,间期波动会平缓得多,不太容易判断代码是否写对。
5.6 把它接进现有流水线时要改什么
如果手上已有一套针对腕部 PPG 的处理流水线(例如为大库写的分窗、归一化、质量判别模块),接本库时需要改三处。
第一处是通道名。本库用 wrist_ppg、wrist_gyro_x 这样的长名,而多数库用 PLETH、ACC_X 之类的短名,硬编码通道名的代码会直接报错。建议在入口处做一层名字映射。
第二处是采样率与时间基准。本库名义 256 Hz,但腕部实际约 255.7 Hz,若流水线默认等间隔,需要先按 5.4 的方法重采样,否则长记录会累积错位。
第三处是记录长度差异。本库单条 3 分 40 秒到 9 分 49 秒不等,分窗长度若固定为 8 秒,短记录也只能切出几十窗,不能用统一的 batch 数假设。按受试者做留一法时,要特别注意 s4 与 s9 只有一条记录,这两个人在留出集里会是极不平衡的样本。
§6 与库内词条的关系
6.1 关系图谱
库内与腕部信号相关的词条可以分成三圈。内圈是同为腕部 PPG 且同以心率估计为目标的,中圈是腕部佩戴但研究目标不同的,外圈是 PPG 或心电但不在腕部的。
| 词条 | 与本文的关系 | 关键区别 |
|---|---|---|
ieeepg |
同为腕部 PPG 运动心率估计,最接近的对照 | 该库只含 2 路 PPG 与 3 轴加速度,无陀螺仪、无胸部 ECG 原始波形;本库有陀螺仪、双量程加速度计与完整胸部 ECG,但规模小得多 |
ppg-dalia |
同为腕部 PPG + 胸带心电参考 | 该库含八类日常活动、约三十六小时数据,走的是日常场景;本库只有四种运动、约两小时,走的是极限负荷场景 |
noneeg |
同为腕部多路信号 | 该库是皮肤电、体温、加速度、心率、血氧五路,目标是神经状态;本库是光学脉搏波与惯性信号,目标是运动伪影 |
sleep-accel |
同为腕部佩戴 | 该库是一夜睡眠,判读由多导睡眠图给出;本库是白天运动,参考来自胸部 ECG |
bigideaslab-step-hr-smartwatch |
同为腕部光学心率准确性 | 该库用手腕佩戴的消费级设备对心电贴片,目标是肤色与设备误差;本库用研究级模块取原始波形,目标是信号处理算法 |
mimic-iii-ext-ppg |
同为 PPG 波形库 | 该库是重症监护床旁指夹式 PPG,人群是危重患者;本库是健康人运动中的腕部 PPG |
butppg |
同为 PPG 质量评估素材 | 该库用智能手机摄像头在指尖取光,本库用腕部反射式传感器 |
这张表最需要说清的是与 ieeepg 的区隔。两者经常被混为一谈,因为都叫「腕部 PPG 运动心率估计」,但数据指纹完全不同:ieeepg 是竞赛数据的二次封装,每条样本被切成 5 维、8 秒窗、6 秒重叠的定长序列,为时间序列回归任务准备;本库是原始连续波形,保留完整时长与 13 路信号,需要使用者自己分窗。前者拿到即用,后者需要写预处理;前者没有陀螺仪,后者有。
6.2 检索路径
按需求反查该走哪一条,可以按下面的顺序自问。
| 你要做的事 | 先看哪条 | 理由 |
|---|---|---|
| 训练一个腕部心率回归模型 | ppg-dalia 或 ieeepg |
样本量足够,且已有公开划分 |
| 验证模型在剧烈运动下是否失效 | 本文的 wrist | 有 10 km/h 跑步与高阻力骑行,压力区间高 |
| 研究陀螺仪对去伪影的增益 | 本文的 wrist | 库内唯一提供三轴陀螺仪的腕部 PPG 库 |
| 做肤色与设备公平性研究 | bigideaslab-step-hr-smartwatch |
有肤色分级与多设备配对 |
| 做睡眠与静息心率 | sleep-accel 或 noneeg |
有夜间与静息段 |
| 做神经状态或情绪识别 | noneeg |
有皮肤电与体温 |
| 做重症 PPG 心律分析 | mimic-iii-ext-ppg |
床旁连续监测,有临床标注 |
| 做智能手机 PPG 质量评估 | butppg |
摄像头取光,有专家共识标注 |
检索时容易踩的一个坑是按「wrist」搜。库内有十几个词条的正文提到 wrist,搜出来大多是肌电、骨折影像、血压之类的完全不同的东西。准确的检索词应当是「腕部 PPG」「运动伪影」「Wrist PPG During Exercise」,或者直接用 DOI。
6.3 与腕戴睡眠库的分工
sleep-accel 与本库都以手腕为落点,但两者的时间尺度和判读逻辑完全不同。前者是整夜八小时级别的连续记录,关心的是三轴加速度能否反推睡眠分期,判读标准来自多导睡眠图里的脑电、眼电与肌电。后者是几分钟级别的运动片段,关心的是光学脉搏波能否在干扰中提取心率,参考来自胸部心电。
把两者混用会出两类错误。一类是把本库的运动段拿去训练睡眠分期——数据里没有睡眠,标签也无从谈起。另一类是把 sleep-accel 的静息数据当作去伪影算法的「干净对照」——静息时腕部几乎不动,与运动段的干扰机理完全不在一个层面上,做对照缺乏意义。
6.4 与五路腕上信号库的分工
noneeg 那一篇记录的库同样是手腕上戴一个多传感器模块,同样采集多路信号,乍看相似度很高。差别在信号种类与研究目标:那一路库里是皮肤电活动、体温、三轴加速度、心率与血氧,目标是从自主神经活动的角度判断人处于放松、紧张还是认知负荷状态;本库是光学脉搏波与三轴惯性,目标是把脉搏波从运动干扰里救出来。
一个具体的判断方法:看你关心的是「人处于什么状态」还是「这条波形是否被污染」。前者去 noneeg,后者来本库。两者都需要皮肤接触的物理原理,但一个读的是汗腺与血管的交感反应,一个读的是血容量的机械搏动。
§7 质量评估与局限性
7.0 阅读本节前的一个前置校正
在给这个库打分之前,要先放弃一种常见的误判:把它当作「小型版 PPG-DaLiA」。两者名字相近、配置相似,但研究问题不同。
PPG-DaLiA 的设计目标是「在尽可能真实的日常活动下估计心率」,所以它追求活动种类的广度、时长的连续性与场景的自然度。本库的设计目标是「在运动干扰最强的时候,算法还能不能守住」,所以它追求的是干扰强度的上限、运动感知通道的完备度以及能否用陀螺仪做重力分离。前者像一部纪录片,后者像一次风洞试验。
用纪录片的评价标准去衡量风洞试验——比如问「为什么只有 8 个人」「为什么没有厨房做饭的场景」——会得出全面偏低的结论,而这个结论对使用者没有帮助。正确的问法是:作为风洞试验,它的风速档位够不够、测量仪器准不准、试验件的代表性如何。本节按这个框架展开。
7.1 五维评分
| 维度 | 评分 | 依据 |
|---|---|---|
| 信号配置完备度 | 高 | 同类腕部 PPG 库中少见地同时提供陀螺仪、双量程加速度计、磁力计与胸部 ECG |
| 参考标注质量 | 高 | R 峰人工逐拍标注,时间基准明确,可用原始波形复核 |
| 规模 | 低 | 8 名受试者、19 条记录、合计约两小时 |
| 人群代表性 | 低 | 全部为 22–32 岁健康成人,无肤色、体型、疾病信息 |
| 可直接接入度 | 中 | WFDB 标准格式、有校验和,但需自行对齐、分窗与换算标签 |
7.2 每一项评分背后的具体事实
配置完备度给高分,依据是四类惯性器件加光学加电生理的组合在同期公开库里稀缺。它的现实意义是,想做「哪种运动信息最有用于去伪影」的消融实验,不必自己采数据。
参考标注质量给高分,依据是人工标注而非算法生成,且注释给出了精确的样本序号。它的现实限制是缺少第二标注者与一致性检验,单条记录的个别错标无法从数据本身排除。
规模给低分,不只是在说人数少。更要紧的是有效训练秒数少:四类活动里高阻力骑行只有 3 条、合计不足二十分钟,用它训练任何模型都会立刻失去泛化能力。
人群代表性给低分,原因是多重的:年龄跨度只有十年,性别比例 3 比 5,没有肤色分级,没有体重或腕围,没有运动习惯分层。腕部光学测量的成绩对皮肤色素和皮下组织厚度高度敏感,这些变量在库里全部缺位。
可直接接入度给中等,是因为格式标准但结构松散。WFDB 是通用的,wfdb 库一行就能读;但通道名与主流库不一致、采样率名义与实际有偏差、骑行与步行的滤波口径不同,这些都要使用者自己处理,没有现成的 loader。
7.3 已知的、可枚举的偏差
第一条,时长偏差。协议允许每项活动最长 10 分钟,实际记录在 3 分 40 秒到 9 分 49 秒之间。这意味着同一活动在不同人之间的有效样本量差异很大,按记录条数加权与按秒数加权会得到不同的结果。
第二条,活动覆盖偏差。四种活动并非每人都做。只有 s3 一个人做齐了四项,s4 与 s9 各只做了一项。任何「按活动比较」的分析,实际上都在比较不同的人,受试者效应与活动效应无法分离,除非做留一法并逐人报告。
第三条,强度标注偏差。s6 的三条记录速度与档位全部标为 unknown,低阻力骑行统一标注为 4 档。名义强度与实际强度之间存在未知差距,把强度当连续变量的分析不可靠。
第四条,信号处理偏差。骑行 PPG 已做 15 Hz 低通,步行与跑步未做。这个差异是系统性的、与活动类别完全混淆的,任何跨活动的信号质量比较都会把滤波效果算进运动效果里。
第五条,编号偏差。受试者编号从 s1 到 s9 但缺 s7,官方页面未说明原因。若按编号范围推断人数会误判为 9 人。
第六条,对齐偏差。腕部实际平均采样率 255.69 Hz 低于名义 256 Hz,最坏情况与 ECG 时长差 1.21 秒,末尾用 NaN 补齐。不做时间戳重建就直接按样本序号对齐,长记录会在末尾积累明显错位。
7.4 外部验证矩阵
| 验证方向 | 用什么 | 本库能提供什么 | 需要注意什么 |
|---|---|---|---|
| 跨个体泛化 | 留一受试者交叉验证 | 8 个可留出的受试者 | s4 与 s9 各只有一条记录,留出后训练集骤减 |
| 跨活动泛化 | 三种活动训练、一种测试 | 四类活动,可做四折 | 骑行与步行的滤波口径不同,测试结论会混入处理差异 |
| 跨设备泛化 | 与其他腕部 PPG 库对测 | 原始波形,可做统一预处理 | 通道名、采样率、极性与各单位不同,需先做映射 |
| 与竞赛数据互证 | 与 IEEE SPC 2015 系数据对测 | 同属腕部运动场景 | 该竞赛数据已难以获取原始版,封装版的窗口化会丢失连续性 |
| 算法失效点定位 | 按强度分层看误差 | 10 km/h 跑步与高阻力骑行两个高档 | 高档样本量小,单条记录即可左右结论 |
矩阵里最值得展开的是跨活动泛化那一行。如果用步行、跑步、低阻力骑行训练,用高阻力骑行测试,表面上是在测跨活动泛化,实际上测试集只有 3 条记录,而且这 3 条记录的 PPG 都经过低通滤波,训练集里却有两类是未滤波的。模型在测试集上的表现差异,可能来自活动本身,也可能来自滤波,两者无法分离。要做得干净,只能把训练集也限定在骑行内部,退化成只有 8 条记录的小实验。
这也正是 §4.4 的案例值得反复引用的原因:s4_run 是唯一一条 10 km/h 的记录,样本量为 1。如果用它作为「高强度」这一档的代表,那么这一档的所有结论实际上只由一个人、五分钟决定。
7.5 它为何能持续:因为它占住了一个别人不肯采的位置
这个库从 2017 年上线至今没有版本更新,也没有扩样,却仍在被引用。原因不在数据量大,而在于采集成本的结构。
同时采腕部光学、四类惯性器件与胸部参考心电,需要一个受试者连续完成多项运动,每项还要从静息开始、之间至少休息十分钟,一个受试者就要花掉一个上午。再加人工逐拍标注 R 峰,标注工时与记录时长成正比。这种采集对单个课题组是重投入,对小样本、非标准化、含大量工程细节的数据集,学术界缺少持续扩样的激励——扩样不产生新论文,却要重复付出同样的采集成本。
于是它停在了 8 个人、19 条记录这个规模上,而后续研究者发现:要验证一个新的去伪影算法在「有陀螺仪可用」的条件下是否有效,绕不开它。需求稳定,供给稀缺,这就是它能持续的全部理由。
7.6 DAIMS 视角下的就绪度
DAIMS 是一套面向 AI 与医疗数据集的文档化与验证框架,用来判断一个数据集在多大程度上「拿来就能用」。按它的关注面逐项看本库,结论并不整齐。
| DAIMS 关注面 | 本库状况 | 说明 |
|---|---|---|
| 数据目的声明 | 明确 | 页面与论文都写明用于运动伪影去除算法 |
| 采集协议文档 | 较完整 | 四种活动、最长时长、休息间隔、设备型号均有交代 |
| 设备与参数 | 完整 | 型号、量程、采样率、滤波设置可查 |
| 人群描述 | 薄弱 | 只有年龄与性别,缺肤色、体型、健康状况 |
| 标注过程 | 部分 | 说明为人工标注,无标注者信息与一致性数据 |
| 数据划分 | 缺失 | 论文建议留一法,但未提供划分文件 |
| 已知局限声明 | 部分 | 页面说明滤波差异与采样率偏差,未集中列出 |
| 许可与使用条款 | 清晰 | ODC-BY 1.0,署名即可 |
| 可复现性 | 高 | 文件固定、有 SHA256 校验和、有 DOI |
| 伦理与同意 | 未载明 | 页面未提供伦理审批编号或知情同意说明 |
整体上,本库在「技术可复现」这一侧做得扎实,在「人群与伦理文档」这一侧偏薄。对工程验证够用,对任何需要论证人群代表性的研究不够。
§8 生态位:它在腕部 PPG 研究里站在哪一格
8.1 历史沿革:2017 年前后的腕部心率技术分水岭
2017 年前后,腕部光学心率正从「能不能测出来」转向「动起来还准不准」。此前公开可用的腕部 PPG 数据主要来自两项来源:一是各类竞赛与挑战赛切片,二是少数实验室的内部数据。这些材料要么被切成定长窗口、失去连续性,要么不对外发布。本库上线的时机恰好落在这个缺口上:它提供未经窗口化的连续波形,并把运动感知做得比同期任何公开腕部库都厚,陀螺仪、双量程加速度计、磁力计一次给齐。这个组合的稀缺性在随后几年被反复引用——引用者往往不是看中数据量,而是看中「有陀螺仪的那一份」。
此后腕部 PPG 数据库逐渐增多,方向却分化:一类走向规模与场景真实,一类走向临床监护,一类走向手机取光。本库没有跟任何一条线,停在一个特定的工程问题上,也因此保持了独特性。
8.2 与竞赛系腕部数据的血缘关系
腕部 PPG 去伪影的研究社区有一个共同起点:2015 年的 IEEE 信号处理杯,那次竞赛提供了带跑步机运动的腕部 PPG 数据,并设定了以平均绝对误差衡量心率估计的评测口径。此后大量论文沿用了这套口径,使得「在竞赛数据上误差多少」成为一种通用的语言。
本库与这套传统的关系是互补而非替代。论文明确建议把本库与竞赛数据互为对方的样本外测试集:在竞赛数据上训练的模型,拿到本库的运动段上试;反过来也一样。这个建议的动机很务实——两者都只有个位数受试者,单独看都不足以证明方法有效,但交叉验证能部分抵消单库过拟合。
8.3 下游研究的常见用法
从后续引用看,本库大致被用在四个位置。第一个位置是去伪影方法的消融实验,尤其是需要对比「加速度 vs 陀螺仪 vs 两者融合」的场合。第二个位置是算法鲁棒性的极端测试,把跑步与高阻力骑行段作为「最坏情况」报告。第三个位置是心率变异性研究的方法学检验,因为库里有逐拍标注的 R 峰,可以检验从 PPG 推算 HRV 的可行边界。第四个位置是教学,跑步段与步行段的波形差异直观,适合在课堂上演示伪影是怎么产生的。
这四个位置有一个共同点:都不需要大样本。它们需要的是「配置齐全、真值可靠、场景极端」,而这正是本库的强项。
8.4 同类资源的补位关系
把腕部可穿戴资源看成一个整体,各家的分工可以用一句话概括。
| 资源 | 一句话分工 | 与本库的互补方式 |
|---|---|---|
| 本库 wrist | 剧烈运动下的腕部 PPG 与运动感知 | 提供极端工况的外部验证 |
| 竞赛系腕部数据 | 标准评测口径的起点 | 提供可对标的误差语言 |
| 日常活动腕部库 | 真实场景下的连续心率 | 提供训练规模与场景多样性 |
| 消费级设备配对库 | 设备间的误差比较 | 提供产品级的外部参照 |
| 睡眠腕部库 | 夜间静息场景 | 提供静息与昼夜节律的对照 |
| 床旁 PPG 库 | 临床连续监测 | 提供病理状态下的波形样本 |
这张表的读法是:不要问「哪个最好」,要问「我想要的那一段工况,哪个库正好覆盖」。本库覆盖的是「人正在出汗、正在喘、正在摆动手臂」那一段,别的库大多刻意避开了这一段。
§9 使用指引
9.1 三种角色的路径
如果你的角色是算法工程师,目标是在现有流水线上加一个外部验证。先按 §5.2 拉清单与头文件,确认 s4_run 与 s3_high_resistance_bike 两条在手上;按 §5.4 做时间戳对齐;把模型输出与 §5.5 的逐拍心率对齐比误差。不要在训练集里混入本库,一旦混入,它就失去了外部验证的意义。
如果你的角色是研究生或教学人员,目标是讲清运动伪影的成因。取 s1_walk 与 s4_run 各一段十几秒的波形,并排画出,标出加速计三轴与 PPG 的同步起伏,让学生看到步频与心率在频谱上如何重叠。这个演示不需要任何预处理,也不需要完整流水线,十分钟可以做完。
如果你的角色是数据管理或评测设计者,目标是判断这个库能不能进评测集。要看三件事:评测是否要求大样本(要求则排除)、是否要求跨人群分层(要求则排除)、是否要求极端运动场景(要求则本库几乎无可替代)。三项过关,再按 §7.4 的矩阵设计分层报告。
9.2 一条最短的可用流程
从零到第一张图,最短路径是六步,全部可以在一台普通笔记本上完成。
#-- 第一步:环境
python3.11 -m pip install --quiet wfdb numpy matplotlib
#-- 第二步:取一条记录(12 路全取,约几兆)
python3.11 -c "import wfdb; r = wfdb.rdrecord('s4_run', pn_dir='wrist/1.0.0'); print(r.sig_name, r.fs, r.sig_len)"
#-- 第三步:读信号并对齐时间轴,第四步:画图,第五步:叠上参考心率,第六步:存结果
import numpy as np, wfdb, matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
rec = wfdb.rdrecord("s4_run", pn_dir="wrist/1.0.0")
ppg = rec.p_signal[:, rec.sig_name.index("wrist_ppg")]
acc = rec.p_signal[:, rec.sig_name.index("wrist_wide_range_accelerometer_y")]
ts = rec.p_signal[:, rec.sig_name.index("sample_times_for_all_signals_apart_from_ecg")]
t = np.unwrap(2 * np.pi * ts / 60.0) * 60.0 / (2 * np.pi)
t = t - t[0]
seg = (t >= 30) & (t < 45)
t0 = t[seg][0]
ppg_z = (ppg[seg] - ppg[seg].mean()) / ppg[seg].std()
acc_z = (acc[seg] - acc[seg].mean()) / acc[seg].std()
ann = wfdb.rdann("s4_run", "atr", pn_dir="wrist/1.0.0")
r_sec = ann.sample / rec.fs
r_in = r_sec[(r_sec >= 30) & (r_sec < 45)]
fig, ax = plt.subplots(2, 1, figsize=(10, 5), sharex=True)
ax[0].plot(t[seg] - t0, ppg_z, lw=0.8)
ax[0].plot(t[seg] - t0, acc_z, lw=0.8, alpha=0.6)
ax[0].set_ylabel("z-score")
ax[0].legend(["wrist PPG", "wide-range ACC Y"])
for x in r_in:
ax[1].axvline(x - 30, color="k", lw=0.6)
ax[1].plot(t[seg] - t0, rec.p_signal[seg][:, 0])
ax[1].set_ylabel("chest ECG (mV)")
ax[1].set_xlabel("seconds within segment")
fig.tight_layout()
fig.savefig("wrist_s4run_15s.png", dpi=140)
print("peaks in window:", len(r_in))
跑完会得到一张两行图:上半是 z-score 后的 PPG 与宽量程加速度计 Y 轴,下半是胸部 ECG 与 R 峰竖线。视觉上会立刻看到两件事:加速度计的周期与 PPG 的大幅起伏同步,而真实的心搏节律在下行图中稳定推进,两者并不同步。这就是运动伪影的全部直观含义,也是这个数据集存在的理由。
回看 §4.4 的案例:之所以默认用 s4_run,是因为它的干扰幅度最大,第一次画图就能看清现象;若用步行记录起步,伪影较弱,反而不容易建立直觉。
9.3 引用方式
正式引用应同时包含数据论文与 PhysioNet 平台记录。数据论文是 Jarchi 与 Casson 发表在 Data 期刊 2017 年第 2 卷第 1 篇的文章,DOI 为 10.3390/data2010001;平台侧引用是 PhysioNet 上的 Wrist PPG During Exercise 版本 1.0.0,DOI 为 10.13026/C2PQ1X。
Jarchi D, Casson AJ. Description of a Database Containing Wrist PPG Signals
Recorded during Physical Exercise with Both Accelerometer and Gyroscope
Measures of Motion. Data. 2017;2(1):1. doi:10.3390/data2010001
Wrist PPG During Exercise (version 1.0.0). PhysioNet.
doi:10.13026/C2PQ1X
许可为 ODC-BY 1.0,署名即可商用与再分发。实践中有两个细节:一是在方法章节写明使用的是哪个版本(1.0.0),因为文件校验和随版本变化;二是若对数据做了滤波或重采样,应在方法中说明,避免读者误以为拿到的是原始波形。
§10 AI 使用声明
| 项目 | 声明 |
|---|---|
| 数据性质 | 真实人体采集的生理信号,8 名健康成年志愿者,非合成、非增强 |
| 可否用于训练 | 可以,ODC-BY 1.0 允许商用与再分发,需署名 |
| 不建议的用途 | 直接用于临床心律判读、单独作为心率算法准确率的对外宣称依据 |
| 伦理约束 | 页面未载明伦理审批与知情同意细节,涉人研究发表时应自行补充合规审查 |
| 去标识程度 | 仅以 s1–s9 编号标识受试者,除年龄与性别外无其他身份信息 |
| 引用要求 | 数据论文与 PhysioNet 平台记录同时引用,并注明版本号 |
| 版本状态 | v1.0.0,2017 年 10 月 20 日发布,此后未更新 |
| 与模型输出的关系 | 本页所有描述来自官方页面、头文件与论文摘要,未对数据做统计推断 |
常见误读(避坑清单)
| 常见说法 | 实际 | 证据 |
|---|---|---|
| 这是一个腕部加速度计数据集 | 主信号是 PPG,加速度计是六路辅助通道 | 头文件中 wrist_ppg 为第 2 路,全库唯一光学通道 |
| 与帕金森或运动障碍有关 | 8 名 22–32 岁健康志愿者,无任何疾病标签 | 官方 Experimental Protocol 段写明 3 男 5 女、年龄范围 |
| 有现成的每分钟心率列 | 只有 R 峰时刻的注释文件,心率需自行换算 | 每条记录配 .atr,注释内容为 R 峰样本序号 |
| 所有信号都是原始未滤波 | 骑行 PPG 经过 15 Hz 低通,ECG 全部施加 50 Hz 陷波 | 官方页面写明两类处理的差异 |
| 一共 9 名受试者 | 编号到 s9 但 s7 缺失,实际 8 人 | RECORDS 清单中无任何 s7 开头记录 |
| 采样率严格等于 256 Hz | 名义 256 Hz,腕部实际平均约 255.7 Hz | 论文说明 Shimmer 用 Unix 时间戳,最坏时长差 1.21 秒 |
| 可以当作大库的训练集 | 合计约两小时,训练深模型会立刻过拟合 | 全库 19 条记录,高阻力骑行仅 3 条 |
| 与 ieeepg 是同一批数据的不同版本 | 两个独立采集,通道数、时长、惯性配置都不同 | ieeepg 为 5 维定长序列,本库为 13 路连续波形 |
| 每人都做齐了四种运动 | 只有 s3 做齐四项,s4 与 s9 各只有一条 | 逐条核对记录名与活动后缀 |
| 强度标注可以直接当数值用 | s6 三项全标 unknown,骑行只给档位不给功率 | 头文件末行注释与论文协议说明 |
FAQ
Q1:这个库到底测的是什么?
测的是腕部光电容积脉搏波在运动状态下被干扰的程度,以及能否被算法还原。配套的陀螺仪、加速度计与磁力计用来描述手怎么动,胸部 ECG 与人工标注的 R 峰用来回答「真实心率是多少」。
Q2:为什么只有 8 个人还能被反复引用?
因为它占住了一个稀缺位置:同时具备陀螺仪、双量程加速度计与胸部参考心电的公开腕部 PPG 库极少。需要验证「有陀螺仪可用时方法是否更好」的研究,绕不开它。
Q3:我可以拿它训练模型吗?
技术上可以,许可也允许。但 8 个人、约两小时的量不足以训练出可用模型,更常见的做法是在大库上训练,用它做外部验证或极端工况测试。
Q4:骑行记录为什么看起来比跑步干净?
不是运动更温和,而是骑行 PPG 在上传前做过 15 Hz 低通滤波,跑步记录没有。这个差异会影响跨活动比较,使用前必须确认口径。
Q5:s7 去哪了?
页面与论文都没有说明。RECORDS 清单里从 s1 跳到 s8,只有 8 个人,因此按编号范围推断人数会得出 9 人的错误结论。
Q6:R 峰标注可靠吗?
由人工逐条标注,时间基准以 ECG 首样本为 0 秒,可用原始 ECG 波形复核每一个点。库未提供第二标注者与一致性数据,个别记录不能排除错标。
Q7:能和 PPG-DaLiA 直接比误差吗?
不建议直接横比。受试者数、活动类型、时长、真值来源与评测划分都不同,公开论文里的数字也多在不同划分下得出。要做对照,需在同一批记录、同一分窗、同一指标下重跑。
Q8:这个库有临床用途吗?
没有。参与者是健康人,胸部 ECG 是单导联非诊断配置,无病理标签。它支撑的是信号处理研究,不是诊断研究。
Q9:为什么有很多 NaN?
腕部信号与 ECG 的采样不严格共时,记录末尾以 NaN 补齐到与 ECG 等长。读到末尾的 NaN 属正常现象,不应当作缺失值处理。
Q10:能用它做心率变异性分析吗?
可以,逐拍 R 峰时刻的分辨率达到 1/256 秒,足以支撑 HRV 的时域与频域指标。但要注意运动状态下的呼吸性窦性心律不齐会被运动节律干扰,解释时应谨慎。
Q11:怎么判断我下载的是不是最新版本?
当前只有 1.0.0 一个版本。用官方提供的 SHA256SUMS.txt 校验文件完整性,即可确认下载无损。
Q12:允许商用吗?
ODC-BY 1.0 允许商用与再分发,条件是署名。署名应包含数据论文与 PhysioNet 平台记录及版本号。
术语表
| 术语 | 含义 |
|---|---|
| PPG | 光电容积脉搏波,用光照射组织并测量反射或透射光量变化来反映血容量搏动 |
| 运动伪影 | 因传感器与皮肤相对运动、组织形变等因素混入的非心源性信号波动 |
| 反射式 PPG | 光源与光电探测器位于同侧,测量反射回来的光,腕部设备常用此结构 |
| R 峰 | 心电图中 QRS 波群的最尖锐正向峰,用于确定每次心搏的时刻 |
| 逐拍间期 | 相邻两个 R 峰之间的时间间隔,其倒数乘以 60 即瞬时心率 |
| HRV | 心率变异性,逐拍间期的波动程度,反映自主神经调节 |
| 陀螺仪 | 测量角速度的惯性器件,不受重力方向影响 |
| 低噪声加速度计 | 满量程较小、噪声底较低的加速度计,适合捕捉小幅运动 |
| 宽量程加速度计 | 满量程较大的加速度计,适合剧烈运动不至于削顶 |
| 磁力计 | 测量磁场强度的器件,可提供绝对航向参考 |
| 重力分离 | 从加速度计读数中剔除重力分量,只保留真实运动加速度 |
| 零相位滤波 | 正反两次滤波以抵消相位延迟,Matlab 中用 filtfilt 实现 |
| 陷波滤波 | 在特定窄频段大幅衰减的滤波器,常用于去除 50 Hz 工频干扰 |
| WFDB | PhysioNet 使用的生理信号存储格式,一条记录由头文件、数据文件与注释文件组成 |
| 头文件 | WFDB 记录的 .hea 文件,声明信号路数、采样率、样本数与每路信号的增益单位 |
| 注释文件 | WFDB 的 .atr 文件,存放事件时刻,本库中为 R 峰样本序号 |
| 留一受试者交叉验证 | 每次留出一名受试者的全部记录做测试,其余做训练 |
| 平均绝对误差 | 预测值与真值之差的绝对值的平均数,心率估计的常用指标 |
| 削顶 | 信号幅度超过量程上限后被截平,造成不可恢复的失真 |
| 零偏漂移 | 陀螺仪在静止时输出不为零且随时间缓慢变化的现象 |
| 采样率偏差 | 实际平均采样率与名义值不符,长时间记录会累积为时间错位 |
| 工频干扰 | 交流供电引起的 50 Hz 或 60 Hz 周期性干扰 |
| DAIMS | 面向 AI 与医疗数据集的文档化与验证框架,用于评估数据就绪度 |
| ODC-BY | 开放数据 commons 署名许可,允许商用与再分发,需署名 |
附录 A:核心数字核对表
| 项 | 值 | 来源 | 来源层级 |
|---|---|---|---|
| 受试者数 | 8 人(3 男 5 女) | 官方页面 Experimental Protocol | 官方页面 |
| 年龄范围 | 22–32 岁,均值 26.5 | 官方页面 Experimental Protocol | 官方页面 |
| 记录总数 | 19 条 | RECORDS 清单 |
官方文件 |
| 信号路数 | 每条 15 路 | 各记录 .hea 首行 | 官方文件 |
| 采样率 | 256 Hz(实际平均约 255.7 Hz) | .hea 首行与论文 | 官方文件 + 论文 |
| 记录总时长 | 约 113 分钟 | 逐条 .hea 样本数换算 | 官方文件 |
| 单条最短 | 220 秒 | s9_walk 头文件 | 官方文件 |
| 单条最长 | 589 秒 | s1_high_resistance_bike 头文件 | 官方文件 |
| 活动类别 | 4 类 | 官方页面与记录名 | 官方页面 |
| 发布版本 | v1.0.0 | 官方页面 | 官方页面 |
| 上线日期 | 2017-10-20 | 官方页面 | 官方页面 |
| DOI | 10.13026/C2PQ1X | 官方页面 | 官方页面 |
| 许可 | ODC-BY 1.0 | 官方页面 License 段 | 官方页面 |
| 骑行滤波 | 15 Hz 二阶 Butterworth 低通 | 官方页面 | 官方页面 |
| ECG 滤波 | 50 Hz 陷波 | 官方页面 | 官方页面 |
| 最坏对齐差 | 1.21 秒 | 论文正文 | 论文 |
附录 B:数字的来源分层
官方页面层(可直接核对):受试者人数与构成、年龄范围、四种活动的协议、设备型号、采样率 256 Hz、滤波设置、许可、DOI、上线日期。
官方文件层(下载头文件后核对):每条记录的信号路数、样本数、时长、通道名、单位、强度注释;RECORDS 清单中的记录名与受试者编号缺口。
论文层级(摘要与正文):实际平均采样率 255.69 Hz、最坏对齐差 1.21 秒、PPG 波长与传感器噪声底等硬件细节、留一法建议、与竞赛数据互补的建议。
未核实层:受试者的招募方式、伦理审批编号、知情同意形式、肤色与体型信息、第二标注者与一致性数据、s7 缺失原因。这些在官方页面与公开摘要中均未出现,任何声称知道其内容的说法都缺乏一手依据。
附录 C:完整参考文献
- Jarchi D, Casson AJ. Description of a Database Containing Wrist PPG Signals Recorded during Physical Exercise with Both Accelerometer and Gyroscope Measures of Motion. Data. 2017;2(1):1. doi:10.3390/data2010001
- Wrist PPG During Exercise (version 1.0.0). PhysioNet. doi:10.13026/C2PQ1X
- Pollard T, Moody BE, Lehman L, Gow B, Fernandes C, Xie C, Johnson A, Mark RG, Heldt T. PhysioNet as a global platform for biomedical research. Nature Health. 2026. doi:10.1038/s44360-026-00096-z
- Zhang Z, Pi Z, Liu B. TROIKA: A general framework for heart rate monitoring using wrist-type photoplethysmographic signals during intensive physical exercise. IEEE Transactions on Biomedical Engineering. 2015;62(2):522–531.
- Reiss A, Indlekofer I, Schmidt P, Van Laerhoven K. Deep PPG: Large-scale heart rate estimation with convolutional neural networks. Sensors. 2019;19(14):3079.
附录 D:任务代码
以下脚本做一次最小可复现的跨受试者测试:以 s4_run 为测试样本,其余跑步记录为参照,比较「直接用 PPG 主频当心率」与「用加速度计估计步频后排除」两种朴素策略的误差。
#-- 极简基线:比较两种朴素心率估计策略在 s4_run 上的误差
import numpy as np, wfdb
from scipy.signal import butter, filtfilt, welch
def load(name):
rec = wfdb.rdrecord(name, pn_dir="wrist/1.0.0")
ppg = rec.p_signal[:, rec.sig_name.index("wrist_ppg")]
acc = rec.p_signal[:, rec.sig_name.index("wrist_wide_range_accelerometer_z")]
return ppg, acc, rec.fs
def dominant(x, fs, lo=0.7, hi=3.2):
b, a = butter(2, [lo / (fs / 2), hi / (fs / 2)], btype="band")
y = filtfilt(b, a, x)
f, p = welch(y, fs=fs, nperseg=int(fs * 8))
m = (f >= lo) & (f <= hi)
return f[m][np.argmax(p[m])]
ppg, acc, fs = load("s4_run")
ann = wfdb.rdann("s4_run", "atr", pn_dir="wrist/1.0.0")
ibi = np.diff(ann.sample) / fs
hr_ref = np.median(60.0 / ibi)
hr_ppg = dominant(ppg, fs) * 60
step_f = dominant(acc, fs, 0.5, 5.0)
print("参考心率", round(hr_ref, 1), "PPG 主频心率", round(hr_ppg, 1), "步频", round(step_f, 1))
print("绝对误差", round(abs(hr_ppg - hr_ref), 1))
这段代码的价值不在精度,而在示范如何把参考心率与估计心率放在同一口径下比较。实际研究应改为滑窗逐段估计,并按 §7.4 的矩阵逐受试者报告,而不是只报一个中位数误差。回看 §4.4 的案例:单条记录的一次比较,只能说明这段代码通了,不能说明方法有效。
附录 E:如果只记五件事
- 主信号是腕部 PPG,不是加速度计;加速度计与陀螺仪是辅助运动描述通道。
- 8 名健康年轻人、19 条记录、四类运动、合计约两小时;不涉及任何疾病人群。
- 每条记录 15 路信号:胸部 ECG、腕部 PPG、陀螺仪三轴、双量程加速度计各三轴、磁力计三轴、时间戳。
- 心率真值来自人工标注的 R 峰时刻,需自行换算;骑行 PPG 已低通、步行跑步未滤波。
- 它适合做外部验证与极端工况测试,不适合训练大模型,也不适合任何临床判读。
编辑说明
核验时间:2026 年 10 月 28 日。核验方式:抓取 PhysioNet 官方项目页面与数据说明页;下载 RECORDS 清单与全部 19 条记录的头文件核对信号路数、采样率、样本数与强度注释;查阅数据论文摘要。未覆盖内容:受试者招募与伦理审批细节、肤色与体型的原始记录、第二标注者一致性数据、s7 缺失原因。文中凡涉及分布形态的判断均未展开,规模类数字只保留受试者数、记录数、时长与通道数四项。与库内 ieeepg、ppg-dalia 两篇的区隔已写入 §6.1 与 §6.4。审校由千方病案医学编辑部交叉完成。
相关数据集导航
以下为站内 AI-Ready 数据集百科中与本词条共享多个主题标签的相关数据集,按相关度降序排列:
- ieeepg — 共享标签:生理信号 / 可穿戴传感 / 心电信号 / 时序数据
- butqdb — 共享标签:生理信号 / 可穿戴传感 / 心电信号
- drivedb — 共享标签:生理信号 / 可穿戴传感 / 心电信号
- bidsleep-dataset — 共享标签:生理信号 / 可穿戴传感 / 心电信号 / 时序数据
- in-gauge-and-en-gage — 共享标签:生理信号 / 可穿戴传感 / 时序数据
- mhealth — 共享标签:生理信号 / 可穿戴传感 / 时序数据
- uci-har — 共享标签:生理信号 / 可穿戴传感 / 时序数据
- wearable-exercise-frailty — 共享标签:生理信号 / 可穿戴传感 / 心电信号
- ephnogram — 共享标签:生理信号 / 可穿戴传感 / 心电信号
- cebsdb — 共享标签:生理信号 / 可穿戴传感 / 心电信号
导航说明:本章节由全站统一标签体系自动计算生成(标签重合度算法),双向可达;点击链接可跳转至对应数据集词条。

