4. 肌电信号预处理:滤波、去噪与基线校正

好,咱们进入第四章。说实话,肌电信号预处理这块,是我在实验室里踩坑最多的地方。你想想看,从皮肤表面采集到的信号,那叫一个「脏」——工频干扰、运动伪迹、基线漂移,啥都有。如果不把这些乱七八糟的东西处理干净,后面做肌肉协同分析,结果基本没法看。

我个人习惯把预处理分成三步走:滤波、去噪、基线校正。每一步都有它的门道。咱们一个一个来聊。

4.1 为什么要做预处理?

先问个问题:你从电极上直接采到的信号,真的是肌肉活动产生的吗?

答案是:不完全是。原始EMG信号里,混杂着各种噪声。我遇到过最夸张的一次,受试者只是轻轻动了一下手臂,信号里就出现了一个巨大的尖峰,差点把放大器给饱和了。后来一查,是电极线接触不良。

所以,预处理的目的就三个:

  • 滤掉无关频率——比如50Hz的工频干扰
  • 去除突发噪声——比如电极松动产生的尖峰
  • 校正基线漂移——让信号回到零电平附近

说白了,就是让信号「干净」到能反映真实的肌肉激活模式。

4.2 滤波:把该留的留下,该扔的扔掉

滤波是预处理的第一步。EMG信号的有效能量主要集中在20-500Hz之间。低于20Hz的,大多是运动伪迹;高于500Hz的,基本是高频噪声。

我常用的滤波器组合是这样的:

滤波器类型 截止频率 作用
高通滤波器 20-30 Hz 去除低频漂移和运动伪迹
低通滤波器 450-500 Hz 去除高频噪声
带阻滤波器 48-52 Hz 去除工频干扰(50Hz)

嗯,这里要注意:滤波器的阶数不要选太高。我见过有人用8阶巴特沃斯滤波器,结果信号相位失真得一塌糊涂。我个人建议用4阶,零相位滤波(用filtfilt函数),这样能避免相位偏移。

给你看一段我常用的Python代码:

import numpy as np
from scipy.signal import butter, filtfilt

def preprocess_emg(signal, fs=1000):
    # 高通滤波 20Hz
    b, a = butter(4, 20/(fs/2), btype='high')
    signal = filtfilt(b, a, signal)
    
    # 低通滤波 450Hz
    b, a = butter(4, 450/(fs/2), btype='low')
    signal = filtfilt(b, a, signal)
    
    # 带阻滤波 50Hz
    b, a = butter(4, [48/(fs/2), 52/(fs/2)], btype='bandstop')
    signal = filtfilt(b, a, signal)
    
    return signal

这段代码我用了好几年,基本没出过问题。但有一点要提醒你:采样率fs一定要设对。如果采样率是2000Hz,那奈奎斯特频率就是1000Hz,截止频率要相应调整。

4.3 去噪:别让噪声毁了你的数据

滤波能解决大部分问题,但有些噪声是滤不掉的。比如电极突然松动产生的尖峰,或者受试者咳嗽引起的肌电爆发。这些噪声在频域上和真实信号重叠,没法用滤波器分离。

那怎么办?我一般用两种方法:

  • 阈值法:设定一个幅度阈值,超过阈值的点直接剔除或替换。阈值怎么定?我习惯用信号标准差的3-5倍。
  • 小波去噪:把信号分解到不同尺度,把噪声所在的小波系数置零,再重构信号。这个方法对非平稳噪声特别有效。

我曾经遇到过一个案例:受试者在实验过程中打了个喷嚏,EMG信号里出现了一个巨大的尖峰。用阈值法处理后,数据基本恢复了正常。但要注意,阈值不能设得太低,否则会把真实的肌肉爆发信号也给削掉。

避坑指南: 我曾经因为阈值设得太低,把受试者的一次最大自主收缩(MVC)信号给削掉了大半。后来做肌肉协同分析时,发现有一个协同模式怎么也提取不出来。折腾了两天,才发现是预处理阶段把关键数据给误删了。所以,去噪时一定要保留原始数据备份,处理完后再对比一下。

4.4 基线校正:让信号回到零电平

基线漂移是个很烦人的问题。你想想看,受试者刚开始放松时,信号应该在0mV附近。但过了一段时间,由于电极极化、皮肤出汗等原因,基线可能慢慢飘到了0.1mV甚至更高。

基线校正的方法很简单:

  1. 取一段静息状态的数据(比如受试者完全放松时的信号)
  2. 计算这段数据的平均值
  3. 从整个信号中减去这个平均值

但这里有个坑:静息状态的数据段怎么选?我建议在实验开始前,先记录5-10秒的静息数据。如果实验过程中需要多次校正,可以在每个动作之间插入一段静息期。

代码实现也很简单:

def baseline_correct(signal, baseline_segment):
    baseline_mean = np.mean(baseline_segment)
    corrected_signal = signal - baseline_mean
    return corrected_signal

嗯,就这么几行。但效果立竿见影。

4.5 预处理流程总览

说了这么多,咱们来画个流程图,把整个预处理流程串起来。我个人觉得,可视化能帮你更好地理解每一步之间的关系。

EMG信号预处理流程 原始EMG信号 滤波(高通+低通+带阻) 去噪(阈值法/小波去噪) 基线校正

你看,整个流程是线性的。但实际工作中,我经常需要来回调整。比如滤波后发现噪声还是很大,那就得回去调整截止频率或者去噪参数。预处理不是一锤子买卖,而是一个迭代优化的过程。

小技巧: 我习惯在预处理完成后,把处理前后的信号画在一起对比。如果处理后的信号看起来「太干净」了,比如完全没有毛刺,那可能过度滤波了。好的预处理,应该保留信号的生理特征,同时去除噪声。

4.6 常见问题与解决方案

最后,我总结几个我在项目中遇到过的问题,供你参考:

  • 问题:滤波后信号幅度变小了
    原因:滤波器增益设置不当,或者截止频率太靠近信号主频。
    解决:检查滤波器参数,确保通带增益为1。
  • 问题:去噪后信号出现「阶梯状」
    原因:阈值法处理时,对超过阈值的点直接置零,导致信号不连续。
    解决:改用插值或平滑方法替换异常点,而不是直接置零。
  • 问题:基线校正后信号仍然有漂移
    原因:静息数据段选择不当,或者漂移是非线性的。
    解决:使用多项式拟合或高通滤波来去除低频漂移。

嗯,预处理这块就聊到这儿。记住一句话:预处理做得好,后续分析事半功倍。我见过太多人急着做肌肉协同提取,结果因为预处理没做好,得出来的协同模式全是噪声。别急,先把信号洗干净。

核心要点回顾:
  • 滤波:高通20Hz + 低通450Hz + 带阻50Hz
  • 去噪:阈值法(3-5倍标准差)或小波去噪
  • 基线校正:减去静息段均值
  • 预处理是迭代过程,需要反复调整参数

专注资料整理