医疗设备图像系统中,首先要把物理电信号转换为数字信息,从心电图到血氧仪,莫不如此,这里首先分析一下心电图。
把电信号转换为图像的度量转换:
这里以心电图为例,在心电图 (ECG) 系统中,将电信号转换为可视化波形涉及医学标准、信号处理和图形学的结合。下面详细解释其核心逻辑和单位换算关系:
一、心电信号的医学基础
1. 电信号来源
- 心脏活动产生的生物电信号,通过皮肤电极采集
- 典型幅度:0.1mV ~ 5mV(毫伏)
- 频率范围:0.05Hz ~ 100Hz(主要能量集中在 0.5Hz ~ 40Hz)
2. 医学标准单位
- 电压:毫伏 (mV) 或微伏 (μV),1mV = 1000μV
- 时间:秒 (s) 或毫秒 (ms),1s = 1000ms
- 标准灵敏度:1mV 对应 10mm 记录纸高度(即 10mm/mV)
- 标准走纸速度:25mm/s(即横向 1mm 对应 40ms)
二、信号数字化与采样
1. ADC 转换
- 模拟心电信号通过 ADC (模数转换器) 转换为数字值
- 常见采样率:250Hz ~ 1000Hz(每秒 250~1000 个采样点)
对照新洲活动电信号频率范围:0.05Hz ~ 100Hz(主要能量集中在 0.5Hz ~ 40Hz)
可以大致认为一个周期内采集10个点的数据?
- 常见分辨率:12 位~16 位(对应 4096~65536 个量化级别)
2. 数字化后的数据格式
- 采样值通常存储为整数
- 例如:12 位 ADC,0 对应 - 5mV,4095 对应 + 5mV
- 数值转换公式:
电压值(mV) = [(采样值 - 零点值) / 满量程值] × 量程范围
- 采样值:ADC 输出的原始数字值(通常是整数)
- 零点值:对应于 0mV 的 ADC 数值(可能不是 0!)
- 满量程值:ADC 的最大数字值(例如 12 位 ADC 为 4095)
- 量程范围:ADC 能够测量的电压范围(例如 ±5mV)
心电图传感器原理:略 后面有时间再补充
三、单位换算与图形映射
1. 电压 → 高度 换算
-
医学标准:1mV = 10mm
-
在心电图常规测量中,临床最关注且最具代表性的是QRS 波群(因其幅度最大、辨识度最高,是判断心律和心室电活动的核心指标)。
从临床常见的中间值来看:
QRS 波群的典型幅度约为 1.0 mV(基于标准测量条件下的多数正常成人数据,介于 0.5~2.0 mV 范围的中间水平)。
-
像素转换:
像素高度 = 电压值(mV) × 灵敏度系数(mm/mV) × 像素密度(DPI/25.4)
简化为:像素高度 = 电压值 × 像素/mV
-
代码示例中的换算:
float fpPerMv = 10 * DOTS; // 1mV对应的像素高度 float fpPerUv = fpPerMv / 1000; // 1μV对应的像素高度
DOTS
为每毫米像素数(类似 DPI 概念)设DOTS=5
,则fpPerMv=50
,即 1mV 对应 50 像素
qt中 像素是逻辑单位哈。
50 像素的物理高度没有固定值,需根据设备 DPI 计算:
- 普通电脑显示器:约 10~13 毫米
- 手机屏幕:约 4~8 毫米
- 打印输出(300 DPI):约 4.2 毫米
2. 时间 → 宽度 换算
-
医学标准:走纸速度 25mm/s → 1mm = 40ms
-
像素转换:
像素宽度 = 时间(ms) × 走纸速度(mm/s) × 像素密度(DPI/25.4)
简化为:像素宽度 = 采样点索引 × (像素/采样点)
-
心电图通常采用 25mm/s 纸速记录,1 小格 = 1mm=0.04 秒。一个典型的心电图波形由 P 波、PR 间期、QRS 波群、ST 段、T 波以及可能出现的 U 波组成。若以正常心率 60-100 次 / 分计算,一个心动周期约为 0.6-1.0 秒,对应的心电图波形长度约为 15-25mm。
采样率与时间的关系
- 采样率(250Hz):表示每秒采集 250 个心电信号样本(即 250 个采样点 / 秒)。
→ 每个采样点对应的时间 =1秒 ÷ 250采样点 = 0.004秒/采样点 = 4毫秒/采样点
。
X 坐标映射逻辑:采样点索引 → 像素宽度
代码中X坐标 = col * rectWidth + i
,其中i
是当前采样点的索引(从 0 开始递增)。
- 对于单列(
col=0
),X 坐标范围是0 ~ rectWidth-1
(即 0~399 像素),对应采样点索引i=0 ~ 399
。 - 因此,每列可容纳的采样点数量 = rectWidth = 400 个。
每列显示的时间计算
已知:
- 每列有 400 个采样点(
i=0
到i=399
) - 每个采样点对应 4 毫秒(见第一步)
则每列显示的总时间 = 采样点数量 × 单个采样点的时间
= 400采样点 × 4毫秒/采样点 = 1600毫秒 = 1.6秒
。
-
代码示例中的映射:
QPoint p(col * rectWidth + i, ...); // i为采样点索引,直接映射为X坐标
- 假设
rectWidth=400
像素,采样率 250Hz,则:
每个像素对应时间 =1秒/250采样点 × 400像素 = 1.6秒
即每列可显示 1.6 秒的心电信号
- 假设
四、坐标系统映射
1. 数学关系
-
X 轴:时间轴,向右为正
X = 列偏移 + 采样点索引 × 时间缩放因子
-
Y 轴:电压轴,向上为正(医学传统),但计算机图形向下为正,需反转
Y = 行中心位置 - 电压值 × 电压缩放因子 + 整体偏移
2. 代码中的实现
ps:心电图数据采集的常规单位是微伏(μV)如果直接使用 fpPerMv
,会导致显示结果 缩小 1000 倍.
QPoint p((qreal)(col * rectWidth + i),
(qreal)(row * rowHeight + middleHeight - data[i].toInt() * fpPerUv) + 25);
-
X 坐标:
col * rectWidth + i
col * rectWidth
:列偏移(左右两列)i
:采样点索引(时间序列)
-
Y 坐标:
row * rowHeight + middleHeight
:行中心位置- data[i].toInt() * fpPerUv
:电压值转换(负值表示向上偏移)+ 25
:整体向下偏移(避免顶部裁剪)
五、常见参数与标准
参数 | 医学标准值 | 代码中可能的取值 |
---|---|---|
灵敏度 | 10mm/mV | fpPerMv = 10 * DOTS |
走纸速度 | 25mm/s | 每像素对应时间 = 1 秒 / 采样率 × 像素数 |
网格密度 | 大格:5mm×5mm 小格:1mm×1mm | DOTS=1 表示 1 像素 /mm |
导联数 | 12 导联 | 2 列 ×6 行布局 |
信号幅度范围 | -2.5mV ~ +2.5mV | 12 位 ADC 可覆盖 ±5mV 范围 |
六、绘制图形完整代码
void ecg::drawHisECGWave(QPainter &painter, int width, int height, double dots)
{
drawECGGrid(painter, width, height, DOTS);
painter.save();
float fpPerMv = 10 * DOTS;
float fpPerUv = fpPerMv / 1000;
int rectWidth = width / 2;
int rowHeight = height / 6;
int middleHeight = rowHeight / 2;
int cols = 2;
int rows = 6;
for (int col = 0; col < cols; col++)
{
for (int row = 0; row < rows; row++)
{
int wavelen = m_channelData[col * rows + row]->getDataArr().size();
QVector<QPoint> vecPoints;
QString name = m_channelData[col * rows + row]->getChannelName();
QJsonArray data = m_channelData[col * rows + row]->getDataArr();
for (int i = 0; i < wavelen; i++)
{
if (i > rectWidth - dots * 2)
break;
QPoint p((qreal)(col * rectWidth + i), (qreal)(row * rowHeight + middleHeight - data[i].toInt() * fpPerUv) + 25);
vecPoints.append(p);
}
painter.save();
painter.setPen(QColor(62, 168, 115));
painter.drawText(col * rectWidth, row * rowHeight + middleHeight + 25, name);
painter.restore();
for (int i = 0; i < vecPoints.size() - 1; i++)
{
painter.drawLine(vecPoints[i], vecPoints[i + 1]);
}
}
}
painter.restore();
}
背景网格绘制:
- 网格绘制函数:drawECGGrid
- 细网格绘制:浅灰色,间隔 DOTS(5 像素)
- 粗网格绘制:稍深灰色,间隔 5*DOTS(25 像素)
- 绘制方式:遍历行和列,用 drawLine 绘制直线
- 调用时机:在绘制波形前调用,作为背景
void ecg::drawECGGrid(QPainter &painter, int width, int height, double dots)
{
painter.save();
painter.setPen(QColor(247, 250, 250));
for (int i = 0; i <= width / DOTS; i++)
{
painter.drawLine(0, i * DOTS, width, i * DOTS); // row
painter.drawLine(i * DOTS, 0, i * DOTS, height); // col
}
painter.setPen(QColor(206, 224, 229));
for (int i = 0; i <= width / DOTS / 5; i++)
{
painter.drawLine(0, i * DOTS * 5, width, i * DOTS * 5); // row
painter.drawLine(i * DOTS * 5, 0, i * DOTS * 5, height); // col
}
painter.restore();
}
std::unique_ptr<HeartData> m_channelData[12];
声明了一个包含 12 个元素的数组,每个元素都是一个 std::unique_ptr<HeartData>
类型的智能指针。
drawLine 的四个参数 ,其实就是开始点与结束点的x、y坐标。
备注:
0.1mV ~ 5mV(毫伏)是一个常见的微弱电压信号范围,广泛应用于各种精密测量和传感器信号处理领域。具体而言:
- 生物医学信号测量:
- 心电图(ECG)信号通常为0.1mV~5mV
- 脑电图(EEG)信号在0.5mV~5mV之间
- 肌电图(EMG)信号约为0.1mV~10mV
- 工业传感器输出:
- 热电偶在常温下输出约0.1mV/℃
- 应变片电桥输出通常在1mV~10mV/V范围内
- 某些振动传感器的输出信号在0.5mV~5mV之间
- 信号处理要求:
- 需要采用高精度放大器(如仪表放大器)进行信号调理
- 信号调理电路通常需要提供100~1000倍的增益
- 必须考虑噪声抑制(50Hz工频干扰等)
- 通常需要16位以上的ADC进行模数转换
- 典型应用电路:
- 三级放大结构:前置放大→滤波→主放大
- 采用屏蔽电缆和适当接地技术
- 常见使用仪表放大器如AD620、INA128等
该电压范围的信号处理需要特别注意:
- 输入阻抗应大于1MΩ
- CMRR(共模抑制比)需大于80dB
- 带宽根据具体应用从0.1Hz到10kHz不等
- 可能需要数字滤波等后处理技术
0.1mV ~ 5mV(毫伏)是一个常见的微弱电压信号范围,广泛应用于各种精密测量和传感器信号处理领域。具体而言:
- 生物医学信号测量:
- 心电图(ECG)信号通常为0.1mV~5mV
- 脑电图(EEG)信号在0.5mV~5mV之间
- 肌电图(EMG)信号约为0.1mV~10mV
- 工业传感器输出:
- 热电偶在常温下输出约0.1mV/℃
- 应变片电桥输出通常在1mV~10mV/V范围内
- 某些振动传感器的输出信号在0.5mV~5mV之间
- 信号处理要求:
- 需要采用高精度放大器(如仪表放大器)进行信号调理
- 信号调理电路通常需要提供100~1000倍的增益
- 必须考虑噪声抑制(50Hz工频干扰等)
- 通常需要16位以上的ADC进行模数转换
- 典型应用电路:
- 三级放大结构:前置放大→滤波→主放大
- 采用屏蔽电缆和适当接地技术
- 常见使用仪表放大器如AD620、INA128等
该电压范围的信号处理需要特别注意:
- 输入阻抗应大于1MΩ
- CMRR(共模抑制比)需大于80dB
- 带宽根据具体应用从0.1Hz到10kHz不等
- 可能需要数字滤波等后处理技术