ECG信号滤波及计算心率

本文基于自己制作的5导联ECG信号采集器采到的心搏信号做处理。

1,心搏信号存储格式为12位一个信号,格式和MIT-BIH的差不多,只是中间8位顺序取值,不用调换大小尾顺序;

2,信号顺序为 Ⅰ导联信号1,Ⅱ导联信号1,Ⅲ导联信号1,Ⅰ导联信号2,Ⅱ导联信号2,Ⅲ导联信号2,...,Ⅰ导联信号n,Ⅱ导联信号n,Ⅲ导联信号n;

3,本文以Ⅱ导联信号作为处理对象;

4,考虑应用服务器压力抽取15秒中间段数据作为心率计算依据。可扩展为全段数据分析、每小时数据分析等,看实际需要。

import math
import numpy as np
import pywt
import argparse
import matplotlib.pyplot as plt
from scipy import signal as sig

## jerry 20211129
## 对ECG采集器采到的数据滤波,定位QRS波,计算心率
## 命令示例:python ./ecg_basics.py -e "D:/code/python/ECG/ld_data/test/163.dat"

def draw(data):
    plt.figure(figsize=(16, 2))
    plt.plot(data)
    plt.show()

# 滤-肌电干扰、工频干扰
def filter_01(data):
    coeffs = pywt.wavedec(data=data, wavelet='db5', level=9)
    cA9, cD9, cD8, cD7, cD6, cD5, cD4, cD3, cD2, cD1 = coeffs
    threshold = (np.median(np.abs(cD1)) / 0.6745) * (np.sqrt(2 * np.log(len(cD1))))
    cD1.fill(0)
    cD2.fill(0)
    for i in range(1, len(coeffs) - 2):
        coeffs[i] = pywt.threshold(coeffs[i], threshold)
    rdata = pywt.waverec(coeffs=coeffs, wavelet='db5')
    return rdata

# 滤-基线漂移
def filter_02(data, fs=500):
    data = np.array(data)
    winsize = int(round(0.2*fs))
    if winsize % 2 == 0:
        winsize += 1
    baseline_estimate = sig.medfilt(data, kernel_size=winsize)
    winsize = int(round(0.6*fs))
    if winsize % 2 == 0:
        winsize += 1
    baseline_estimate = sig.medfilt(baseline_estimate, kernel_size=winsize)
    ecg_blr = data - baseline_estimate
    return ecg_blr.tolist()

# QRS波定位
def getQRS(data):
    count_q = []
    count_r = []
    count_s = []

    T = 256
    N = 24
    rE = T // 3
    E = T // 7
    x = data[:]
    x = x.astype("float")
    x = (x - np.mean(x)) / np.std(x)

    # plt.figure(num=None, figsize=(16, 2), dpi=80)

    x1 = sig.lfilter([1, 0, 0, 0, 0, 0, -2, 0, 0, 0, 0, 0, 1], [1, -2, 1], x)
    x2 = sig.lfilter(
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [1, 1],
        x1)

    # plt.plot(x2[0:3000])

    x3 = np.zeros(x.shape)
    for i in range(2, len(x2) - 2):
        x3[i] = (-1 * x2[i - 2] - 2 * x2[i - 1] + 2 * x2[i + 1] + x2[i + 2]) / (8 * T)

    x4 = x3 * x3

    x5 = np.zeros(x.shape)
    for i in range(N, len(x4) - N):
        for j in range(N):
            x5[i] += x4[i - j]
    x5 = x5 / N

    peaki = x5[0]
    spki = 0
    npki = 0
    c = 0
    peak = [0]
    threshold1 = spki
    pk = []
    for i in range(1, len(x5)):
        if x5[i] > peaki:
            peaki = x5[i]

        npki = ((npki * (i - 1)) + x5[i]) / i
        spki = ((spki * (i - 1)) + x5[i]) / i
        spki = 0.875 * spki + 0.125 * peaki
        npki = 0.875 * npki + 0.125 * peaki

        threshold1 = npki + 0.25 * (spki - npki)
        threshold2 = 0.5 * threshold1

        if (x5[i] >= threshold2):

            if (peak[-1] + N < i):
                peak.append(i)
                pk.append(x5[i])

    p = np.zeros(len(x5))
    rPeak = []
    Q = np.zeros(2)
    S = np.zeros(2)
    THR = 50
    for i in peak:
        if (i == 0 or i < 2 * rE):
            continue
        p[i] = 1

        ind = np.argmax(x2[i - rE:i + rE])
        maxIndexR = (ind + i - rE)
        rPeak.append(maxIndexR)
        # plt.plot(maxIndexR, x2[maxIndexR], 'ro', markersize=12)
        count_r.append(maxIndexR)
        prevDiffQ = 0
        prevDiffS = 0
        #    FIND THE Q POINT
        for i in range(1, THR):

            Q[0] = x2[maxIndexR - i]
            Q[1] = x2[maxIndexR - (i + 1)]

            diffQ = Q[0] - Q[1]

            if (diffQ < prevDiffQ):
                minIndexQ = maxIndexR - i
                break
            prevDiffQ = diffQ / 5

        # plt.plot(minIndexQ, x2[minIndexQ], 'bo', markersize=6)
        count_q.append(minIndexQ)

        #    FIND THE S POINT
        for i in range(1, THR):

            S[0] = x2[maxIndexR + i]
            S[1] = x2[maxIndexR + (i + 1)]

            diffS = S[0] - S[1]

            if (diffS < prevDiffS):
                minIndexS = maxIndexR + i
                break
            prevDiffS = diffS / 5

        # plt.plot(minIndexS, x2[minIndexS], 'go', markersize=6)
        count_s.append(minIndexS)
    rPeak = np.unique(rPeak)

    # plt.xlabel('time')
    # plt.show()
    return count_q,count_r,count_s

