PCG+ECG同步心音数据集:从预处理到一维CNN分类的完整实践
2026/9/16 11:14:12 网站建设 项目流程

简介:一个面向心音信号处理与疾病辅助诊断的专用数据集,包含3126条心音图(PCG)记录与同步心电图(ECG),单次记录时长在10~60秒之间,每条记录均提供同步双模态信号,便于进行多模态分析。数据采集严格参照听诊标准,涵盖主动脉区、肺动脉瓣区、三尖瓣区及二尖瓣区四个最常见听诊位置,可用于心音分类、心音分割、异常心音筛查、心脑血管疾病辅助诊断等任务。压缩包内共2000个文件,整体约206.81MB,文件类型以mat数值文件、wav音频、hea头文件、dat原始信号记录为主,其中mat与wav可直接在MATLAB或Python中读取,适合进行信号预处理与特征工程。压缩包另附REFERENCE系列CSV标签文件以及records-normal与records-abnormal划分列表,便于快速构建训练集和测试集,免去手动整理标签的麻烦。已有378人学习下载,适合生物医学工程、电子与信息、机器学习等相关专业的学生与研究人员,借助该数据快速开展心音信号分析实验并验证算法模型。

1. 心音图数据集:3126条PCG与ECG同步记录能做什么

心音图(PCG)记录的是心脏瓣膜开闭与血流冲击产生的机械振动,心电图(ECG)记录的是心脏电活动。两者在临床上互为补充:ECG提供心脏节律的时相基准,PCG提供瓣膜状态的直接声学证据。这个包含3126个PCG记录、且每一条都带同步ECG信号的数据集,在公开心音数据里属于规模与信息量都可用的一档,尤其适合做心音分类、异常心音检测、S1/S2自动分割这类任务,研究单纯心音特征的同事也可以拿同步ECG当标注基准来用。

数据集来自医院场景的采集流程,覆盖正常、瓣膜病、心律失常等常见情况的听诊记录。对做医学信号处理或者多模态融合的人来说,真正有价值的是这3126条记录的同步性:不需要自己雇人做听诊标注,直接用ECG定位心音事件,这在训练集和测试集划分、模型评估时能省掉大量人工。读这篇文章的人,大多是在找“怎么把这个PCG+ECG数据集跑起来”的完整路径。下面按一套可复现的方案展开。

2. 心音图数据集的PCG与ECG文件结构:从读取到波形可视化

2.1 PCG与ECG信号的采集参数和时相对应关系

这套数据集用电子听诊器同时采集两路信号,原始波形通常是双通道的wav文件,第一通道是心音,第二通道是同步ECG。心音的频率范围主要落在20Hz到200Hz之间,瓣膜开闭的瞬态成分能到600Hz左右,所以常见采样率是2000Hz到4000Hz;ECG的QRS波群能量集中在0.5Hz到45Hz,同一文件用同一采样率存下来,并不会损失关键形态。

时相对应关系上,S1心音出现在ECG的R波后20ms到50ms,由房室瓣关闭产生;S2心音在T波终点附近,由主动脉瓣与肺动脉瓣关闭产生。这个对应关系是后面做自动分割的核心依据。很多公开数据集只给单个PCG通道,而这个数据集同时给了ECG通道,就可以用R峰位置作为锚点来切分心搏,而不需要跑复杂的心音包络检测算法。

2.2 用Python读取wav文件并分离PCG与ECG通道

读取这类双通道记录,标准做法是用scipy的wavfile模块,它直接返回采样率和原始PCM数据。

import numpy as np import scipy.io.wavfile as wavfile sample_rate, data = wavfile.read('record_0001.wav') print('采样率:', sample_rate, 'Hz') print('数据形状:', data.shape, 'dtype:', data.dtype) pcg = data[:, 0].astype(np.float64) # 第一通道:心音 ecg = data[:, 1].astype(np.float64) # 第二通道:心电图 print('PCG时长(秒):', len(pcg) / sample_rate)

读取后把整型PCM转成float64,是为了后续滤波和特征计算时避免整型溢出。数据形状通常是(N, 2),N是总采样点数,第一维是时间轴。如果文件的位数是16bit,取值范围在-32768到32767之间,后续如果要把振幅归一化到[-1,1],直接除以32768.0就好。

可视化是检查数据质量的第一步。把两路信号画在同一个时间轴上,先看心音包里有没有明显的S1、S2形态,再看ECG的R波是否清晰可辨。

