3. 信号预处理:滤波(带通、陷波)、去基线漂移、全波整流与低通滤波(线性包络)
各位同行,咱们直接进入正题。信号预处理这事儿,说白了就是给原始EMG信号“洗个澡”,把脏东西去掉,留下干净有用的部分。我做了这么多年肌电分析,可以负责任地告诉你:预处理做不好,后面提取出来的肌肉协同,十有八九是错的。
嗯,咱们一步步来。先看一张图,把整个流程串起来。
3.1 带通滤波:把信号“框”在有效频段内
原始EMG信号里什么乱七八糟的频率都有。低频有运动伪迹,高频有环境噪声。我个人的习惯是,先用一个带通滤波器,把信号框在20-450 Hz这个范围里。
为什么会选这个范围?因为表面EMG信号的有效能量,基本都集中在这个频段。低于20 Hz的,大多是电极移动或者线缆晃动造成的伪迹。高于450 Hz的,基本就是白噪声了,没什么用。
核心要点:
- 下限频率:10-30 Hz(常用20 Hz)。太低会保留运动伪迹,太高会丢失低频肌电成分。
- 上限频率:400-500 Hz(常用450 Hz)。太高会引入噪声,太低会丢失高频肌电成分。
- 滤波器阶数:4阶或8阶Butterworth滤波器,零相位延迟(使用filtfilt函数)。
我在项目中遇到过一件事:有个学生用20-200 Hz的带通,结果提取出来的肌肉协同数量总是偏少。后来发现,他把高频肌电成分给滤掉了,导致信号能量不足。你想想看,这就像你只听了低音部分就去判断一首歌的旋律,能准吗?
3.2 陷波滤波:干掉工频干扰
工频干扰,说白了就是50 Hz(国内)或60 Hz(国外)的电源噪声。这个噪声特别讨厌,因为它正好落在EMG信号的有效频段内。
我建议的做法是:先用带通滤波,再做陷波。顺序不能乱。为什么?因为带通滤波已经去掉了一部分低频和高频噪声,陷波只需要针对那个特定的频率点,这样对信号的损伤最小。
我的小技巧:
如果工频干扰不严重,可以不做陷波。因为陷波器会稍微扭曲信号相位。我一般先看一眼频谱,如果50 Hz的峰值明显高于周围频率,才做陷波。否则,带通滤波就够了。
3.3 去基线漂移:让信号“站直”
基线漂移,就是信号整体在缓慢地上下移动。这通常是因为电极与皮肤之间的阻抗变化,或者受试者出汗导致的。
去基线漂移的方法很简单:用一个高通滤波器,截止频率设在0.5-1 Hz。这样,低于这个频率的缓慢变化就被滤掉了,信号就能“站直”了。
注意:
千万不要用均值去基线!因为EMG信号本身就不是零均值的。用均值去基线,会把肌肉收缩时的直流分量也去掉,导致信号失真。
嗯,这里要注意:去基线漂移和带通滤波的下限频率是两回事。带通滤波的下限是20 Hz,去基线漂移的下限是0.5 Hz。它们处理的是不同频段的噪声,不能互相替代。
3.4 全波整流:把负值“翻正”
全波整流,就是把所有负值变成正值。说白了就是取绝对值。
为什么要做这一步?因为EMG信号是双极性的,有正有负。但肌肉的激活程度,只跟信号的幅值有关,跟正负无关。整流之后,信号就变成了单极性,方便后续处理。
我曾经见过有人跳过整流,直接做低通滤波。结果提取出来的包络,正半周和负半周互相抵消,信号变得很小。这就像你把两个方向相反的力加在一起,结果合力为零,完全失去了意义。
3.5 低通滤波(线性包络):提取肌肉激活的“轮廓”
最后一步,也是最重要的一步:低通滤波。这一步的目的是提取信号的包络,也就是肌肉激活的“轮廓”。
低通滤波的截止频率,通常设在2-10 Hz。我个人的经验是:
- 2-4 Hz:用于慢速、持续性的肌肉收缩(如姿势维持)。
- 5-7 Hz:用于中等速度的运动(如步行、跑步)。
- 8-10 Hz:用于快速、爆发性的运动(如跳跃、投掷)。
你想想看,如果截止频率设得太低(比如1 Hz),包络会变得过于平滑,丢失细节。如果设得太高(比如15 Hz),包络会保留太多毛刺,看起来像没滤波一样。
线性包络 vs. RMS包络:
线性包络(整流+低通)和RMS包络(滑动窗口计算均方根)是两种常用的方法。我个人更推荐线性包络,因为它没有窗口效应,时间分辨率更高。RMS包络的窗口长度会影响结果,需要额外调参。
3.6 代码示例:完整的预处理流程
下面是我常用的预处理代码,用Python写的。你可以直接拿去用。
import numpy as np
from scipy import signal
def preprocess_emg(emg_raw, fs, bandpass=[20, 450], notch=50, hp_cutoff=0.5, lp_cutoff=6):
"""
EMG信号预处理
参数:
emg_raw: 原始信号 (n_samples, n_channels)
fs: 采样率 (Hz)
bandpass: 带通滤波范围 [low, high]
notch: 陷波频率 (Hz)
hp_cutoff: 高通截止频率 (去基线漂移)
lp_cutoff: 低通截止频率 (线性包络)
返回:
emg_envelope: 预处理后的包络信号
"""
# 1. 带通滤波
b, a = signal.butter(4, [bandpass[0]/(fs/2), bandpass[1]/(fs/2)], btype='band')
emg_filtered = signal.filtfilt(b, a, emg_raw, axis=0)
# 2. 陷波滤波
b_notch, a_notch = signal.iirnotch(notch, 30, fs)
emg_filtered = signal.filtfilt(b_notch, a_notch, emg_filtered, axis=0)
# 3. 去基线漂移 (高通滤波)
b_hp, a_hp = signal.butter(4, hp_cutoff/(fs/2), btype='high')
emg_filtered = signal.filtfilt(b_hp, a_hp, emg_filtered, axis=0)
# 4. 全波整流
emg_rectified = np.abs(emg_filtered)
# 5. 低通滤波 (线性包络)
b_lp, a_lp = signal.butter(4, lp_cutoff/(fs/2), btype='low')
emg_envelope = signal.filtfilt(b_lp, a_lp, emg_rectified, axis=0)
return emg_envelope
避坑指南:
我曾经犯过一个错误:先做整流,再做带通滤波。结果整流产生的直流分量被带通滤掉了,信号变得很奇怪。正确的顺序是:带通 → 陷波 → 去基线 → 整流 → 低通。这个顺序不能乱。
3.7 预处理效果的验证
预处理做得好不好,不能光看代码。我建议你每次做完预处理,都做两件事:
- 看时域波形:预处理后的包络应该平滑、无毛刺、无基线漂移。肌肉收缩时包络上升,放松时包络下降。
- 看频谱:预处理后的信号,能量应该集中在0-10 Hz(包络)和20-450 Hz(原始EMG)。50 Hz处不应该有尖峰。
如果发现包络有异常波动,或者频谱里有不该有的频率成分,那就说明预处理参数需要调整。别偷懒,这一步做扎实了,后面的肌肉协同提取才能靠谱。
好了,信号预处理这部分就讲到这里。记住一句话:预处理的质量,决定了肌肉协同分析的上限。你花再多时间在这上面,都是值得的。