第1章:数据读取与可视化——从原始信号到第一张频谱图

做肌电信号处理这么多年,我始终觉得:数据读取和可视化,是所有分析的起点。你想想看,连信号长什么样都不知道,后面那些滤波、特征提取、分类,不就成了空中楼阁吗?

这一章,我们就来搞定三件事:读数据、画波形、看频谱。我会带着你,用Python把这三步走通。

1.1 常见格式的读取:CSV、EDF、BIO

肌电信号的存储格式五花八门。我个人最常遇到的是这三种:

格式 特点 典型场景
CSV 纯文本,通用性强 实验室自采数据、公开数据集
EDF 欧洲标准,含元数据 临床肌电、多导睡眠图
BIO Biomation专用格式 商用肌电采集设备

1.1.1 CSV格式读取

CSV是最简单的。但要注意:肌电CSV通常没有表头,或者第一列是时间戳。

import pandas as pd
import numpy as np

# 读取CSV,假设第一列是时间,第二列是肌电信号
df = pd.read_csv('emg_data.csv', header=None)
time = df.iloc[:, 0].values
emg = df.iloc[:, 1].values

print(f'采样点数:{len(emg)},采样率:{1/(time[1]-time[0]):.1f} Hz')
我的小经验: 读CSV时,先看一眼文件头几行。我曾经遇到过一整个数据集的时间戳是字符串格式,折腾了半天才发现。

1.1.2 EDF格式读取

EDF格式自带元数据,比如采样率、电极位置、患者信息。我用的是pyedflib库。

import pyedflib

f = pyedflib.EdfReader('emg_record.edf')
n_channels = f.signals_in_file
channel_names = f.getSignalLabels()
sample_rate = f.getSampleFrequency(0)

# 读取第一个通道的数据
emg = f.readSignal(0)
f.close()

print(f'通道数:{n_channels},通道名:{channel_names}')
print(f'采样率:{sample_rate} Hz,数据长度:{len(emg)}')
注意: EDF文件可能包含多个通道,但肌电信号通常只占其中几个。读取前先确认通道名,别把心电信号当成肌电分析了。

1.1.3 BIO格式读取

BIO格式是Biomation公司的专有格式。说实话,这个格式我一开始也头疼。后来发现可以用biomation库处理。

# 假设有biomation库(需单独安装)
from biomation import BioFile

bio = BioFile('emg_data.bio')
emg = bio.get_channel_data(0)  # 获取第一个通道
sample_rate = bio.sample_rate

print(f'采样率:{sample_rate} Hz')
核心要点: 无论什么格式,最终我们需要的就两样东西:原始信号数组采样率。有了这两样,后面的事就好办了。

1.2 时域波形绘制——让信号“现原形”

数据读进来了,第一件事就是画波形。我习惯用matplotlib,简单直接。

import matplotlib.pyplot as plt

plt.figure(figsize=(12, 4))
plt.plot(time, emg, color='#2c7bb6', linewidth=0.8)
plt.xlabel('时间 (s)')
plt.ylabel('幅值 (mV)')
plt.title('原始肌电信号时域波形')
plt.grid(True, alpha=0.3)
plt.show()

画出来之后,你会看到什么?

  • 基线漂移: 信号整体上下浮动,像波浪一样
  • 运动伪迹: 突然的大幅波动,通常是电极移动造成的
  • 肌电活动段: 幅值明显增大的区域,对应肌肉收缩
避坑指南: 我曾经在画图时忘了设置linewidth,结果信号太密,看起来像一团黑糊糊的东西。对于高采样率(比如1000 Hz以上)的信号,线宽设到0.5-0.8比较合适。

1.3 频谱图绘制——看看信号里藏着哪些频率

时域波形只能看到幅值变化,但频率信息才是肌电信号的核心。肌电信号的频率范围通常在20-500 Hz,其中主要能量集中在50-150 Hz。

画频谱图,我用的是scipy.signal.periodogram或者自己写FFT。

from scipy import signal

frequencies, psd = signal.periodogram(emg, fs=sample_rate)

plt.figure(figsize=(12, 4))
plt.semilogy(frequencies, psd, color='#d7191c', linewidth=1.0)
plt.xlabel('频率 (Hz)')
plt.ylabel('功率谱密度 (dB)')
plt.title('肌电信号频谱图')
plt.xlim([0, 500])  # 只显示0-500 Hz
plt.grid(True, alpha=0.3)
plt.show()

为什么用semilogy?因为功率谱的数值跨度很大,线性坐标下低频部分会被压扁,看不清楚。

关键观察:
  • 50 Hz(或60 Hz)处如果有尖峰,那是工频干扰,需要后续滤除
  • 低频段(<20 Hz)能量过高,说明有运动伪迹
  • 高频段(>400 Hz)能量过低,可能是信号衰减或采集设备带宽限制

1.4 本章知识体系总览

下面这张图,是我自己总结的本章核心逻辑。你照着这个流程走,基本不会出错。

第1章:数据读取与可视化——知识流程图 数据读取 CSV / EDF / BIO 时域波形绘制 观察幅值、基线、伪迹 频谱图绘制 分析频率成分 输出:信号数组 + 采样率 + 可视化结果 常见问题排查 ① 数据读不出来?检查文件路径和格式匹配 ② 波形太乱?先确认采样率是否正确 ③ 频谱有异常尖峰?大概率是工频干扰

1.5 本章小结

嗯,到这里,第一章的内容就差不多了。我们做了三件事:

  • 读数据: 三种常见格式,各有各的坑,但核心都是拿到信号和采样率
  • 画波形: 一眼看出信号质量,基线漂移、运动伪迹都逃不过你的眼睛
  • 看频谱: 频率成分一目了然,工频干扰、伪迹频率都能定位

我个人觉得,这一章是后面所有分析的基础。你想想看,如果连原始信号长什么样都不知道,后面那些滤波参数怎么设?特征提取往哪个方向做?

所以,别急着跳过去。找一段你自己的肌电数据,按照上面的步骤走一遍。画出来的图,多看看,多想想。你会发现,信号会“说话”的。

一句话记住本章: 读数据是基础,画波形是直觉,看频谱是科学。三者缺一不可。

专注资料整理