import matplotlib.pyplot as plt time = np.arange(len(pcg)) / sample_rate fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 6), sharex=True) ax1.plot(time, pcg, color='steelblue', linewidth=0.8) ax1.set_ylabel('PCG (mV)') ax1.set_title('心音图波形') ax2.plot(time, ecg, color='crimson', linewidth=0.8) ax2.set_ylabel('ECG (mV)') ax2.set_xlabel('时间 (秒)') plt.tight_layout() plt.savefig('pcg_ecg_preview.png', dpi=150)

画图时把两路信号用不同颜色区分,检查时间轴对齐情况。如果PCG的包络和ECG的R波有明显的固定延迟,说明采集硬件本身就有延迟;如果延迟超过50ms,后续做R波锚定时要先把两路信号做互相关校正。这一步不要跳过,直接决定后面分割的精度。

2.3 数据集的统计分布与切分注意事项

拿到3126条记录后,先做一轮统计,确认每条音频的时长和采样率是否一致。常见情况是采样率统一、但时长从2秒到12秒不等。时长差异大的数据集,做批量训练之前需要统一到固定长度,或者按心动周期切块。

统计项常见范围处理建议
采样率2000-4000 Hz若不统一,重采样到2000Hz
时长2-12秒按平均心动周期切成定长片段
通道数2(PCG+ECG)先拆通道,再分别处理
位深16bit除以32768归一化

切分时还要注意一个容易踩的坑:如果把同一条记录的心音片段放进训练集和验证集,会导致数据泄露,模型验证指标虚高。常见做法是按record级别划分数据集,也就是整条wav文件只能出现在训练、验证或测试中的某一个集合里。3126条记录按8:1:1划分,训测比对这个量级的数据是合理的。

3. 心音图预处理链路:滤波、R波定位与心搏节段分割

3.1 带通滤波参数:PCG与ECG各用哪一组截止频率

PCG信号在采集过程中会混入环境噪声、呼吸音和传感器摩擦声,直接用于建模会引入大量无效特征。滤波这一步在传统特征工程和深度学习管线下都需要做,差别只是放在特征提取前还是网络输入前。PCG的带通范围,我一般设20Hz到600Hz,低端切掉基线漂移和呼吸音,高端保留瓣膜开闭的瞬态成分。ECG则设0.5Hz到45Hz,保留QRS波群为主的能量带。

用scipy的butter和filtfilt实现零相位带通滤波:

from scipy.signal import butter, filtfilt def bandpass_filter(signal, lowcut, highcut, fs, order=4): nyquist = 0.5 * fs low = lowcut / nyquist high = highcut / nyquist b, a = butter(order, [low, high], btype='band') return filtfilt(b, a, signal) pcg_filt = bandpass_filter(pcg, 20, 600, sample_rate) ecg_filt = bandpass_filter(ecg, 0.5, 45, sample_rate)

filtfilt做双向滤波,零相位延迟,保证滤波后的波形和原始波形的R波位置对齐,不会因为滤波引入时移。order为4时过渡带足够陡,对心音这种瞬态信号来说,不会明显过冲。如果明显听到高频噪声残留,把PCG高阶截止频率降到400Hz;如果ECG的T波模糊,把低端截止频率调到1Hz以下重新试。

3.2 基于ECG的R峰检测:跨过心音包络检测的坑

单纯在PCG上做心音分割,需要先算包络(Hilbert或香农能量),再找峰值配对,算法复杂且容易在瓣膜病理性杂音干扰下出错。有了同步ECG,直接检测R峰然后按生理时序推算S1/S2窗口,可靠性高很多。R波在ECG里是能量最集中的波段,用scipy的find_peaks就够用。

