第四节:心电数据处理任务设计
好,咱们接着聊。这一节要讲的是心电数据处理的核心任务——数字滤波、基线漂移去除,还有QRS波检测。说实话,这部分是我个人觉得整个监护系统里最有意思的地方。你想想看,原始心电信号进了ADC之后,那波形简直没法看,各种噪声混在里面。怎么把这些噪声去掉,把有用的心电特征提取出来,这就是我们要解决的问题。
4.1 数字滤波设计
先说说滤波。心电信号里最常见的干扰就是50Hz工频噪声。我在项目中遇到过好几次,明明硬件上已经做了屏蔽和滤波,但采集到的信号里还是有明显的50Hz分量。后来我总结了一下,这玩意儿很难完全靠硬件滤干净,数字滤波是必须的。
4.1.1 50Hz工频陷波器
工频陷波,说白了就是把50Hz这个频率点上的信号干掉。我习惯用IIR陷波器,阶数低,计算量小,适合在RTOS环境里跑。
这里给一个二阶IIR陷波器的系数计算方法:
/* 50Hz陷波器设计参数 */
#define SAMPLE_RATE 500 /* 采样率500Hz */
#define NOTCH_FREQ 50.0f /* 陷波频率 */
#define Q_FACTOR 30.0f /* 品质因数 */
/* 计算系数 */
float omega = 2.0f * PI * NOTCH_FREQ / SAMPLE_RATE;
float alpha = sin(omega) / (2.0f * Q_FACTOR);
float b0 = 1.0f;
float b1 = -2.0f * cos(omega);
float b2 = 1.0f;
float a0 = 1.0f + alpha;
float a1 = -2.0f * cos(omega);
float a2 = 1.0f - alpha;
/* 归一化系数 */
b0 /= a0; b1 /= a0; b2 /= a0;
a1 /= a0; a2 /= a0;
4.1.2 0.5-100Hz带通滤波器
带通滤波器的目的是保留心电信号的有效频段。0.5Hz以下的是基线漂移,100Hz以上的主要是肌电噪声。嗯,这里要注意,如果采样率是500Hz,那设计带通滤波器时要留够过渡带。
我推荐用FIR滤波器,虽然计算量大一点,但线性相位特性好,不会造成波形失真。对于QRS波检测来说,波形保真度很重要。
/* FIR带通滤波器系数示例(51阶,500Hz采样率) */
#define FIR_ORDER 50
float fir_coeff[FIR_ORDER + 1] = {
/* 这里放实际计算出的系数 */
/* 可以用Matlab的fdatool生成 */
};
/* 滤波函数 */
float fir_filter(float input) {
static float buffer[FIR_ORDER + 1] = {0};
static int index = 0;
float output = 0.0f;
int i, j;
buffer[index] = input;
j = index;
for(i = 0; i <= FIR_ORDER; i++) {
output += fir_coeff[i] * buffer[j];
j = (j == 0) ? FIR_ORDER : j - 1;
}
index = (index == FIR_ORDER) ? 0 : index + 1;
return output;
}
4.2 基线漂移去除
基线漂移是低频干扰,主要来自呼吸运动、电极移动等。频率一般在0.5Hz以下。去除基线漂移,我常用的方法有两种:高通滤波法和中值滤波法。
4.2.1 高通滤波法
最简单的方法就是用0.5Hz的高通滤波器。但要注意,心电信号的ST段也含有低频成分,高通滤波可能会让ST段变形。所以,如果要做ST段分析,这个方法要慎用。
4.2.2 中值滤波法
我个人更推荐中值滤波法。它的原理很简单:用一个足够宽的窗口对信号做中值滤波,提取出基线,然后用原始信号减去基线。
/* 中值滤波去除基线漂移 */
#define BASELINE_WINDOW 200 /* 窗口宽度,200ms @500Hz */
float remove_baseline(float input, float *buffer, int *index) {
float baseline;
float temp[200];
int i, j;
/* 更新环形缓冲区 */
buffer[*index] = input;
*index = (*index + 1) % BASELINE_WINDOW;
/* 复制并排序 */
for(i = 0; i < BASELINE_WINDOW; i++) {
temp[i] = buffer[i];
}
/* 冒泡排序取中值 */
for(i = 0; i < BASELINE_WINDOW - 1; i++) {
for(j = 0; j < BASELINE_WINDOW - i - 1; j++) {
if(temp[j] > temp[j + 1]) {
float t = temp[j];
temp[j] = temp[j + 1];
temp[j + 1] = t;
}
}
}
baseline = temp[BASELINE_WINDOW / 2];
return input - baseline;
}
4.3 QRS波检测算法
QRS波检测是心电分析的核心。说白了,就是要找到每个心跳的位置。我做过很多种算法,从简单的阈值法到复杂的小波变换,但在嵌入式系统里,我推荐用Pan-Tompkins算法。这个算法计算量适中,检测准确率高,非常适合在RTOS任务里跑。
4.3.1 算法流程
Pan-Tompkins算法的核心步骤:
- 带通滤波:5-15Hz,突出QRS波的能量
- 差分运算:突出斜率变化
- 平方运算:增强QRS波特征
- 移动窗口积分:平滑信号,便于阈值检测
- 自适应阈值:根据信号质量动态调整
/* Pan-Tompkins QRS检测核心代码 */
typedef struct {
float signal; /* 原始信号 */
float filtered; /* 带通滤波后 */
float derivative; /* 差分 */
float squared; /* 平方 */
float integrated; /* 积分 */
} qrs_channel_t;
/* 自适应阈值 */
typedef struct {
float signal_peak; /* 信号峰值 */
float noise_peak; /* 噪声峰值 */
float threshold; /* 检测阈值 */
int refractory_period; /* 不应期,200ms */
int last_qrs_index; /* 上次QRS位置 */
} qrs_detector_t;
int qrs_detect(float sample, qrs_channel_t *ch, qrs_detector_t *det) {
/* 1. 带通滤波 */
ch->filtered = bandpass_5_15hz(sample);
/* 2. 差分 */
ch->derivative = (2 * ch->filtered + ch->filtered_prev1
- ch->filtered_prev3 - 2 * ch->filtered_prev4) / 8;
/* 3. 平方 */
ch->squared = ch->derivative * ch->derivative;
/* 4. 移动窗口积分(150ms窗口) */
ch->integrated = moving_window_integrate(ch->squared, 75);
/* 5. 阈值检测 */
if(ch->integrated > det->threshold) {
/* 检查不应期 */
if(current_index - det->last_qrs_index > det->refractory_period) {
det->last_qrs_index = current_index;
/* 更新信号峰值 */
det->signal_peak = 0.125 * ch->integrated + 0.875 * det->signal_peak;
return 1; /* 检测到QRS */
}
} else {
/* 更新噪声峰值 */
det->noise_peak = 0.125 * ch->integrated + 0.875 * det->noise_peak;
}
/* 更新阈值 */
det->threshold = det->noise_peak + 0.25 * (det->signal_peak - det->noise_peak);
return 0;
}
4.4 RTOS任务设计要点
把这些算法放到RTOS里,有几个要点要注意:
| 任务名称 | 优先级 | 周期 | 说明 |
|---|---|---|---|
| 数据采集任务 | 最高 | 2ms (500Hz) | 从ADC读取数据,放入队列 |
| 滤波处理任务 | 高 | 2ms | 执行陷波、带通滤波 |
| 基线去除任务 | 中 | 2ms | 中值滤波去除基线 |
| QRS检测任务 | 中 | 2ms | Pan-Tompkins算法 |
| 心率计算任务 | 低 | 1000ms | 统计RR间期,计算心率 |
好了,这一节的内容就到这里。数字滤波、基线漂移去除、QRS波检测,这三块是心电数据处理的基础。下一节我们会讲怎么把这些任务整合到RTOS里,实现完整的调度方案。