ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

用表面肌电信号控制智能小车:从信号采集到动作识别与运动控制

用表面肌电信号控制智能小车:从信号采集到动作识别与运动控制 简介一篇题为《基于手臂表面肌电信号的智能小车控制系统》的研究论文发表于《生物医学工程研究》2017年第4期。该文面向生物医学工程、人工智能及人机交互领域的开发者和研究者介绍了一种利用左臂表面肌电信号控制小车前进、左转、右转、停止的智能系统系统涵盖肌电采集、信号识别与指令编码、无线传输、小车控制四个模块实验验证了其高识别率与低延迟。压缩包内含1个PDF文件共9.26MB为论文完整版已有229人学习。读者可借此获取系统整体框架、信号处理与模式识别方法、nRF24L01无线通信设计细节及实测数据对开展智能小车、可穿戴设备、肌电假肢等方向的研究与课程设计具有直接参考价值。1. 手臂表面肌电信号如何变成一辆小车的方向盘表面肌电信号sEMG是从皮肤表面采集到的肌肉电活动幅度通常在几十微伏到几毫伏之间频率集中在 20500Hz。这类信号天然适合做“非键盘式”的人机交互入口——握拳、抬腕、屈肘这些动作对应着不同的肌肉激活模式识别出来就能映射成控制指令。把这套逻辑放在智能小车上就是用手臂动作替代遥控器的方向摇杆和油门。这套系统真正难的点不在于小车本身而在于把“肌肉用力程度”和“动作类型”从噪声里干净地拆出来。工频干扰、电极移位、出汗导致的阻抗变化都会让同一动作的特征发生漂移。做这个题目的人通常是嵌入式方向的学生或做康复机器人预研的工程师需要同时碰模拟前端、数字滤波、模式识别和电机控制。下面按我实际搭系统的顺序把采集、识别、控制到联调的关键节点都过一遍。2. 表面肌电信号的采集链路设计与参数设定2.1 传感器选型差分输入比高增益更重要表面肌电信号的第一道坎是共模干扰。人体相当于一个天线50Hz 工频会以共模形式耦合到皮肤表面幅度往往比肌电信号还大。所以前端放大器必须采用差分结构仪表放大器是常见选择例如 AD620、INA128或者是市面上集成了放大和滤波的肌电传感器模块如 OYMotion、Myoware 这类。选型时重点看三个参数共模抑制比CMRR要大于 90dB输入阻抗要高一般大于 10MΩ增益可调范围要能覆盖 1002000 倍。我一般会在仪表放大器之后加两级处理第一级高通滤波截止频率 1020Hz去掉运动伪迹和直流偏置第二级低通滤波截止频率 500Hz去掉高频噪声。做低通时要注意运放的压摆率别用带宽太小的通用运放否则 500Hz 附近的相位延迟会变大后续做实时特征提取时会感觉到明显的滞后。电极方面推荐 Ag/AgCl 湿电极粘贴前用酒精棉擦拭皮肤能有效降低接触阻抗。参数推荐值说明采样率1000Hz 以上Nyquist 定理要求至少 1000Hz建议 15002000Hz 留裕量高通截止频率1020Hz滤除运动伪迹过高会削掉信号能量低通截止频率500Hz肌电有效能量集中在 20500Hz陷波频率50Hz数字域做模拟陷波容易把信号相位搞乱ADC 位数12 位以上配合放大倍数确保分辨率不低于 1μV2.2 用单片机读取肌电数据的采集程序框架采集端我习惯用 STM32F103 的 ADC 连续采样模式搭配 DMA 传输避免 CPU 被中断频繁打断。下面给出一段核心初始化代码// 初始化ADC1通道5PA5采样时间拉长以保证输入阻抗匹配 void ADC_Init(void) { GPIO_InitTypeDef gpio; ADC_InitTypeDef adc; DMA_InitTypeDef dma; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); gpio.GPIO_Pin GPIO_Pin_5; gpio.GPIO_Mode GPIO_Mode_AIN; GPIO_Init(GPIOA, gpio); DMA_DeInit(DMA1_Channel1); dma.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR; dma.DMA_MemoryBaseAddr (uint32_t)adc_buffer; dma.DMA_DIR DMA_DIR_PeripheralSRC; dma.DMA_BufferSize 1024; dma.DMA_PeripheralInc DMA_PeripheralInc_Disable; dma.DMA_MemoryInc DMA_MemoryInc_Enable; dma.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; dma.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; dma.DMA_Mode DMA_Mode_Circular; DMA_Init(DMA1_Channel1, dma); adc.ADC_Mode ADC_Mode_Independent; adc.ADC_ScanConvMode DISABLE; adc.ADC_ContinuousConvMode ENABLE; adc.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; adc.ADC_DataAlign ADC_DataAlign_Right; adc.ADC_NbrOfChannel 1; ADC_Init(ADC1, adc); ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 1, ADC_SampleTime_239Cycles5); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while (ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1)); ADC_SoftwareStartConvCmd(ADC1, ENABLE); DMA_Cmd(DMA1_Channel1, ENABLE); }这段代码的逻辑是ADC 连续转换转换结果通过 DMA 自动搬运到adc_buffer数组环形缓冲区写满后从头开始覆盖。这样 CPU 只需在需要时读取缓冲区即可适合做实时控制。采样时间选择了ADC_SampleTime_239Cycles5是 STM32F103 最长档位为了降低信号源阻抗对采样电容充电不足的影响——肌电传感器的输出阻抗往往比普通传感器大采样时间不够会造成幅度压缩。采集端还有一点容易被忽略传感器模块的供电和单片机的参考电压必须共地而且电源纹波要控制在 50mV 以内。用 USB 供电时电脑开关电源的纹波会直接耦合进模拟前端我就遇到过“握拳时信号正常一插 USB 满屏 100Hz 纹波”的情况最后换成锂电池供电才解决。3. 动作特征的提取与识别模型从抓取到分类3.1 滑动窗口和时间域特征的计算方法肌电信号是非平稳信号不能拿整段数据离线分析后再控制小车必须用滑动窗口实时计算特征。窗口长度是第一个要调的参数窗口太短特征值抖动大误判率上升窗口太长控制延迟大小车反应迟钝。实际调试时我从 200ms 试到 500ms最终固定在 250ms、滑动步长 50ms这样特征更新频率 20Hz既跟得上手的动作节奏又不会让特征曲线毛刺太多。特征选择上时间域特征计算量小、适合单片机实时运行。最常用的是平均绝对值MAV和均方根值RMS。MAV 对信号幅值变化敏感适合检测“有没有用力”RMS 与肌肉收缩力呈近似线性关系适合做力度分级。过零率ZC对频率变化敏感握拳时由于运动单元的募集密度增加过零率会比放松状态显著上升因此也常作为区分动作类型的辅助特征。下面给出 Python 端的计算逻辑用于离线分析标定阈值import numpy as np def compute_features(window, fs1000): # 去除直流分量避免静息态漂移影响特征值 window window - np.mean(window) mav np.mean(np.abs(window)) rms np.sqrt(np.mean(window ** 2)) # 过零率需要设置一个滞回区间避免噪声导致的额外穿越 threshold 0.01 * np.max(np.abs(window)) zc 0 for i in range(1, len(window)): if (window[i-1] -threshold and window[i] threshold) or \ (window[i-1] threshold and window[i] -threshold): zc 1 zc zc / (len(window) - 1) return mav, rms, zc这段代码里的过零率加了滞回区间threshold这是容易被忽略的细节。原始信号即使静息状态也会因噪声来回穿越零点如果不设置阈值放松和握拳的过零率差异会被噪声淹没。滞回区间的值不是固定的我会先采集一段静息数据取幅值的 1%2% 作为参考。3.2 阈值分类与多通道融合的取舍分类模型的选择取决于通道数和动作集。如果只做一个通道放在前臂伸肌群上能稳定区分的动作也就 23 个放松、握拳、强力握拳。这时没有必要上神经网络设两个阈值——RMS 低于 0.05V 为放松0.050.15V 为握拳高于 0.15V 为强力握拳可靠性和实时性都最好。如果要区分握拳、张手、腕屈、腕伸这四类动作单通道信息不够需要用两个通道分别贴在指浅屈肌和指伸肌上。此时“握拳”表现为通道1显著激活、通道2相对静默而“张手”正好相反。特征是 2 维的 (RMS1, RMS2)我一般用简单的决策树即可不需要 SVM 那种稍重的模型。只有当动作数超过 6 类或者同一动作的不同力度等级也要区分时才值得考虑 LDA 或带 12 层隐藏层的小型全连接网络。一个我在实际项目中踩过的坑分类器的阈值必须针对当天佩戴位置重新标定。电极位置的微小偏移会导致 RMS 绝对值变化 20%30%昨天好的阈值今天会大面积误判。所以在系统启动流程里固定一段 5 秒的标定过程先放松 3 秒记录底噪再最大力握拳 2 秒记录上限实际阈值按线性插值取得。4. 智能小车运动控制动作映射与电机调速策略4.1 控制指令映射表的设计思路肌电动作识别出来的结果只有几类离散状态要变成小车的平滑运动必须设计好动作——指令的映射关系。我最开始做的是直接映射握拳前进、张手后退、腕屈左转、腕伸右转。实际测试发现一个问题握拳和张手的识别准确率高但腕屈和腕伸这两个动作在单通道下误判率明显上升经常出现小车倒车时突然拐弯。后面改成“主从式”映射把所有动作分成两个维度——力度等级和动作类型。力度等级由 RMS 幅度连续调节对应小车的 PWM 占空比动作类型只决定方向不决定速度。这样即使方向误判速度也受控车身姿态不会大幅突变。识别动作生效指令舵机/电机动作优先级放松RMS 阈值1STOP左右电机停转最高握拳中等力度FORWARD双电机正转PWM 按力度映射正常强力握拳RMS 阈值2TURBOPWM 强制置 100%正常腕屈TURN_LEFT左轮减速、右轮全速正常腕伸TURN_RIGHT右轮减速、左轮全速正常这个映射表的另一个好处是安全性放松状态设计为最高优先级停止指令。实际操作中人的手臂总会不自觉的微微用力导致误触发前进而“放松即停车”能够在小车要撞上障碍物时靠肌肉卸力快速刹停比起按键急停更符合直觉。4.2 基于 PID 的差速控制与 PWM 输出实现小车运动控制的核心是差速。转向时不采用舵机而是左右两轮独立调速利用速度差实现转向半径控制。速度环我用增量式 PID因为肌电信号本身就有波动直接让 PWM 跟随 RMS 会导致小车一顿一顿的加 PID 后速度过渡更平滑。// 增量式PID输出为PWM占空比补偿值 float pid_update(float target_speed, float current_speed, float *err_last, float *err_sum) { float kp 12.0f, ki 0.5f, kd 0.2f; float err target_speed - current_speed; float delta_pwm kp * (err - *err_last) ki * err kd * (err - 2.0f * (*err_last) *err_prev); *err_prev *err_last; *err_last err; return delta_pwm; } // 按键动作映射到左右轮目标速度 void motion_ctrl(uint8_t action, float rms_value, float *left_pwm, float *right_pwm) { float base_speed 0; switch (action) { case ACTION_FORWARD: base_speed 200.0f (rms_value - 0.1f) * 300.0f; // RMS越大速度目标越高 break; case ACTION_TURN_LEFT: *left_pwm 150.0f; // 左轮减速 *right_pwm 250.0f; // 右轮保持高速 return; case ACTION_STOP: *left_pwm 0; *right_pwm 0; return; } *left_pwm base_speed; *right_pwm base_speed; }这段代码的关键在base_speed的计算方式它不是简单把 RMS 线性映射到 PWM而是优先保证当握拳力度变化时小车速度变化有“油门”的感觉。式中的常数值受限于电机额定转速不同电机需要重新标定。我建议先在空载条件下把 RMS 从阈值1 扫到阈值2记录对应的实际轮速画一条曲线再进行线性化标定否则会出现“轻握不动、重握猛冲”这类控制手感问题。5. 系统联调时最常遇到的 5 类问题与验证方法5.1 工频干扰让特征值普遍偏高怎么办现象是静息状态下 RMS 就已经达到握拳时的一半特征曲线完全分不开。先检查电极与皮肤的接触按压电极看波形幅值是否下降再确认传感器模块的参考电极与 STM32 的 GND 是否单点接地。若仍然存在优先在数字域加 50Hz 陷波器用二阶 IIR 就能见效注意品质因数 Q 值设置在 12 之间过高会把 4951Hz 附近的真实肌电能量也滤掉导致握拳特征的幅度方差变大。5.2 动一下手臂小车就乱动这通常是运动伪迹导致的。肌肉收缩前的肢体位移会带来低频基线漂移幅度比肌电大一个数量级。解决方法是把高通截止频率从 10Hz 提高到 20Hz代价是部分低频肌电能量丢失但换来的是基线稳定。如果 20Hz 还不行就要检查是否电极贴在了肌腱附近而不是肌腹中央——肌腱处的信号本身幅值就低抗伪迹能力差。5.3 握拳识别为张手的概率高单通道下这两个动作本来就不容易区分因为二者激活的肌群在手臂前侧位置太靠近。可在前端放大电路后端加一路 LED 指示当前阈值状态用于快速验证用力握拳时 LED 亮张手时 LED 灭。如果张手时 LED 也亮说明电极位置偏向了指总伸肌方向需要向指浅屈肌肌腹位置调整。另外确认动作执行方式——张手时手指要完全伸展开不要只张开一半。5.4 PID 调节时小车出现振荡速度闭环出现持续抖动常见原因是 PID 输出限幅缺失和当前速度反馈采样周期不匹配。电机增量式编码器读取周期建议固定为 10msPID 运算周期也保持 10ms不要一快一慢。遇到振荡时先把 I 项清零仅用 P 项调试当 P 增大到临界值后回退 40%再逐步加入 I 项这比直接调大调小三个参数要快得多。5.5 标定阈值的时间太长换人后重新标定麻烦可以做一个简单的自适应基线系统每 10 秒刷新一次静息态 MAV取最近 10 个窗口的均值作为浮动零位实际特征值先减掉浮动零位再参与阈值比较。这样两个不同的人切换使用时基础阈值不用改只需要保留下限——静息时的浮动零位。实测换人后 3 秒内即可稳定工作。联调完成之后做一个定量验证是值得的连续执行 30 次动作序列放松 5s、握拳 3s、腕屈 3s、腕伸 3s用串口打印实际识别结果与标签对比计算出单类准确率。我的经验是单通道下分类准确率能到 90% 以上双通道下到 96% 以上就具备实际可控性了。再低的话建议先回到特征参数调优而不是急着调 PID——控制器的敏感度有上限喂进来的很多信号本身就不干净。本文还有配套的精品资源点击获取
返回列表