from scipy.signal import find_peaks abs_ecg = np.abs(ecg_filt) threshold = 0.6 * np.mean(abs_ecg) + 0.4 * np.percentile(abs_ecg, 95) peaks, properties = find_peaks( ecg_filt, distance=int(0.4 * sample_rate), # 心率上限150bpm对应的最小间隔 height=threshold, prominence=threshold * 0.5 ) r_peaks_sec = peaks / sample_rate print('检测到R峰数量:', len(r_peaks_sec), '平均心率(bpm):', 60 / np.mean(np.diff(r_peaks_sec)))

distance参数控制两个R峰之间的最小采样点数,按心率上限150次/分来设,防止T波被误检成R波。threshold用的是绝对幅值和分位数组合,比单一固定阈值适应性强,因为ECG在运动过程中基线会漂移。prominence参数进一步筛掉低幅尖峰。检测完R峰后,把R峰位置在原始图上叠画一遍,肉眼检查漏检和误检比例,这个步骤值得做一次。

3.3 按心动周期切分PCG与ECG:S1和S2的时窗怎么定

有了R峰时间戳,按生理知识定义每个心动周期的切分范围。每个R峰两侧各取一段,比如R峰前200ms到下一个R峰前200ms,这个区间恰好覆盖S1、收缩期、S2、舒张期四个部分。S1位于当前R峰后20-50ms,S2位于下一个R峰前约100ms。具体做法是取相邻R峰的间隔作为周期T,从当前R峰前0.2T到下一个R峰前0.2T作为一段。

def split_heartbeats(pcg_filt, ecg_filt, r_peaks, fs, pad_ratio=0.2): beats = [] for i in range(len(r_peaks) - 1): start = int(r_peaks[i] - pad_ratio * (r_peaks[i+1] - r_peaks[i])) end = int(r_peaks[i+1] - pad_ratio * (r_peaks[i+1] - r_peaks[i])) if end - start < int(0.4 * fs): continue pcg_beat = pcg_filt[start:end] ecg_beat = ecg_filt[start:end] beats.append((pcg_beat, ecg_beat)) return beats beats = split_heartbeats(pcg_filt, ecg_filt, peaks, sample_rate)

切分时过滤掉过短的片段,这类片段通常是心率异常或者R峰误检导致的,保留下来会污染训练数据。这套切分逻辑可以做成一个通用函数,批量处理3126条记录时只需要一个循环。所有切出来的片段保存成npy文件按标签归档,后续加载时直接np.load,比每次训练都重新读wav和处理要快。训练集和测试集的切分必须在切分心搏之前按record维度完成。

处理阶段输出保存格式说明
原始wav双通道整型wav保持原始采样率
滤波后双通道浮点npy消除呼吸音与基线漂移
R峰检测时间戳数组npy与wav文件名一一对应
心搏切分定长片段npz每个片段附带源记录ID

4. 用PCG与ECG做心音分类:特征向量构造与PyTorch模型训练

4.1 心音分类任务定义与模型输入形态选择

心音分类的常见任务是把每个心动周期划分为正常或异常两类,也可以细分为瓣膜狭窄、二尖瓣反流、主动脉瓣病变等类别。3126条记录的量级,做二分类或三分类是合适的;类别超过5个,数据集规模就会显得不足,需要靠数据增强或用预训练模型迁移。

输入形态的选择上,三类方案各有取舍。原始波形经过滤波和重采样后直接输入一维卷积网络,是最省事的方式,本次预处理链路产出的心搏片段正好匹配;用短时傅里叶变换或梅尔频谱转成二维图像再用ResNet做,特征是稳定的,但会丢失时序上的相位信息;第三种是把PCG包络和ECG的RR间期作为手工特征,训练传统机器学习模型,适合做基线。本文用第一种方案,把切好的PCG片段长度统一到固定采样点数,喂给一维CNN。

4.2 PyTorch一维CNN模型:轻量化网络结构

对心音这种一维生理信号,一维卷积网络比二维网络参数更少、收敛更快。下面这个模型包含三个卷积块和一个分类头。

import torch import torch.nn as nn class PCGClassifier(nn.Module): def __init__(self, input_length=4000, num_classes=2): super().__init__() self.conv1 = nn.Sequential( nn.Conv1d(1, 32, kernel_size=15, stride=2, padding=7), nn.BatchNorm1d(32), nn.ReLU(), nn.MaxPool1d(2) ) self.conv2 = nn.Sequential( nn.Conv1d(32, 64, kernel_size=11, stride=2, padding=5), nn.BatchNorm1d(64), nn.ReLU(), nn.MaxPool1d(2) ) self.conv3 = nn.Sequential( nn.Conv1d(64, 128, kernel_size=7, stride=2, padding=3), nn.BatchNorm1d(128), nn.ReLU(), nn.AdaptiveAvgPool1d(1) ) self.fc = nn.Linear(128, num_classes) def forward(self, x): x = x.unsqueeze(1) x = self.conv1(x) x = self.conv2(x) x = self.conv3(x) x = x.squeeze(-1) return self.fc(x)

输入张量形状是(batch, input_length),在forward里通过unsqueeze(1)变成单通道,送进卷积网络时支持批量计算。kernel_size取15和11,对应到2000Hz采样率下是7.5ms和5.5ms的时间窗,既能捕捉瓣膜开闭的瞬态又不会过多关注高频噪声。AdaptiveAvgPool1d(1)把时序维压缩成1,让全连接层的输入维度与输入长度解耦——也就是说,训练时用4000点,部署时遇到不同时长的片段也能前向传播。BatchNorm1d放在激活函数前面,能加速训练收敛并降低对学习率的敏感性。

4.3 训练参数选择:这个量级该用什么学习率与批量大小

训练数据是切好的心搏片段,每个片段的标签来自数据集的注释或按record维度映射。训练循环的常见写法如下。

def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss, correct, total = 0.0, 0, 0 for pcg_batch, labels in loader: pcg_batch = pcg_batch.to(device).float() labels = labels.to(device).long() optimizer.zero_grad() outputs = model(pcg_batch) loss = criterion(outputs, labels) loss.backward() optimizer.step() total_loss += loss.item() * pcg_batch.size(0) preds = outputs.argmax(dim=1) correct += (preds == labels).sum().item() total += labels.size(0) return total_loss / total, correct / total

每轮迭代先清零梯度,再前向传播计算损失,然后反向传播并更新参数。这里的criterion用CrossEntropyLoss,它内部已经包含softmax,所以模型中不需要额外加log_softmax。学习率是医学信号里最容易调错的参数,1e-3适合充分训练时从零学起的CNN,1e-4到5e-5适合迁移学习继续微调的阶段。批量大小受显存限制,一维信号复杂度低,32到64都是安全的起点,输入长度4000点、批量64时,一块12G显存的卡完全够用。

超参数推荐值调整依据
学习率1e-3(从头训练)、1e-4(微调)损失不降时降到1/10
批量大小32-64显存允许范围内尽量大
训练轮数30-60早停,看验证损失
优化器AdamWweight_decay设1e-4
权重初始化默认初始化不需要特殊处理

训练过程中要监控验证损失而不是训练损失。心音数据的类别通常有较大不平衡,比如正常样本占七成,直接按准确率评估会让模型偏向多数类,F1分数和敏感度、特异度都应该一起打印。数据量不够的时候可以给ECG也同时输入进一个并联分支,做简单的双模态融合,尾部concat后接分类头,把这个数据集的最大价值利用起来。

5. 用同步ECG做片段级验证与跨模态校准的落地技巧

选用同步ECG作为一个额外的监督信号,而不是只把它当作R峰参考,这是这个数据集区别于纯PCG数据集的比较优势。做法是把ECG的QRS持续时间、RR间期变异度算出来,作为辅助特征拼接到CNN的中间层输出上,帮助区分心律不齐和单纯瓣膜杂音。

落地的第一步是从验证集里随机抽出300段切好的心搏,把PCG波形和ECG波形按时间轴叠画并保存成图。用R峰位置验证模型分类正确样本的S1时间分布,如果正常类样本的S1集中出现在R波后20ms到60ms,异常类样本的S1位置方差明显变大,就说明模型学到的是病理特征而不是波形幅度的偶然偏差。这一步在论文复盘时也特别有用。

第二个技巧是用RR间期做数据清洗。滤波后的ECG里,RR间期变异超过20%的片段,多半是R峰误检或早搏,把这些片段单独抽出来人工复核,而不是直接扔进训练集。3126条记录里这类片段通常占5%以内,清洗后分类效果的提升往往比调整网络结构更明显。

第三个技巧落在测试时的滑动窗口预测上。推理阶段的输入和训练输入长度不一致,可以用一个固定长度的窗在整条PCG上滑动,得到多个窗口的概率输出,取多数投票作为这条记录的最终分类结果。窗口之间重叠50%,既覆盖了心动周期的完整边界,又不会引入过多冗余计算。注意这条操作要在与训练数据相同的前置处理和归一化参数下执行,否则窗口概率会系统性偏移。

最后做一个五折交叉验证来评估3126条记录的综合性指标,五折按record维度划分,每折大约625条记录做验证。最终要看的不是单折最高准确率,而是五折平均F1和标准差。如果标准差超过0.04,说明有某些折里混入了分布外的记录类型,这时回到R峰检测结果检查该折数据的波形质量,而不是无脑加正则化系数。这套流程跑完,再回头调整滤波范围,训练参数一版比一版更稳。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询