MATLAB肌电信号处理全流程:预处理、特征提取与分类实战
发布时间:2026/9/17 3:39:43 作者:尧图编辑部 阅读量:1,286

简介面向生物医学工程与信号处理学习者这份基于MATLAB的肌电信号处理资源完整覆盖了从原始数据读取、预处理、特征分析到图形可视化的主要步骤可帮助初学者理清肌电信号分析的整体流程。资源共204个文件压缩包仅2.8MB以170个MATLAB脚本为主体同时配有9个FIG交互界面、5个MAT数据文件和8个XLS表格数据另有TEX与TXT说明文档便于对照算法和整理结果。从内容预览可见资源提供主界面、处理界面、时频页面等多个图形窗口可直观演示短时傅里叶变换、功率谱计算等关键环节帮助读者理解算法原理与参数影响从而掌握完整的肌电信号分析流程。已有1823人学习下载适合希望快速上手肌电信号MATLAB实现并基于现成界面与脚本进行二次开发的研究人员脚本结构清晰便于修改与扩展。1. 肌电信号处理从哪一步开始先看清原始信号里有什么表面肌电sEMG信号幅度在几十微伏到几毫伏之间电极贴上去的瞬间工频干扰、运动伪迹和基线漂移就一起混了进来。直接在 MATLAB 里拿原始波形做特征提取噪声会把指标带偏后面接什么分类器都救不回来。肌电信号处理的步骤按工程习惯排是预处理、活动段检测、特征提取、模式识别验证四段MATLAB 处理肌电信号最合适的地方在于每个中间量都能画出来核对参数设错了能立刻定位到是哪一段出了问题。这里按真实处理顺序把每步的代码、参数依据和常见坑位写清楚适合正在做手势识别、假肢控制、疲劳评估或康复分析的工程师和研究生直接对着调。下面从预处理链路的参数设计开始。2. 肌电信号处理第一步预处理链路在 MATLAB 里的参数设计2.1 预处理顺序先带通滤波还是先陷波表面肌电的有效能量集中在 20–500 Hz。20 Hz 以下主要是电极移动造成的基线漂移和运动伪迹500 Hz 以上主要是设备噪声和辐射干扰这两部分都要先压掉所以第一条滤波器是带通。工频干扰单独处理中国大陆电网是 50 Hz欧美是 60 Hz陷波器就是专门对准这个频率的窄带滤波器。顺序上我一般先带通再陷波。原因有两个带通之后信号带宽缩到 500 Hz 以内陷波器边缘对带外的影响范围变小振铃效应更可控另外肌电幅值本来就小先窄带处理能让后面的陷波器数值稳定性更好。反过来先陷波再做带通滤波器级联响应会在陷波点附近产生更复杂的相位畸变不配合 filtfilt 使用的话很容易在 50 Hz 周围看到多余的震荡。2.2 用 designfilt 搭一条可复现的滤波链路旧版本 MATLAB 习惯用 fdatool 设计滤波器再导出系数现在直接给 designfilt 传参数即可代码即文档后续换采样率或换频带只改一行fs 2000; % 采样率2000 Hz覆盖 sEMG 频带上限 emg load(emg_raw.mat).emg; % 原始信号列向量 % 带通 FIR20–500 Hz100 阶 bp designfilt(bandpassfir, ... FilterOrder, 100, ... CutoffFrequency1, 20, ... CutoffFrequency2, 500, ... SampleRate, fs); emg_bp filtfilt(bp, emg); % 50 Hz 工频陷波陷波带宽 49–51 Hz notch designfilt(bandstopiir, ... FilterOrder, 4, ... HalfPowerFrequency1, 49, ... HalfPowerFrequency2, 51, ... SampleRate, fs); emg_clean filtfilt(notch, emg_bp);FilterOrder 取 100 的原因fs 在 2000 Hz 时100 阶 FIR 的过渡带宽度大约在 40–60 Hz刚好能把 20 Hz 以下的漂移和 500 Hz 以上的高频压下去又不至于把 20 Hz 附近的有效成分削太狠。阶数再往上走过渡带更窄但计算量在涨离线处理无所谓实时系统里要权衡。陷波用 IIR 是因为指标窄、阶数低4 阶 bandstopiir 的过渡带足够陡配合 filtfilt 做零相位处理。MATLAB 里序列长度大于 3 倍滤波器阶数时就能安全使用 filtfilt实际数据通常都满足。filter 和 filtfilt 的差别在相位filter 引入非线性相位延迟做完滤波后信号整体滞后且不同频率滞后量不一样这对特征提取里的过零率、波长这类对波形形状敏感的指标影响很大。所以预处理统一用 filtfilt代价是实时性差一些在线场景可以分段处理或者在滤波后做延迟补偿。2.3 基线校正与线性包络两个最容易看漏的细节带通滤波后残留的直流偏置和缓慢漂移用静息段均值来校正。做法是取用力前 0.5–2 秒静息信号的均值从整段里减掉这样零线才能对齐到收缩强度为零的位置。活动段检测一般不看原始波形而看线性包络包络是整流后低通平滑得到的能清晰显示肌肉激活和静息的边界。整流用 abs平滑用 5 Hz 低通 FIR。这个截止频率对应收缩包络的变化速度人手动作的包络频率一般不超过 3–5 Hz取 5 Hz 既能保留起止沿又不会引入高频毛刺% 基线校正以用力前 1 秒静息均值作为零线 rest 1:fs; % 假设前 1 秒是静息段 baseline mean(emg_clean(rest)); emg_cor emg_clean - baseline; % 线性包络全波整流 5 Hz 低通平滑 envelope abs(emg_cor); lp designfilt(lowpassfir, ... FilterOrder, 64, ... CutoffFrequency, 5, ... SampleRate, fs); envelope filtfilt(lp, envelope);这段代码里最容易错的两处一是 rest 段的选取如果静息段混进了轻微动作均值被抬高后面的活动段阈值也跟着偏大二是整流前的基线没校正整流会把负向直流成分翻到正方向包络整体抬高看起来像一直在激活。做完预处理建议先画图确认把原始信号、带通后信号和包络叠在一起看静息段包络应该是一条接近 0 的平线。预处理参数汇总参数推荐值说明采样率1000–2000 Hz低于 1000 Hz 时高频成分会混叠带通范围20–500 Hz覆盖 sEMG 主要能量带陷波中心50 Hz 或 60 Hz按当地电网频率选择包络平滑截止5 Hz对应肌肉收缩包络变化速度3. 肌电信号特征提取时域特征的计算公式与滑窗参数怎么定3.1 时域特征先上场实时系统没那么多算力分给 FFT特征提取阶段是把一段肌电信号压缩成少量数值描述。工程上最常用的是时域特征因为它们计算简单、几乎无参数依赖在单片机这类嵌入式平台上可以直接跑。频域特征每帧都要做 FFT功耗和延迟都高实时手势识别产品里通常是时域特征顶在前面。常用时域特征按实用排序RMS均方根、MAV平均绝对值、过零率 ZC、斜率符号变化次数 SSC、波长 WL。RMS 和 MAV 都反映收缩强度但 RMS 对大的瞬态幅值更敏感适合区分不同力度等级ZC 和 SSC 反映信号形态变化快慢对疲劳状态下肌电频谱向低频迁移比较敏感WL 综合了幅值和频率的信息是不少分类器的首选单特征。3.2 一个函数算完时域加频域参数与计算口径下面这个函数把一段信号变成 7 维特征向量时域 5 个加频域 2 个各特征口径先列在表里特征计算口径对什么敏感RMS平方均值开根号收缩力度、瞬态峰值MAV绝对值平均收缩力度比 RMS 平滑ZC带阈值过零次数信号频率变化SSC带阈值斜率符号变化次数信号复杂度WL相邻样本差绝对值累加幅值与频率综合MNF功率谱一阶矩频谱重心MDF累积功率 50% 对应频率疲劳时向低频偏移function f emgFeatures(x, fs, th) % x : 单段肌电信号列向量 % fs : 采样率 % th : 过零阈值单位与信号相同常用 10–20 uV N length(x); rms_val sqrt(mean(x.^2)); % 均方根 mav_val mean(abs(x)); % 平均绝对值 wl_val sum(abs(diff(x))); % 波长 s1 x(1:N-1); s2 x(2:N); % 过零率相邻两样本符号不同且差值超过阈值 zc_val sum(((s1 0 s2 0) | (s1 0 s2 0)) ... abs(s1 - s2) th); d diff(x); d1 d(1:N-2); d2 d(2:N-1); % 斜率符号变化次数避免微小噪声触发 ssc_val sum((d1 .* d2) 0 abs(d1 - d2) th); freq linspace(0, fs/2, N/21); seg x .* hann(N); % 加汉宁窗抑制频谱泄漏 P abs(fft(seg, 2^nextpow2(N))).^2; P P(1:N/21); mnf sum(freq .* P) / sum(P); % 平均频率 mdf freq(find(cumsum(P) sum(P)/2, 1)); % 中值频率 f [rms_val, mav_val, zc_val, ssc_val, wl_val, mnf, mdf]; end过零阈值的引入是为了防止基线附近的小幅噪声产生大量假过零。阈值取多少取决于信号单位如果数据经过放大并转成电压阈值取 10–20 μV 合适如果用 ADC 原始计数就要按 ADC 增益换算。SSC 里 d1 .* d2 0 的含义是相邻两个斜率符号相反说明波形在这一点发生了方向转折加上幅值差条件后只有转折幅度足够大才算一次有效变化。频域部分有个细节直接对原始信号做 FFT矩形窗泄漏会把能量涂抹到旁瓣导致平均频率偏高。加汉宁窗后主瓣和旁瓣比改善MNF、MDF 的稳定性更好。P 取单边功率谱freq 从 0 到 fs/2长度必须和 P 一致这里最容易写错长度不匹配时 MATLAB 会直接报维度错误。3.3 滑窗分段窗口 256、步进 64 是怎么算出来的特征提取之前先把连续信号切成一帧一帧的窗口。窗口太短小于 50 ms帧内肌电的随机波动没被平均掉RMS 方差很大窗口太长大于 300 ms动作切换的瞬态被抹平实时系统延迟也超标。128 ms 是常见起点2000 Hz 采样率下对应 256 个点。步进长度决定特征更新频率实时控制场景延迟预算通常 50–100 ms所以步进取 32–64 点保证每 16–32 ms 出一个新特征win 256; % 窗口长度128 ms 2000 Hz hop 64; % 步进32 ms实时控制不会太卡顿 featAll []; % 每行对应一个窗口的特征向量 idx 1; while idx win - 1 length(emg_clean) seg emg_clean(idx : idx win - 1); featAll(end1, :) emgFeatures(seg, fs, 10); %#okSAGROW idx idx hop; end窗口和步进的比例建议在 3:1 到 8:1 之间。重叠太多特征之间相关性太高对某些分类器的正则化是负面影响重叠太少包络的时序信息丢失。上面用 while 是为了让窗口边界清楚数据量大时提前用 zeros 预分配 featAll 再填充避免 end1 动态增长拖慢速度。各特征量纲不同RMS 和 WL 是微伏级ZC 和 SSC 是计数值堆成矩阵后要先做 zscore 标准化再送分类器否则距离类算法会被幅值量纲主导featAll zscore(featAll);MNF、MDF 的单位是 Hz量纲与幅值类特征天然不同zscore 对各列独立计算不会破坏频域特征原有的单位关系。4. 肌电信号处理完整流水线活动段检测、样本标注与 LDA 快速分类4.1 用线性包络做活动段检测自适应阈值加迟滞特征矩阵里不是所有窗口都有分析价值静息段的窗口只会给分类器添乱。所以先把肌肉激活的活动段切出来。常见做法是用包络幅度做自适应阈值静息段均值加 5 倍标准差作为开启阈值再乘 0.5 作为关闭阈值。开启阈值要求高保证不是噪声触发的关闭阈值低保证动作末段的弱收缩也被完整保留这就是迟滞机制避免包络在阈值附近抖动时频繁开关% envelope 来自第 2 节的线性包络rest 是静息段索引 th_on mean(envelope(rest)) 5 * std(envelope(rest)); th_off th_on * 0.5; on envelope th_on; on bwareaopen(on, round(0.2 * fs)); % 去掉短于 200 ms 的毛刺 d diff([0; on; 0]); starts find(d 1); % 每个活动段的起点 ends find(d -1) - 1; % 终点 % 用关断阈值做迟滞逐个活动段检查包络并截断 for k 1:numel(starts) seg_env envelope(starts(k):ends(k)); off_pos find(seg_env th_off, 1); if ~isempty(off_pos) ends(k) starts(k) off_pos - 1; end endbwareaopen 是图像处理工具箱里的连通域滤波作用在逻辑序列上会保留长度不小于给定值的连续 1 段正好用来清掉猝发噪声造成的短促激活。200 ms 是最短活动段的先验值来自正常人一次快速抓握动作也不短于 200 ms 的经验。迟滞循环处理的是包络先跌破 th_off 再反弹的场景on 序列会把中间静息部分合并成一段直接用 ends 会包含静息窗口所以要逐个活动段截断。4.2 组织训练样本段级特征和窗口级特征别混用活动段切出来后样本的构造有两种粒度。窗口级样本把每个窗口的特征作为一行适合实时分类分类器每个步进周期输出一次段级样本把整个活动段内所有窗口的特征平均成一行适合离线动作识别样本数少但类别代表性强。这里最常翻车的做法是两种粒度混用训练时用段级平均特征验证时用窗口级特征两者分布不同测试准确率会骗人。我一般这样做段级样本送离线分类器看特征可分性窗口级样本送实时分类器做在线准确率评估两套指标分开报告。% 假设 featAll 是窗口级特征矩阵starts/ends 来自 4.1 segFeat []; segLabel []; for k 1:numel(starts) idx starts(k):ends(k); segFeat(end1, :) mean(featAll(idx, :), 1); %#okSAGROW segLabel(end1, 1) actionType(k); % 该段的真实动作类别 end窗口级和段级各自的适用场景粒度样本数适用场景延迟窗口级多实时控制、连续解码一个步进周期段级少离线动作识别、疲劳评估一个完整动作标注这一步还要注意时间对齐动作开始前和结束后各 100 ms 左右的窗口处于过渡态特征既不完全是静息也不完全是收缩训练时最好去掉否则分类边界的噪音会偏大。4.3 基线分类器先用 LDA一张混淆矩阵看到全貌分类器不需要一上来就上深度学习。sEMG 特征在低维空间里通常近似线性可分Fisher 线性判别LDA在样本量只有几百的场景下比 SVM 更稳也更不容易过拟合。MATLAB 里直接 fitcdiscrrng(0); cvp cvpartition(segLabel, HoldOut, 0.3); % 30% 做测试 tr cvp.training; te cvp.test; mdl fitcdiscr(segFeat(tr,:), segLabel(tr), ... DiscrimType, pseudoLinear); % 类别数少时数值更稳 pred predict(mdl, segFeat(te,:)); acc mean(pred segLabel(te)); fprintf(LDA 测试集准确率: %.1f%%\n, acc * 100); figure; confusionchart(segLabel(te), pred); % 混淆矩阵看类别混淆DiscrimType 里 pseudoLinear 是在协方差矩阵奇异时使用的伪逆解特征维度接近样本数时不会报错。真实项目里第一次跑 LDA 准确率低于 85%先别怀疑分类器回头查预处理和特征活动段是不是切多了、两个动作的发力模式本身是否接近、zscore 是不是在全量数据上计算的导致数据泄漏。fitcdiscr 内部假设各类别协方差矩阵相同动作之间的收缩强度差异过大时这个假设会被破坏可以换 quadratic 类型试一下但样本量必须够大二次判别对协方差估计的要求高得多。第一版验证跑通之后再用 fitcecoc 套多分类 SVM 或者上窄网络。基线模型保底后面每次改动预处理或特征都拿 LDA 对比一次能明确知道性能提升来自哪里。5. 肌电信号处理结果别急着信伪迹剔除与交叉验证两个必做动作5.1 窗口级 RMS 的 MAD 阈值剔除伪迹预处理能压掉工频和漂移但电极松动造成的瞬时尖峰、线缆被拉动的冲击伪迹频带往往和有效信号重叠滤波摘不干净。这类伪迹的典型特征是窗口 RMS 突然跳到相邻窗口的数倍以上。用中位数加绝对中位差MAD做阈值判定比均值加标准差更抗离群值污染因为均值本身会被尖峰带跑% 每帧 RMS 序列buffer 第二参数是窗长第三参数是重叠 winRMS sqrt(mean(buffer(emg_clean, win, win-hop).^2, 1)); medv median(winRMS); madv mad(winRMS, 1); % 绝对中位差 bad winRMS medv 6 * madv; % 超过 6 倍 MAD 判为伪迹 fprintf(伪迹窗口占比: %.1f%%\n, mean(bad) * 100);buffer 第三个参数 win-hop 是窗口重叠量对应第 3 节滑窗的步进。判定为伪迹的窗口要直接从特征矩阵删除而不是置零置零等于给分类器塞入零向量某些分类器会因此输出错误的类别分布。伪迹窗口占比超过 5% 时优先检查电极贴附而不是继续调阈值采集阶段的问题用算法救不彻底。5.2 十折交叉验证看泛化混淆矩阵看类别单次 holdout 划分的结果受随机性影响很大样本少时一次测试集上的波动能到 5 个百分点。十折交叉验证能给出平均水平和方差比单次准确率可信得多cvp cvpartition(segLabel, KFold, 10); accK zeros(cvp.NumTestSets, 1); for k 1:cvp.NumTestSets tr cvp.training(k); te cvp.test(k); mk fitcdiscr(segFeat(tr,:), segLabel(tr), DiscrimType, pseudoLinear); accK(k) mean(predict(mk, segFeat(te,:)) segLabel(te)); end fprintf(10折平均 %.1f%% ± %.1f%%\n, mean(accK)*100, std(accK)*100);交叉验证里最容易出漏洞的地方是预处理参数和 zscore 的均值方差在全量数据上计算后才划分折这会造成信息泄漏。正确做法是每折只在训练集上算 zscore 参数再映射到测试集否则测试集被训练集信息污染交叉验证结果虚高。5.3 陷波参数先用频域图定别照抄参数表最后一个实用技巧陷波宽度不要直接抄 49–51 Hz。先用 pspectrum(emg_clean, fs, spectrogram) 看工频干扰的实际带宽有的采集设备 50 Hz 谱峰很细陷波带宽 1 Hz 就够有的开关电源干扰展宽到 2–3 Hz陷波太窄压不干净频谱上留一个残留尖峰这个尖峰经过整流后还可能在包络上产生周期性波动。先画图确认再定陷波参数比套用默认值更靠谱。带通上下限同样对着频谱图确认如果采集设备在 450 Hz 以上有谐振峰上限就收到 450而不是照抄 500 Hz 的模板。这类按数据定参数的步骤就是肌电信号处理里最值得花时间打磨的地方。本文还有配套的精品资源点击获取