资讯动态

基于Web Bluetooth与Canvas的肌电信号实时可视化:myojs-emg项目解析

发布时间:2026/10/17 8:37:20 来源:尧图企业网站定制
简介myojs-emg 是一份面向前端开发者、生物信号处理爱好者与人机交互研究者的开源项目源码聚焦于将 Myo 臂章采集的肌电EMG信号转化为浏览器中可交互的可视化图形。项目以 JavaScript 为核心借助 MyoJS 库与 Myo 设备通信实时读取肌肉电信号并驱动图表渲染可用于手势识别、运动控制与增强现实等场景的学习与二次开发。资源包共 69 个文件约 652KB包含 33 个 js 脚本核心逻辑与 MyoJS 库、12 个 html 示例页面、12 个 md 说明文档、4 个 json 配置及若干 png 图片与 yml 构建文件目录中可见 Hub、Myo、Pose、Quaternion 等模块以及 chartjs、three.js 可视化示例结构清晰便于按模块研读。目前已有 249 人学习下载。通过该源码可掌握 JavaScript 与硬件设备通信、EMG 数据解析、实时数据处理与图表动画渲染的完整思路适合作为可穿戴设备数据可视化的实践参考。1. 从一块臂环到浏览器myojs-emg 能帮你把肌电信号画出来如果你手上有一块 Myo 臂章想看看自己握拳、伸指时前臂肌肉发出的肌电信号到底长什么样又不想装一整套笨重的桌面软件myojs-emg 这个项目就是冲这个场景来的。它是一个纯 JavaScript 写的 EMG 数据可视化工具跑在浏览器里通过 Web Bluetooth 直接连 Myo 臂章把八路 EMG 通道的原始数据实时画成波形图。适合两类人一类是做手势识别、肌电控制原型的学生和独立开发者想快速拿到可视化反馈另一类是前端工程师想找一个真实硬件数据流的 Web 可视化练手项目。它不依赖后端打开页面就能连设备这一点在同类工具里算省事的。2. 技术选型与整体架构为什么是 Web Bluetooth 加 Canvas2.1 浏览器直连硬件的可行性判断Myo 臂章通过低功耗蓝牙广播 EMG、IMU 和姿态数据。传统做法是写一个桌面端程序用操作系统提供的蓝牙 API 接收再转发给前端。myojs-emg 绕开了这一步直接用 Web Bluetooth API 在浏览器里发起连接。这个选择的好处是部署成本几乎为零——不需要装驱动、不需要本地服务一个静态页面就能跑。代价是浏览器兼容性受限目前主要工作在基于 Chromium 的桌面浏览器上移动端支持还不完整。项目整体分三层蓝牙通信层负责发现设备、订阅特征值数据解析层把二进制包拆成八路 EMG 采样渲染层用 Canvas 2D 把数据滚动绘制成波形。三层之间通过一个简单的事件总线解耦蓝牙层收到数据后派发事件渲染层监听事件后更新画布。这种结构的好处是如果你想换成别的 EMG 设备只需要替换蓝牙通信层渲染逻辑不用动。2.2 目录结构与模块职责项目目录大致如下我按实际拆包时看到的组织方式描述myojs-emg/ ├── index.html # 入口页面包含连接按钮和画布容器 ├── src/ │ ├── bluetooth.js # Web Bluetooth 连接与特征值订阅 │ ├── parser.js # EMG 数据包解析 │ ├── renderer.js # Canvas 波形绘制 │ └── main.js # 初始化与事件绑定 ├── styles/ │ └── main.css # 页面布局与画布样式 └── package.json # 开发依赖与启动脚本bluetooth.js里封装了navigator.bluetooth.requestDevice的调用过滤条件按 Myo 的服务 UUID 设置。parser.js是纯函数模块输入是DataView输出是八路浮点数组。renderer.js维护一个环形缓冲区每次收到新样本就推入并重绘。main.js把三者串起来处理用户点击连接按钮后的流程。2.3 数据流从蓝牙到画布的完整链路一次完整的数据流是这样的用户点击连接bluetooth.js弹出设备选择框选中 Myo 后建立 GATT 连接找到 EMG 数据特征值并开启通知。每当设备推送一包数据characteristicvaluechanged事件触发parser.js被调用把原始字节转成八路 EMG 值。解析结果通过自定义事件传给renderer.js渲染器把新样本追加到每个通道的缓冲区然后清空画布、重绘所有通道的波形。整个过程在 50 毫秒内完成肉眼看起来是连续滚动的。注意Web Bluetooth 要求页面运行在 HTTPS 或 localhost 下直接双击打开index.html会连不上设备。3. 跑起来从克隆到看到第一条波形3.1 环境准备与依赖安装先把项目拉到本地进入目录后安装开发依赖。项目用了一个轻量开发服务器来提供 HTTPS 本地环境因为 Web Bluetooth 在file://协议下不工作。git clone 项目地址 myojs-emg cd myojs-emg npm installnpm install会装两个东西一个静态服务器常见做法是用http-server或vite以及可选的代码检查工具。如果你不想装 Node 环境也可以用 Python 自带的 HTTP 服务器但需要自己配 HTTPS 证书稍微麻烦一点。3.2 启动本地服务并连接臂章安装完成后用项目里配好的脚本启动npm start终端会输出一个https://localhost:端口的地址。用 Chrome 或 Edge 打开这个地址页面上应该有一个「连接 Myo」按钮和一块空白画布。点击按钮浏览器会弹出蓝牙设备选择窗口这时需要先唤醒 Myo 臂章——把它戴在手臂上做一次短促的握拳动作让它从休眠中醒来然后双击臂章上的按钮进入广播模式。设备列表里会出现 Myo 的名字选中并确认。连接成功后画布上会立刻出现八条彩色波形每条对应一个 EMG 通道。握拳时波形幅度明显增大放松时回到基线附近。如果画布一直空白先检查浏览器控制台有没有报错再确认臂章电量是否充足。3.3 关键参数怎么调项目里几个影响体验的参数集中在renderer.js和main.js里。我列一下常见可调项参数位置作用建议值bufferSizerenderer.js每个通道保留的样本数500lineWidthrenderer.js波形线宽1.5channelColorsrenderer.js八路通道颜色数组彩虹色系scaleFactorrenderer.js纵向缩放系数根据信号幅度调sampleRateparser.js采样率假设值200 HzbufferSize决定画布上同时显示多长时间的数据。500 个样本在 200 Hz 下大约是 2.5 秒的窗口。如果你要看更长时间的趋势把它调到 1000 或 2000但注意画布宽度固定时波形会变密。scaleFactor是玄学参数不同人手臂肌肉信号强度差异很大同一个人不同位置贴臂章结果也不一样建议先设成 1.0 看波形幅度再决定放大还是缩小。4. 核心模块拆解解析、缓冲与绘制4.1 蓝牙连接与特征值订阅bluetooth.js的核心是这段连接逻辑// bluetooth.js const MYO_SERVICE_UUID 0000xxxx-0000-1000-8000-00805f9b34fb; const EMG_CHAR_UUID 0000yyyy-0000-1000-8000-00805f9b34fb; async function connectMyo() { // 请求设备按服务 UUID 过滤 const device await navigator.bluetooth.requestDevice({ filters: [{ services: [MYO_SERVICE_UUID] }], optionalServices: [MYO_SERVICE_UUID] }); // 建立 GATT 连接 const server await device.gatt.connect(); // 获取服务与特征值 const service await server.getPrimaryService(MYO_SERVICE_UUID); const characteristic await service.getCharacteristic(EMG_CHAR_UUID); // 开启通知绑定数据回调 await characteristic.startNotifications(); characteristic.addEventListener(characteristicvaluechanged, (event) { const value event.target.value; // DataView const emgData parseEMGPacket(value); dispatchEvent(new CustomEvent(emgdata, { detail: emgData })); }); return device; }这段代码里filters决定了设备选择框里只显示广播了 Myo 服务的设备避免用户选错。optionalServices是必须写的否则后续getPrimaryService会抛权限错误这是 Web Bluetooth 的一个常见坑。startNotifications之后每次设备推送数据都会触发characteristicvaluechanged事件对象的target.value是一个DataView直接交给解析函数处理。解析结果通过CustomEvent派发渲染层不需要知道蓝牙细节。4.2 EMG 数据包解析Myo 的 EMG 数据包是二进制格式每个采样点用有符号整数表示。parser.js负责把DataView转成八路浮点数组// parser.js function parseEMGPacket(dataView) { const channelCount 8; const samplesPerPacket 1; // 每包一个采样点八通道 const result new Array(channelCount).fill(0); // Myo EMG 数据为 8 个有符号 8 位整数依次排列 for (let i 0; i channelCount; i) { // getInt8 读取有符号字节范围 -128 到 127 const raw dataView.getInt8(i); // 归一化到 -1.0 到 1.0 之间方便绘制 result[i] raw / 128.0; } return result; }这里的关键是getInt8因为 Myo 的 EMG 原始值是有符号的用getUint8会得到错误的正值。归一化除以 128 是一个经验做法让不同通道的幅度落在统一区间绘制时不用为每个通道单独算缩放。如果你的设备输出的是 16 位数据把getInt8换成getInt16除数改成 32768逻辑一样。4.3 环形缓冲区与 Canvas 绘制渲染层维护一个环形缓冲区避免数组无限增长导致内存问题// renderer.js class EMGRenderer { constructor(canvas, channelCount, bufferSize) { this.ctx canvas.getContext(2d); this.channelCount channelCount; this.bufferSize bufferSize; // 每个通道一个环形缓冲区 this.buffers Array.from({ length: channelCount }, () []); this.colors [#e74c3c, #e67e22, #f1c40f, #2ecc71, #1abc9c, #3498db, #9b59b6, #34495e]; } pushSample(emgData) { for (let ch 0; ch this.channelCount; ch) { const buf this.buffers[ch]; buf.push(emgData[ch]); // 超出容量时移除最旧样本 if (buf.length this.bufferSize) { buf.shift(); } } this.draw(); } draw() { const { ctx, buffers, colors, bufferSize } this; const width ctx.canvas.width; const height ctx.canvas.height; const channelHeight height / this.channelCount; ctx.clearRect(0, 0, width, height); for (let ch 0; ch this.channelCount; ch) { const buf buffers[ch]; const baseline channelHeight * (ch 0.5); ctx.beginPath(); ctx.strokeStyle colors[ch]; ctx.lineWidth 1.5; for (let i 0; i buf.length; i) { const x (i / bufferSize) * width; // 纵向缩放让波形在通道高度内可见 const y baseline - buf[i] * channelHeight * 0.4; if (i 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); } ctx.stroke(); } } }pushSample每收到一包数据就调用一次把八个通道的值分别推入各自的缓冲区超出bufferSize时用shift移除最旧的。draw方法先清空画布然后为每个通道画一条折线。baseline是该通道的垂直中心channelHeight * 0.4是纵向缩放系数保证波形不会溢出到相邻通道。这个实现简单直接在 200 Hz 数据率下性能足够。如果你要跑更高采样率可以把draw改成requestAnimationFrame节流避免每包都重绘。5. 避坑与常见问题连不上、画不出、波形乱5.1 设备选择框里找不到 Myo现象是点击连接按钮后浏览器弹出的设备列表是空的或者只有其他蓝牙设备。原因通常是臂章处于休眠状态没有广播。Myo 在静止一段时间后会自动休眠需要做一次肌肉收缩动作唤醒然后双击按钮进入广播模式。另外确认臂章电量电量过低时广播会不稳定。如果还是找不到检查浏览器是否支持 Web Bluetooth在地址栏输入chrome://bluetooth-internals可以查看底层扫描情况。5.2 连接成功但画布没有波形现象是设备连上了控制台没有报错但画布一直空白。原因可能是特征值 UUID 写错了或者startNotifications没有真正生效。先检查bluetooth.js里的EMG_CHAR_UUID是否和你的设备匹配不同批次的 Myo 固件可能用不同的 UUID。然后在characteristicvaluechanged回调里加一行console.log(value.byteLength)如果一直没有输出说明通知没开起来。解决方法是确认optionalServices里包含了服务 UUID并且getCharacteristic返回的对象不为空。5.3 波形幅度太小或太大现象是波形几乎是一条直线或者上下剧烈跳动超出通道范围。原因是scaleFactor和归一化除数不匹配。Myo 的 EMG 原始值范围在 -128 到 127 之间除以 128 后落在 -1 到 1 之间再乘以channelHeight * 0.4后幅度通常合适。但如果你的臂章贴的位置肌肉信号弱或者信号强就需要手动调scaleFactor。我一般会在renderer.js里加一个滑块控件实时调整缩放找到合适值后再写死。5.4 页面卡顿或波形撕裂现象是波形滚动不流畅或者出现断裂。原因是每包数据都触发一次draw而draw里做了全量重绘。在 200 Hz 下每秒重绘 200 次如果画布尺寸大、通道多浏览器会吃力。解决方法是把draw调用改成requestAnimationFrame节流或者用离屏 Canvas 做双缓冲。另一个原因是bufferSize设得太大比如 5000每次shift和遍历都变慢。建议先降到 500 试试确认流畅后再逐步加大。5.5 浏览器兼容性翻车现象是在 Firefox 或 Safari 里点击连接按钮没反应。原因是 Web Bluetooth 在这些浏览器里默认不启用或完全不支持。目前稳妥的做法是只用基于 Chromium 的桌面浏览器比如 Chrome 或 Edge。如果你需要跨浏览器得准备一个 WebSocket 桥接方案用本地 Node 服务接收蓝牙数据再转发给页面但那就不是这个项目原本的轻量路线了。6. 二次开发换设备、加通道、存数据这个项目的结构留了不少改造口子。如果你想换成另一款 EMG 设备只需要改bluetooth.js里的服务 UUID 和特征值 UUID再调整parser.js的解析逻辑。比如某款设备输出 16 位有符号整数把getInt8换成getInt16除数从 128 改成 32768其他层不用动。这种分层设计的好处在这里体现得很明显。加通道也简单。renderer.js里的channelCount是构造参数parser.js返回的数组长度跟着改颜色数组补几个色值就行。我试过把八通道扩到十六通道画布纵向分成十六份每条波形高度减半肉眼还能分辨。如果你要做手势识别可以在main.js里加一个特征提取模块每次收到emgdata事件后计算均方根或过零率再喂给一个简单的分类器。数据存储方面可以在pushSample里顺手把样本写进IndexedDB方便回放和标注。有一个我踩过的坑值得说环形缓冲区用shift在样本量大时性能下降明显因为shift是 O(n) 操作。后来我改成固定长度数组加写指针覆盖旧数据而不是移动数组帧率立刻稳了。从那以后我每次做实时波形都强制走一遍「固定缓冲加指针」的方案不再用shift。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