躺在床上不动的时候身体其实一直在往外发信号。胸腔随呼吸起伏心脏每次搏动都会把一小股血液顶进主动脉这个冲击顺着组织传到背部再传到床垫表面——幅度大概只有几微米到几十微米。压电薄膜或者压阻式传感器贴在这些位置上能把这点微小形变转成电信号问题在于心跳、呼吸、体动、床架振动全部叠在同一条波形里看上去就是一团乱七八糟的抖动。这篇内容想讲的就是怎么用 EMD 算法把这一团信号拆开把心跳和呼吸各自分离出来并且附上可以直接跑的 Python 源代码硬件侧面向 PVDF 压电薄膜以及压阻式人体体征传感器两类方案。如果你正在做智能床垫、智能坐垫、生命体征监测雷达之外的接触式方案或者单纯想搞清楚 EMD 在生理信号上的实际表现这篇应该能省你不少试错时间。1. 为什么床垫上能听到心跳和呼吸1.1 压电薄膜和压阻传感器各自抓的是什么先把这个物理过程说清楚不然后面调参全是瞎猜。人躺在床垫上体重产生的静态压力由床垫支撑层承担传感器真正感兴趣的是叠加在这个静态压力之上的动态分量。呼吸带来的动态分量频率很低成人静息呼吸大约每分钟 12 到 20 次换算过来是 0.2 到 0.33 Hz心跳带来的体表微振动要快得多静息心率 60 到 100 次每分钟对应 1.0 到 1.67 Hz。这两个频段虽然不重叠但中间只隔了不到一个八度而且呼吸的二次、三次谐波会往上跑很容易盖到心跳频段上这就是为什么简单地做一次带通滤波经常翻车。PVDF 压电薄膜的工作原理是压电效应薄膜受力形变时内部产生电荷输出的是电荷量不是电压所以必须配电荷放大器或者高输入阻抗的跨阻电路把电荷转成电压。它的特点是只响应动态变化你躺上去那一瞬间有个大尖峰之后静止不动就慢慢回零静态体重它测不到。这个特性对心跳呼吸检测反而是好事相当于自带一个高通直流漂移小。压阻式传感器比如导电橡胶、压阻织物、FSR走的是另一条路受力后电阻变化需要恒压或恒流激励好处是能同时测静态压力和动态分量可以做体重分布、离床检测代价是温漂明显、功耗比 PVDF 高、长时间受压会有蠕变。我实测下来两类传感器在床垫场景下都能用PVDF 的灵敏度和响应速度更占优压阻的成本和电路简单程度更占优。选择哪类主要看你的产品还要不要做在床/离床判断和体动识别如果要做压阻或者 PVDF 加一路压阻的组合方案更省事。1.2 心跳呼吸混在一起难点到底在哪很多人第一反应是不就是个滤波嘛直接上两个巴特沃斯带通一个 0.1 到 0.5 Hz一个 0.8 到 2.0 Hz不就分开了我一开始也是这么想的做完发现有三个坑。第一滤波器的过渡带不够陡。要让 0.5 Hz 和 0.8 Hz 之间干净分开你得用很高阶的滤波器而高阶滤波器在窄带信号上会引入严重的振铃和相位失真心跳的峰值位置会漂算出来的心率就跟着飘。第二生理信号的频率不是固定的。呼吸可能从 0.15 Hz 变到 0.4 Hz心率可能从 0.9 Hz 变到 1.8 Hz你按固定频带切人一深呼吸或者心率稍快分量就串了。第三非平稳性。翻身、咳嗽、说话这些动作会在短时间内产生大幅值的宽带冲击线性滤波器处理这种冲击会产生很长的瞬态响应把后面好几秒的数据都污染掉。EMD 的价值就在这儿。它是一种自适应的分解方法不预设基函数不预设频带直接把信号按照自身的局部时间尺度一层层剥开得到若干个本征模态函数IMF。低频的呼吸自然落到后面的 IMF高频的心跳落到前面的 IMF你只需要判断每个 IMF 的主导频率就能把它们归类重构。这个思路对非平稳信号天然友好这也是我最后选它而不是死磕滤波器的原因。1.3 EMD 与其他分离方案的取舍除了 EMD市面上常见的还有几种做法简单对比一下方便你做技术选型。方案原理优点短板固定带通滤波频带分割实现简单、实时性好频带固定、过渡带振铃、非平稳信号差自适应滤波LMS/RLS用参考信号抵消收敛后可实时需要额外参考通道床垫上很难拿到纯净参考小波变换多尺度分解时频局部化好小波基和层数要选选错效果差很多经验模态分解 EMD数据自适应筛分无需先验基函数、适合非平稳端点效应、模态混叠、计算量偏大变分模态分解 VMD变分求解模态混叠比 EMD 轻需要预设模态数 K调参麻烦我的判断是离线或者在 MCU 上位机做整段分析EMD 性价比最高如果要在端侧做实时逐点输出EMD 的计算量是个问题可以考虑滑动窗口 EMD 或者换 VMD。这篇先把 EMD 这条路走通因为它对新手最友好代码短原理直观调参维度也少。2. EMD 算法拆解从极值点到本征模态函数2.1 EMD 到底在做什么EMD 的核心思想可以用一句话概括任何一个复杂信号都可以拆成若干个振荡模式加一个单调趋势项。这里的振荡模式就是 IMF它必须满足两个条件整个数据段内极值点极大值加极小值的数目和过零点的数目之差不超过 1任意时刻由局部极大值构成的上包络和由局部极小值构成的下包络的均值为零。条件一保证它是纯振荡没有叠加趋势条件二保证它关于零轴局部对称。满足这两条的分量就是 IMF剩下的部分继续拆直到剩余项是单调函数或者极值点少于 3 个为止。具体怎么把一个分量筛出来就是著名的**筛分sifting**过程反复操作下面这几步找到信号所有的局部极大值和局部极小值用三次样条插值分别拟合上包络和下包络求出上下包络的均值曲线 m(t)用原始信号减去 m(t)得到 h(t)判断 h(t) 是否满足 IMF 条件不满足就把 h(t) 当新信号重复上述过程满足就记为一个 IMF。听起来有点绕我打个比方。你把一条抖动的绳子按在桌上先用一根橡皮筋从上面兜住所有凸起再用一根从下面兜住所有凹陷两根橡皮筋的中间线就是均值包络。绳子减去这条中间线剩下的抖动就更对称了。重复几次剩下的抖动就变成一个标准的上下对称振荡这就是第一阶 IMF它对应信号里变化最快的那部分。把它减掉剩下的慢变化继续用同样方法拆。2.2 停止准则筛太多次和筛不够都麻烦筛分不能无限做下去。做得太少得到的 IMF 不满足条件后续分解会混做得太多包络会越来越平最后把 IMF 变成一个近似的常数或者幅度极小的无用分量还白白增加计算量。工程上常用的有两个判据我在代码里同时用了满足任意一个就停SD 判据标准差判据$$ SD \frac{\sum_{t0}^{T} |h_{k-1}(t) - h_k(t)|^2}{\sum_{t0}^{T} h_{k-1}^2(t)} $$一般取 SD 小于 0.2 到 0.3 就停。这个判据衡量的是相邻两次筛分结果之间的差异差异很小说明已经收敛。S-number 判据极值点判据统计当前分量极值点总数和过零点总数如果两者之差小于等于一个阈值常取 0 或 1且已经筛了至少 4 次就认为基本满足 IMF 条件可以停。我实际跑下来SD 阈值 0.2 加 S-number 4 的组合在床垫信号上比较稳。SD 调小到 0.05 会让分解层数变多、计算量涨两三倍效果提升却很小调到 0.4 以上会出现明显的模态混叠呼吸的谐波会漏到心跳层里去。2.3 端点效应与模态混叠EMD 的两块硬骨头端点效应是 EMD 最出名的问题。三次样条插值需要边界条件而信号的两端不一定有极值点直接用端点当极值会造成包络在边界处发散误差会随着筛分逐层向内传播越靠后的 IMF 端点附近越离谱。常见的处理办法有三种镜像延拓、极值点延拓、波形匹配延拓。我在代码里用的是镜像延拓实现最简单效果也够用做法是在信号两端各镜像复制一段数据在延长后的序列上做包络插值插值完再把两端裁掉。镜像段的长度取信号长度的 5% 左右太短挡不住端点效应太长没必要还费时间。模态混叠是说一个 IMF 里混进了时间尺度差别很大的成分或者同一个尺度的成分被拆到了不同 IMF 里。床垫场景下最典型的混叠来源就是呼吸的谐波和体动冲击。缓解手段有几个用集合经验模态分解EEMD往信号里加多次白噪声再平均代价是计算量乘倍数在分解前先用低通滤波把 5 Hz 以上的高频噪声压掉减少极值点数量检查每个 IMF 的主导频率和能量占比能量占比低于 3% 的直接丢弃不参与重构。第二种和第三种组合是我现在常用的配置成本低、见效快。EEMD 我试过在一段 30 秒的信号上跑 50 次集合Python 单核要好几秒做离线分析可以做实时就免谈。3. 硬件与数据链路的关键参数3.1 传感器怎么放放哪里传感器布置直接决定信号质量这一步比算法重要。PVDF 薄膜建议放在床垫上表面下方 2 到 5 厘米的位置横向覆盖胸腔对应的宽度也就是人躺下后肩胛骨下缘到腰之间的区域。放得太浅翻身摩擦会引入大量噪声放得太深信号被床垫吸收幅度掉一个数量级。如果你用的是条带状 PVDF 薄膜长度建议 40 到 60 厘米横跨整个躯干宽度这样人无论怎么躺都能覆盖到。压阻式传感器做成阵列的话横向 4 到 8 个点比较合适既能测体征又能做体动定位但每个点都要走一路采集通道通道间还得做同步采样成本和布线复杂度都会上去。提示传感器和床垫支撑层之间建议垫一层薄泡棉或者无纺布做缓冲避免人体重量长期集中压在一个点上导致 PVDF 薄膜疲劳或压阻材料蠕变。这个细节我在第一版样机上没做两个月后薄膜灵敏度掉了差不多 20%。3.2 信号调理电路与采样参数怎么定采样率的计算很直接。我们关心的最高频率是心跳的几次谐波取到 10 Hz 已经很富裕了按奈奎斯特定理至少 20 Hz工程上取 5 到 10 倍余量50 Hz 到 100 Hz就足够。我一般用 100 Hz一来频率分辨率更好算二来留出余量给后续可能加入的体音分析之类的高频需求。前置调理这条链路PVDF 和压阻不太一样环节PVDF 方案压阻方案前端电荷放大器或高阻抗跨阻恒压/恒流激励 仪表放大器高通0.05 Hz 一阶无源或有源0.05 Hz低通10 Hz 抗混叠二阶以上10 Hz增益电荷放大段 10^8 V/C 量级仪表放大 100 到 500 倍ADC16 位以上ΣΔ 型更佳12 到 16 位注意高阻节点必须防潮防污、走线尽量短激励电流稳定性直接影响零点抗混叠低通一定要在 ADC 之前做不能指望事后数字滤波。混叠进来的高频噪声一旦折回基带跟心跳频率叠在一起任何算法都救不回来。3.3 一个低成本的验证平台如果你想先验证算法再考虑产品化可以用这套最简配置PVDF 薄膜一片带引线、电荷放大模块一个、STM32 或 ESP32 开发板一块、SD 卡模块或者串口上传 PC。采样率设 100 Hz16 位 ADC每 30 秒一帧上传到电脑Python 侧处理。这个平台的重点是先拿到干净的原始数据算法验证阶段的痛点是数据质量不是芯片性能。我自己踩过的坑是一开始为了省事用了一个增益过高的模块结果轻微体动就直接削顶饱和反而比低增益更难处理。宁可选小一点的增益把动态范围留出来削顶是不可逆的失真。采集时同步记一份参考数据很重要比如用胸带式呼吸传感器和指夹式脉搏波同时测方便后面算误差。没有参考你根本不知道分解出来的是真心跳还是噪声。4. 心跳呼吸分离的完整代码实现4.1 预处理去趋势和带通不能省原始信号进 EMD 之前先做两步处理。第一步去趋势床垫信号里往往有一个缓慢漂移的基线可能是温度变化或者人体缓慢位移造成的不去掉会让筛分次数暴涨。第二步低通到 5 Hz 左右把高频电子噪声和摩擦噪声压掉减少后面找极值点时出现的伪极值。import numpy as np from scipy.signal import detrend, butter, filtfilt def preprocess(x, fs100.0, lp5.0, hp0.05, order4): 去趋势 带通预处理 x: 原始信号 fs: 采样率 Hz lp: 低通截止频率 Hz hp: 高通截止频率 Hz x detrend(np.asarray(x, dtypefloat)) nyq fs / 2.0 b, a butter(order, [hp / nyq, lp / nyq], btypeband) y filtfilt(b, a, x) # 零相位滤波避免峰值位置偏移 return y这里特别说一下filtfilt。它做的是前向加后向两次滤波等效零相位好处是心跳峰值的时间位置不会因为滤波而漂移这对后面做峰值检测算心率很关键。代价是计算量翻倍还会在数据两端产生一小段边界效应所以我在后面分析时会把首尾各 2 秒的数据丢掉。4.2 手写 EMD 的完整实现下面是我实际在用的 EMD 实现包含镜像延拓、双停止判据没有依赖 PyEMD可以直接复制运行。import numpy as np from scipy.interpolate import CubicSpline from scipy.signal import argrelextrema def _extrema(x): 返回极大值和极小值索引 mx argrelextrema(x, np.greater, order1)[0] mn argrelextrema(x, np.less, order1)[0] return mx, mn def _mirror_extend(x, pad): 两端镜像延拓缓解端点效应 if pad 0: return x, 0 left 2 * x[0] - x[1:pad 1][::-1] right 2 * x[-1] - x[-pad - 1:-1][::-1] return np.concatenate([left, x, right]), pad def _build_envelopes(x, pad_ratio0.05): 构造上下包络返回去延拓后与原信号等长的包络 n len(x) pad max(3, int(n * pad_ratio)) xp, off _mirror_extend(x, pad) idx np.arange(len(xp)) mx, mn _extrema(xp) if len(mx) 2 or len(mn) 2: return None, None up CubicSpline(idx[mx], xp[mx], bc_typenatural)(idx) lo CubicSpline(idx[mn], xp[mn], bc_typenatural)(idx) return up[off:off n], lo[off:off n] def _sift(x, sd_thresh0.2, s_number4, max_iter100): 单次筛分返回一个 IMF 候选和是否成功 h x.copy() for _ in range(max_iter): up, lo _build_envelopes(h) if up is None: return h, False m 0.5 * (up lo) h_new h - m sd np.sum((h - h_new) ** 2) / (np.sum(h ** 2) 1e-12) mx, mn _extrema(h_new) zc np.sum(np.diff(np.sign(h_new)) ! 0) s_num abs(len(mx) len(mn) - zc) h h_new if sd sd_thresh or s_num s_number: break return h, True def emd(x, max_imf10, sd_thresh0.2, s_number4): 经验模态分解主函数 返回 imfs: (k, n) 数组, resid: 残差趋势项 r np.asarray(x, dtypefloat).copy() imfs [] for _ in range(max_imf): mx, mn _extrema(r) if len(mx) len(mn) 3: break imf, ok _sift(r, sd_thresh, s_number) if not ok: break imfs.append(imf) r r - imf d np.diff(r) if np.all(d 0) or np.all(d 0): break if not imfs: return np.zeros((0, len(x))), r return np.vstack(imfs), r几个关键点解释一下。_mirror_extend里left的构造是先把x[1:pad1]倒序再用2*x[0]减去它得到的是关于x[0]对称的一系列点right同理。镜像段的长度我用信号长度的 5%30 秒 100 Hz 的数据就是 150 点实测够用。_sift里的bc_typenatural是自然边界条件二阶导在端点为 0配合镜像延拓能有效抑制边界过冲。如果你不加镜像延拓直接把端点当极值点包络会在两端甩出去越往后越明显。4.3 IMF 筛选与分量重构分解完了接下来要判断每个 IMF 属于呼吸、心跳还是噪声。最稳的办法是看主导频率也就是对每个 IMF 做 FFT取频带内能量最大的频率点。def dominant_freq(imf, fs, fmin0.05, fmax5.0): 计算 IMF 的主导频率 n len(imf) w np.hanning(n) spec np.abs(np.fft.rfft(imf * w)) ** 2 freqs np.fft.rfftfreq(n, 1.0 / fs) mask (freqs fmin) (freqs fmax) if not np.any(mask): return 0.0 return freqs[mask][np.argmax(spec[mask])] def imf_energy_ratio(imf, all_imfs): 单个 IMF 在总能量中的占比 e np.sum(imf ** 2) total np.sum(np.sum(all_imfs ** 2, axis1)) 1e-12 return e / total BREATH_BAND (0.10, 0.50) # Hz HEART_BAND (0.80, 2.00) # Hz def separate_components(imfs, fs, min_energy_ratio0.03): 按主导频率把 IMF 归类重构呼吸与心跳分量 br np.zeros(imfs.shape[1]) hr np.zeros(imfs.shape[1]) detail [] for i, imf in enumerate(imfs): f0 dominant_freq(imf, fs) er imf_energy_ratio(imf, imfs) label discard if er min_energy_ratio: if BREATH_BAND[0] f0 BREATH_BAND[1]: br br imf label breath elif HEART_BAND[0] f0 HEART_BAND[1]: hr hr imf label heart detail.append({ imf: i 1, dom_freq: round(f0, 4), energy_ratio: round(er, 4), label: label }) return br, hr, detail这里有两个参数值得展开。min_energy_ratio0.03是能量门限低于 3% 的 IMF 直接丢掉主要是过滤掉那些幅度极小的噪声层它们虽然主导频率可能落在心跳带里但能量贡献太小留着只会增加抖动。这个值设成 0.05 会丢得更多信号干净但可能把弱心跳也丢了设成 0.01 基本等于不过滤噪声全进来。另外一个隐患是呼吸谐波伪装成心跳。呼吸基频 0.25 Hz二次谐波 0.5 Hz三次 0.75 Hz四次 1.0 Hz正好落进心跳带。判断办法是看这个 IMF 的主导频率是否接近某个低频 IMF 主导频率的整数倍如果是很可能就是谐波应该归到呼吸那边或者直接丢弃。实现上可以加一个检查def is_harmonic(f0, base_freqs, tol0.05, max_order5): 判断 f0 是否是某个基频的整数倍谐波 for fb in base_freqs: if fb 0: continue for k in range(2, max_order 1): if abs(f0 - k * fb) tol: return True return False在归类循环里先把主导频率落在呼吸带的 IMF 收集成base_freqs再判断心跳带里的候选是否是它们的谐波是就降级处理。这一步加上之后我在深呼吸较多的测试片段里心率估计的偏差大概降了一半。4.4 心率呼吸率的计算分量重构出来以后算具体数值有两种常用方法我都写上了实际用的时候两个都算一遍差距太大就说明这一段不可信直接标记为无效。from scipy.signal import find_peaks def rate_by_fft(seg, fs, band): 基于 FFT 峰值算速率返回 次/分钟 n len(seg) w np.hanning(n) spec np.abs(np.fft.rfft(seg * w)) ** 2 freqs np.fft.rfftfreq(n, 1.0 / fs) mask (freqs band[0]) (freqs band[1]) if not np.any(mask): return 0.0 f0 freqs[mask][np.argmax(spec[mask])] return f0 * 60.0 def rate_by_peaks(seg, fs, band, min_dist_sec): 基于峰值间隔算速率返回 次/分钟 nyq fs / 2.0 b, a butter(4, [band[0] / nyq, band[1] / nyq], btypeband) y filtfilt(b, a, seg) peaks, _ find_peaks(y, distanceint(min_dist_sec * fs)) if len(peaks) 2: return 0.0 duration (peaks[-1] - peaks[0]) / fs return (len(peaks) - 1) / duration * 60.0心跳的峰值检测里min_dist_sec取 0.4 秒比较合适因为生理上两次心搏间隔不会短于 0.4 秒对应 150 bpm 上限呼吸取 1.5 秒对应最快 40 次每分钟也够覆盖了。FFT 法的优势是抗噪缺点是频率分辨率受窗口长度限制30 秒窗在 100 Hz 采样下的分辨率是 1/30 约 0.033 Hz换算成心率约 2 bpm够用但不够精细。想要更细的分辨率要么加长窗口要么对频谱做抛物线插值。峰值法的优势是能反映逐拍的变异做 HRV 分析时必用缺点是容易被运动伪迹骗到出现假峰。4.5 把整条链路串起来下面是端到端的调用示例从原始信号到输出的完整流程。def analyze(x, fs100.0): # 1. 预处理 y preprocess(x, fsfs, lp5.0, hp0.05, order4) # 2. 去掉两端各 2 秒规避滤波边界效应 cut int(2 * fs) y y[cut:-cut] # 3. EMD 分解 imfs, resid emd(y, max_imf10, sd_thresh0.2, s_number4) if imfs.shape[0] 0: return None # 4. 分量分离 br, hr, detail separate_components(imfs, fs, min_energy_ratio0.03) # 5. 速率估计 breath_fft rate_by_fft(br, fs, BREATH_BAND) breath_pk rate_by_peaks(br, fs, BREATH_BAND, min_dist_sec1.5) heart_fft rate_by_fft(hr, fs, HEART_BAND) heart_pk rate_by_peaks(hr, fs, HEART_BAND, min_dist_sec0.4) return { breath_fft: round(breath_fft, 1), breath_peak: round(breath_pk, 1), heart_fft: round(heart_fft, 1), heart_peak: round(heart_pk, 1), imf_detail: detail, breath_signal: br, heart_signal: hr, }输出里同时给了两种方法的估算值实践中的判据是两者相差超过 3 bpm 或 2 次/分就认为这一段结果不可用通常是出现了体动或者信号削顶。产品化的时候这种段直接不显示数值比给个错数强得多。5. 实测调参与常见问题排查5.1 我踩过的坑和排查思路调试期遇到的问题五花八门我把最高频的几个整理成表方便你对号入座。现象大概率原因排查与解决心率数值长期恒定为 60 或某个固定值心跳分量里混进了 50/60 Hz 工频或其折叠分量检查抗混叠滤波检查采集地线看 IMF 主导频率是否在 0.8-1.0 附近异常聚集呼吸和心跳波形看起来一样模态混叠两者被拆到同一个 IMF提高 SD 阈值到 0.3或在 EMD 前加 0.8 Hz 以上带阻预滤数据两端分解结果发散端点效应确认镜像延拓已启用pad_ratio 从 0.05 调到 0.1翻身段之后几秒心率全乱大幅值冲击的瞬态加体动检测整段标记无效等信号平复后重新取窗分解层数特别多、越往后幅度越小噪声导致的伪极值EMD 前低通截止降到 3 Hz或提高能量门限有人在床上时数值正常空床时乱跳空床时信号基本是噪声算法仍在强行分解加一个信号能量判据低于门限直接返回空结果计算太慢30 秒数据跑了好几秒筛分迭代次数多、镜像段长、样条插值开销降采样到 50 Hz 再分解或缩短窗口到 20 秒心率比实际偏高约 2 倍呼吸谐波被当成心跳基频启用谐波判据把心跳带内的谐波 IMF 剔除体动检测这块单独说一句。最简单的做法是算滑动窗口内的信号方差超过阈值的窗口标记为运动段不参与结果输出。复杂一点可以用 IMF 的瞬时幅值包络但没必要方差法在床垫场景已经够用成本还低。5.2 提高准确率的几个实操心得窗口长度的选择。太短频率分辨率不够心跳和呼吸都算不准太长非平稳性累积人的呼吸和心率在 60 秒内本来就会变平均值意义下降。我最后定在30 秒窗、10 秒步进也就是每 10 秒更新一次结果用的是最近 30 秒的数据。这个配置在实时性和稳定性之间平衡得比较好。采样率不是越高越好。有人觉得采样率高信号更完整实际上床垫信号的带宽就那么宽100 Hz 采样已经把有效信息全抓住了再往上只是增加数据量、拖慢 EMD 的极值点搜索。如果非要降到 50 Hz记得把抗混叠低通相应降到 20 Hz 以下。先做一轮离线全量分析再定参。我的习惯是攒够 20 到 30 组数据包含不同体型、不同睡姿、不同心率水平的样本用同一套参数跑一遍看误差分布再决定门限值。凭一两组数据调出来的参数换个人就崩。保留中间结果。分解出的每个 IMF、每个 IMF 的主导频率和能量占比都存下来。出问题的时候你能一眼看出是哪一层串了比只看最终数值效率高得多。别迷信单一算法。EMD 是好用但它不是万能的。如果后面要做产品建议设计成EMD 为主算法结果做交叉校验的结构比如同时用峰值法和频谱法一致才输出不一致就丢弃这一帧。注意生理信号涉及个人健康信息采集、存储、传输环节该做的保护一样不能少。本地处理、加密存储、最小化采集这些原则从原型阶段就该养成习惯后面补救成本很高。5.3 从原型到产品还能怎么走如果你已经跑通了上面的流程接下来有几个方向可以扩展。一是多传感器融合横向铺几个 PVDF 点用不同位置的信号做加权或者做相干分析能显著提高抗体动能力因为翻身时各点响应方向不同而心跳是全身性的。二是实时化把滑动窗口 EMD 放到 MCU 上或者用定点数重写关键步骤实测在 Cortex-M4 上跑 10 秒 50 Hz 的数据优化后大概几百毫秒做秒级更新是可行的。三是扩展体征维度把同样的框架复用到体动识别、在床离床判断、睡眠分期上呼吸和心跳的变异性本身就是睡眠分期的好特征。我自己在做这一类项目时体会最深的一点是算法只占三成另外七成是硬件布置、信号质量和参数调校。同样的代码传感器放偏了几厘米或者泡棉垫厚了一点结果就可能天差地别。所以别急着优化算法先把数据采干净把参考数据对齐你会发现后面所有的调参都变得有章可循。