第4章:肌电信号时域分析:整流、平滑(RMS计算)、积分肌电值(iEMG)、平均振幅(MAV)

好,咱们进入时域分析。这一块是肌电信号处理里最基础、也最常用的手段。说白了,就是把原始信号那些“毛刺”和“抖动”处理成有物理意义的数值。

我刚开始接触肌电时,看着屏幕上那条疯狂跳动的曲线,心里直犯嘀咕:这玩意儿能看出啥?后来才明白,时域分析就是给这条“疯狗”套上缰绳,让它乖乖听话。

4.1 为什么需要整流?

原始肌电信号是交流信号,有正有负。你想想看,如果直接求平均,正负一抵消,结果可能接近零——这显然不能反映肌肉的真实活动强度。

整流,就是把负半周的信号翻到正半周来。有两种方式:

  • 半波整流:只保留正半周,负半周直接置零。说实话,这方法用得少,因为会丢掉一半信息。
  • 全波整流:把负半周的绝对值取正。这是主流做法。

核心要点:全波整流后的信号,才能进行后续的平滑和积分计算。否则你算出来的平均值会严重偏低。

我在项目中遇到过一位新手,直接用原始信号算RMS,结果数值忽大忽小,完全没法用。我一看,整流这步跳过了。嗯,这是最常见的坑之一。

4.2 平滑与RMS计算

整流后的信号虽然都是正的,但依然很“扎手”。这时候就需要平滑处理。最经典的方法就是均方根(RMS)计算。

RMS的公式很简单:

RMS = sqrt( (1/N) * Σ(x_i²) )

其中 x_i 是窗口内的采样点,N 是窗口长度。

为什么用RMS而不是直接平均?因为RMS对信号中的“爆发”更敏感。肌肉收缩时,信号幅度会突然增大,RMS能更好地捕捉这种变化。

窗口长度怎么选? 我个人习惯用50-100毫秒的滑动窗口。太短了平滑效果差,太长了又会丢失细节。举个例子,采样率1000Hz时,窗口长度取50-100个点比较合适。

小技巧:如果你做实时分析,可以用指数移动平均代替滑动窗口,计算量更小,响应也更快。

4.3 积分肌电值(iEMG)

iEMG,全称是integrated EMG。它计算的是一段时间内整流后信号的面积。说白了,就是曲线下的总面积。

公式:

iEMG = Σ |x_i| * Δt

其中 Δt 是采样间隔。

iEMG反映的是肌肉在某个时间段内的总活动量。比如你让受试者做10秒的等长收缩,iEMG就能告诉你这10秒里肌肉总共“出了多少力”。

应用场景:

  • 疲劳分析:随着肌肉疲劳,iEMG会逐渐增大(因为需要更多运动单位参与)
  • 康复评估:对比患侧和健侧的iEMG,判断肌肉激活程度

注意:iEMG对信号长度敏感。比较不同时间段的iEMG时,一定要保证时间窗口一致。我曾经吃过这个亏,两个实验的时间窗口差了0.5秒,结果对比完全失真。

4.4 平均振幅(MAV)

MAV,Mean Absolute Value,就是整流后信号的平均值。公式:

MAV = (1/N) * Σ |x_i|

MAV和RMS很像,但计算更简单。它反映的是信号的平均强度。在工程实践中,MAV常用于:

  • 手势识别:不同手势的MAV特征差异明显
  • 肌肉激活阈值检测:当MAV超过某个阈值时,认为肌肉开始收缩

我个人觉得,MAV比RMS更直观。RMS因为平方再开方,数值会被“放大”,而MAV就是实实在在的平均幅度。

4.5 知识体系总览

下面这张图,把本章的核心逻辑串起来了:

肌电信号时域分析流程 原始EMG信号 全波整流 RMS计算(平滑) iEMG(积分) MAV(平均振幅) 肌肉活动强度 疲劳分析 / 总活动量 手势识别 / 阈值检测

4.6 实战代码示例

下面是一段Python代码,演示了如何计算RMS、iEMG和MAV。我习惯用numpy,效率高,代码也简洁。

import numpy as np

def compute_emg_features(signal, fs):
    """
    计算肌电信号的时域特征
    signal: 原始EMG信号(一维数组)
    fs: 采样率(Hz)
    """
    # 1. 全波整流
    rectified = np.abs(signal)
    
    # 2. RMS计算(滑动窗口,窗口长度100ms)
    window_len = int(0.1 * fs)
    rms = np.array([
        np.sqrt(np.mean(rectified[i:i+window_len]**2))
        for i in range(0, len(rectified) - window_len, window_len//2)
    ])
    
    # 3. iEMG(每1秒积分一次)
    segment_len = fs  # 1秒
    iemg = np.array([
        np.sum(rectified[i:i+segment_len]) / fs
        for i in range(0, len(rectified) - segment_len, segment_len)
    ])
    
    # 4. MAV(每1秒平均一次)
    mav = np.array([
        np.mean(rectified[i:i+segment_len])
        for i in range(0, len(rectified) - segment_len, segment_len)
    ])
    
    return rms, iemg, mav

# 使用示例
fs = 1000  # 假设采样率1000Hz
signal = np.random.randn(10000)  # 模拟10秒信号
rms, iemg, mav = compute_emg_features(signal, fs)
print(f"RMS均值: {np.mean(rms):.3f}")
print(f"iEMG均值: {np.mean(iemg):.3f}")
print(f"MAV均值: {np.mean(mav):.3f}")

避坑指南:我曾经在计算iEMG时忘了除以采样率,结果数值大得离谱。记住,iEMG的单位是mV·s,一定要考虑时间因素。

4.7 三个指标怎么选?

很多新手会问:RMS、iEMG、MAV,到底用哪个?我的建议是:

  • 做实时控制(比如假肢):用RMS,因为它对信号变化响应快,平滑效果也好
  • 做疲劳分析:用iEMG,它能累积反映肌肉的总工作量
  • 做模式识别(手势分类):用MAV,计算简单,特征稳定

当然,实际项目中往往三个都算,然后看哪个特征区分度最好。嗯,数据不会骗人,多试试就知道了。


公众号:蓝海资料掘金营,微信deep3321