心电监测正从医院的大型设备走向手腕上的一块表。苹果手表的房颤提示、华为手表的ECG测量,背后都是同一套技术链路:采集心电信号、提取特征、识别异常。真正难的不是算法本身,而是把这套算法塞进一个内存只有几百KB、功耗预算以毫瓦计的穿戴设备里。这篇文章就从信号处理、模型设计到芯片部署,把整个流程拆开讲清楚。

一、穿戴设备ECG信号的特点与预处理
穿戴设备的ECG和医院十二导联心电图完全是两回事。单导联、采样率低(通常125Hz到250Hz)、电极接触不稳定,导致信号里混着基线漂移、肌电干扰和工频噪声。如果直接把原始信号喂给模型,识别准确率会大幅下降,所以预处理环节占整个方案一半以上的工作量。
首先是去噪。基线漂移主要由呼吸和电极移动引起,属于低频分量,可以用截止频率0.5Hz左右的高通滤波器处理;肌电噪声频率较高,40Hz以上的低通滤波即可抑制。Python里的scipy提供了现成的 Butterworth滤波器实现:
from scipy.signal import butter, filtfilt
def bandpass_filter(data, lowcut=0.5, highcut=40.0, fs=250, order=4):
nyq = 0.5 * fs
b, a = butter(order, [lowcut / nyq, highcut / nyq], btype='band')
return filtfilt(b, a, data)这里用filtfilt而不是lfilter,是因为它做前向加后向双向滤波,相位失真为零,对后续的波形定位非常重要。在嵌入式端,如果内存不足以缓存整段信号,可以改用IIR直接II型结构的逐点滤波实现。
其次是心拍切分。异常检测通常以单个心拍为单位,需要先用Pan-Tompkins算法定位R波峰值,再截取R波前后的片段。经典做法是对信号做带通滤波、求导、平方后做滑动窗口积分,R波会在积分信号中形成一个明显的平台。切分时一般取R峰前0.3秒、后0.5秒,这个窗口覆盖了P波到T波的主要形态。
二、模型设计与轻量化改造
在云端,你会用ResNet50甚至更深的网络来做ECG分类,但在穿戴设备上这条路的门是关死的。一颗典型的 Cortex-M4 主控,Flash 512KB、SRAM 128KB,跑一个INT8量化的一维CNN(参数量2万左右)已经是舒适上限。
实践证明,一维卷积网络对单导联ECG的分类效果并不输给大模型。一个三层1D-CNN加全局平均池化的结构,在MIT-BIH数据集的五分类任务上能达到98%以上的准确率,参数量却只有1万级别:
import torch.nn as nn
class ECGNet(nnn.Module): # 注意:应为 nn.Module
pass
# 正确的结构定义如下
class ECGNet(nn.Module):
def __init__(self, num_classes=5):
super().__init__()
self.features = nn.Sequential(
nn.Conv1d(1, 16, kernel_size=7, padding=3),
nn.BatchNorm1d(16),
nn.ReLU(),
nn.MaxPool1d(2),
nn.Conv1d(16, 32, kernel_size=5, padding=2),
nn.BatchNorm1d(32),
nn.ReLU(),
nn.MaxPool1d(2),
nn.Conv1d(32, 64, kernel_size=3, padding=1),
nn.BatchNorm1d(64),
nn.ReLU(),
nn.AdaptiveAvgPool1d(1)
)
self.classifier = nn.Linear(64, num_classes)
def forward(self, x):
x = self.features(x).squeeze(-1)
return self.classifier(x)训练数据方面,MIT-BIH心律失常数据库仍然是事实标准,包含48条半小时长度的双导联记录,标注了心拍级别的心律类别。需要注意的是类别极不均衡,正常心拍占了九成以上,训练时必须做重采样或加权交叉熵,否则模型会把所有心拍都判成正常。另一个坑是患者级划分:同一个人的心拍同时出现在训练集和测试集会虚高准确率,正确做法是按病人ID做交叉验证。
训练完成后,量化是上板前的关键一步。训练后量化(PTQ)简单但掉点明显,对于ECG这种小模型,建议用量化感知训练(QAT),把量化误差在训练过程中消化掉。经验数据是:INT8量化后模型体积缩到四分之一,准确率损失控制在0.5%以内。如果还嫌大,可以再做结构化剪枝,把通道数砍半,精度还能保住97%以上。
三、边缘端部署与功耗优化
部署环节的目标平台分两类。一类是传统MCU方案,用TFLite Micro或CMSIS-NN做推理,优点是生态成熟、成本低;另一类是带NPU的SoC,比如Ambiq Apollo系列、恒玄的穿戴芯片,卷积运算由硬件加速器完成,能效比高出一个数量级。
以TFLite Micro为例,部署流程是:PyTorch模型先转ONNX,再转TFLite格式并完成INT8量化,最后用TFLite Micro的解释器在MCU上运行。下面是设备端推理的C++核心代码:
#include "tensorflow/lite/micro/micro_interpreter.h"
// 模型加载后创建解释器,tensor_arena是推理用的内存池
constexpr int kTensorArenaSize = 40 * 1024;
uint8_t tensor_arena[kTensorArenaSize];
// 输入一个心拍片段(例如200个采样点),输出5类概率
void RunInference(tflite::MicroInterpreter* interpreter,
const float* ecg_beat, float* output) {
float* input = interpreter->typed_input_tensor<float>(0);
for (int i = 0; i < 200; i++) {
input[i] = ecg_beat[i];
}
interpreter->Invoke(); // 执行推理
float* out = interpreter->typed_output_tensor<float>(0);
for (int i = 0; i < 5; i++) {
output[i] = out[i];
}
}功耗优化是穿戴设备的生死线。几个行之有效的手段:第一,用事件驱动替代连续监测,先用极低功耗的硬件比较器检测R波,只有检测到心拍才唤醒主控跑推理;第二,控制采样占空比,静息状态下每分钟监测15秒,结合运动传感器数据动态调整;第三,模型分级触发,MCU上的小模型先做初筛,判定可疑时再把片段通过BLE传给手机端的大模型复核,这样既省电又能压低误报。
还有一个容易被忽视的工程问题:电极接触不良会产生伪信号,模型可能把运动伪差识别成室性早搏,造成用户恐慌。所以在推理之前必须做信号质量评估(SQI),常用方法是计算信号与模板的相关系数,低于阈值就丢弃该片段并提示用户调整佩戴位置。医疗级产品对误报的容忍度远低于消费电子,这一步不能省。
四、方案对比与选型建议
不同硬件平台之间的差异值得用一张表说清楚。假设目标是五分类心律失常检测、单心拍推理:
| 平台 | 模型格式 | 单次推理耗时 | 功耗量级 | 适用场景 |
|---|---|---|---|---|
| Cortex-M4 @ 80MHz | TFLite Micro INT8 | 约80ms | 毫瓦级 | 手环、低成本胸贴 |
| Cortex-M55 + Ethos-U55 | TFLite INT8 | 约5ms | 微瓦到毫瓦级 | 智能手表主流方案 |
| 专用ECG AFE + SoC | 厂商SDK模型 | 约2ms | 微瓦级 | 长时程贴片监测 |
选型的核心权衡是实时性与续航。房颤筛查并不要求逐拍实时,攒30秒片段再推理完全可以接受,这种情况MCU软算就够用;而要做人机交互式的即时反馈,或者要在本地跑多导联分析,NPU基本是必选项。另外,设备端只输出类别还不够,最好附带置信度,低置信度样本走云端复核,这对通过医疗认证时的敏感性分析很有帮助。
最后提醒一点合规边界:边缘端AI输出的结论定位为风险提示而非诊断,文案和交互设计上要避免直接给出疾病名称,这是消费级穿戴产品过审的普遍做法。技术上做到位,产品表述上守住边界,ECG异常检测才能真正从实验室走进用户日常。