# 按12位取数据
def read_uint12(data_chunk):
    data = np.frombuffer(data_chunk, dtype=np.uint8)
    fst_uint8, mid_uint8, lst_uint8 = np.reshape(data, (data.shape[0] // 3, 3)).astype(np.uint16).T
    fst_uint12 = (fst_uint8 << 4) + (mid_uint8 >> 4)
    snd_uint12 = ((mid_uint8 % 16) << 8) + lst_uint8
    return np.reshape(np.concatenate((fst_uint12[:, None], snd_uint12[:, None]), axis=1), 2 * fst_uint12.shape[0])

# 取中段15秒数据
def getdata_10(data,sampling_rate):
    time_s = 15
    rdata = data
    signal_len = len(data)
    signal_len_10 = sampling_rate * time_s
    if signal_len < signal_len_10:
        pass
    else:
        offset = math.floor((signal_len - signal_len_10)/2)
        rdata = data[offset:signal_len_10+offset]
    return rdata

# 获取平均心率
def get_heart_rate_mean(ecg_data_path):
    # 单个信号位长度
    bit_len = 12
    # 采样率 320/s
    sampling_rate = 320

    f = open(ecg_data_path, 'rb')
    signal_bit = f.read()
    f.close()
    #8位 to 12位
    signal = read_uint12(signal_bit)
    # 分开3个通道
    signal_abc = np.array(signal).reshape(math.floor(len(signal)/3), 3)
    # 2号通道取10秒
    signal_10 = getdata_10(signal_abc[:,1],sampling_rate)
    # 拆分导联,滤肌电干扰、工频干扰
    f0_signal_b = filter_01(signal_10)
    # 滤基线漂移
    f1_signal_b = filter_02(f0_signal_b,sampling_rate)
    f1_signal_b = np.array(f1_signal_b)
    # 定位QRS
    count_q,count_r,count_s = getQRS(f1_signal_b)
    # 计算心率
    seconds = len(f1_signal_b)/sampling_rate
    heart_rate_mean = len(count_r)*60/seconds
    print(heart_rate_mean)
    # draw(f1_signal_b)
    return heart_rate_mean

##
if __name__ == '__main__':
    # ecg_data_path = "D:/code/python/ECG/ld_data/test/163.dat"
    ap = argparse.ArgumentParser()
    ap.add_argument("-e", "--ecg", required=True, help="path to the input ecg data")
    args = vars(ap.parse_args())
    ecg_data_path = args['ecg']
    print(ecg_data_path)
    get_heart_rate_mean(ecg_data_path)

  • 4
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

外码斯迪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值