
简介这是一份基于Python开发的脉象识别系统毕业设计源码主要面向计算机相关专业学生适合用于毕业设计、课程设计或期末大作业。系统覆盖数据预处理、模型训练与预测、Web管理端等模块基于Django框架实现项目结构完整、界面简洁代码附有详细注释即使新手也能轻松读懂并做二次开发。压缩包共61个文件以47个Python源码文件为核心另有8个CSV样本数据文件、Markdown说明文档及H5模型权重文件整体仅1.26MB轻量且便于携带。项目已经过严格调试可直接在Django环境下运行配套部署教程与数据库脚本可帮助使用者快速跑通整个脉象识别流程。资源还内置API接口、权限管理、日志系统、异常处理等工程化细节读者可从中借鉴完整的Web项目组织方式其中CSV数据导入、模型加载与预测、Token鉴权等实现也能直接复用到其他课题。目前已有202人学习下载适合需要完成相似毕业设计或理解Python Web项目架构的开发者。1. 脉象识别系统到底在做什么不是玄学是信号处理中医把脉靠手指想练出可靠的指感需要多年临床积累。脉象识别系统要做的就是把这个过程变成可量化的信号处理流程传感器采脉搏Python做滤波和特征提取分类模型输出脉象类别。基于Python开发的脉象识别系统是毕业设计里典型的软硬结合题目——需求明确、工作量好拆解还能体现数据分析与人工智能两个方向的能力。它适合想完整走一遍“采集→算法→界面”的同学。以我调这类系统的经验项目能不能站住脚七成取决于信号质量剩下三成才是模型选型。2. 脉搏信号从哪来三种采集方案与Python预处理链路2.1 三种采集方案怎么选脉象识别的第一步是拿到干净的脉搏波这一步直接决定后面所有工作的上限。常见的做法有三种成本和工作量差别很大。第一种是压阻式脉搏传感器典型的是HK-2000A/B这类。探头贴在桡动脉位置搏动压力让电阻变化再经过运放和ADC变成数字信号。这种方案信号质量最好重搏波细节保留完整但需要自己搭放大电路对焊接和硬件调试有要求适合有嵌入式基础的人。第二种是光电式容积脉搏波PPG用MAX30102这类芯片。LED光照透皮肤血管容积随心跳变化影响光吸收量芯片把光信号转成数字量通过I2C接口接ESP32或树莓派Python从串口读数据。整套硬件成本几十元焊接难度低是毕业设计最常见的做法。缺点是容易受手指抖动和环境光干扰后续滤波要多花功夫。第三种是直接用公开数据集比如PhysioNet上公开的脉搏波数据库。完全不碰硬件专门做算法侧适合时间紧或者实验条件受限的情况。但要注意很多公开数据只适合算心率没有中医脉象标注不一定能直接用来做弦脉、滑脉这类分类。如果目标是做出“弦脉/滑脉/迟脉/数脉”几类分类我建议方案二和方案三组合着来先用公开数据集把算法流程跑通再用自采数据做实测两头都有数据支撑。采样率方面脉搏信号的能量绝大部分集中在0.5到10Hz采样率设250Hz就够用。我习惯用500Hz因为后面要做峰值检测和周期切分高一点能减少时间量化误差。采样率低于100Hz的话重搏波的位置基本测不准脉象分类里非常重要的一个特征就丢了。2.2 用Python写带通滤波与去基线漂移原始信号从串口读进来之后要先做两件事带通滤波和去基线漂移。原始脉搏信号里混着呼吸引起的基线漂移频率通常0.2到0.4Hz、肌肉抖动噪声和50Hz工频干扰。带通滤波取下界0.5Hz、上界10Hz是比较通用的参数。下界0.5Hz能把大部分基线漂移压掉上界10Hz保留主波和重搏波的尖峰又不会把高频噪声放进来。import numpy as np from scipy.signal import butter, filtfilt def bandpass_filter(data: np.ndarray, fs: float, low: float 0.5, high: float 10.0, order: int 4) - np.ndarray: nyquist 0.5 * fs b, a butter(order, [low / nyquist, high / nyquist], btypeband) return filtfilt(b, a, data) fs 500.0 raw np.loadtxt(pulse_raw.csv, delimiter,) filtered bandpass_filter(raw, fsfs, low0.5, high10.0, order4)这里必须用filtfilt而不是lfilter。lfilter是因果滤波输出波形会整体偏移一段肉眼不容易发现但后面的峰值检测会把主波峰位置算偏整个周期切分就全错了。filtfilt做零相位滤波峰的位置保留在原处这是预处理里最容易忽视的一个坑。order取4就够。order太高会出现数值震荡低通部分还可能把重搏波的波形细节磨平。调参数的时候把filtered波形画出来看主波峰明显、基线平直、两个主波之间没有多余尖峰这组参数就能用。滤波之后再做一次基线校正。用中值滤波提取缓慢变化的基线然后从原信号里减掉。窗口长度取0.5到1秒之间太短会把主波当成漂移吃掉太长又压不掉呼吸引起的起伏。from scipy.ndimage import median_filter def remove_baseline(signal: np.ndarray, fs: float, window_s: float 0.8) - np.ndarray: window_len int(fs * window_s) baseline median_filter(signal, sizewindow_len) return signal - baseline中值滤波窗口必须大于一个脉搏周期。0.8秒窗口对应75bpm的心率周期如果测试对象是儿童或者运动后的快心率要把窗口改成0.5秒否则主波幅度会被吃掉一大截。2.3 峰值检测与周期切分预处理干净后用find_peaks做主波峰检测。这个函数是scipy里现成的比手动设阈值可靠得多。from scipy.signal import find_peaks def detect_peaks(signal: np.ndarray, fs: float): min_distance int(0.4 * fs) # 0.4s对应150bpm再快不作为正常脉搏 peaks, props find_peaks(signal, distancemin_distance, prominence0.1 * np.max(signal)) return peaks, propsdistance是最小峰间距设为0.4秒是为了防止把重搏波误检成主波。prominence设为最大幅值的10%小于这个凸起的噪声尖峰直接忽略。这两个参数是峰值检测最容易翻车的地方prominence太大漏检、太小多检。调试时把检测结果画出来叠加在原波形上看比看数值直观得多。切分周期时我一般取每个主波峰前0.15秒到后0.35秒作为一段保证上升支起点和降支尾部都被包含进去。每个周期算一组特征一个人采60秒数据就有几十个周期对应几十个样本样本量一下就上来了。这一步做完数据预处理链路就完整了后面可以安心做特征提取。3. 把波形变成特征向量时域特征与频域特征的计算要点3.1 时域特征怎么对应到脉象脉象识别的胜负手是特征设计不是模型。拿到干净的周期波形后先看时域。一个标准的脉搏波周期有四个关键位置主波峰、降中峡、重搏波峰、周期起点。常用时域特征包括主波幅值、上升支最大斜率、降中峡幅值、重搏波幅值、脉搏周期和脉率。这些特征与脉象类别的对应关系是有规律可循的特征计算方式常见脉象倾向主波幅值周期内最大值减基线浮脉偏高沉脉偏低上升支最大斜率起点到主波峰的一阶差分最大值弦脉偏大滑脉偏缓降中峡幅值主波峰后第一个局部极小值弦脉明显滑脉不明显重搏波幅值降中峡后的局部极大值减降中峡滑脉明显涩脉弱或消失脉率60除以平均周期迟脉小于60数脉大于90主波宽度主波峰半幅处宽度滑脉偏宽弦脉偏窄弦脉的特点在波形上很直观血管张力大主波高耸、上升支陡、重搏波低平。滑脉则像珠子滚过上升支平滑、主波宽大、重搏波偏高。这些特征从波形上肉眼能分辨但只有量化成数字模型才有依据。在做特征分析时图像验证这一步不能省。把特征矩阵存成csv用matplotlib画散点图或者散点图矩阵看不同脉象的样本点在特征空间里是不是能分开。如果两类样本完全重叠说明特征没选对。这个用pandas加matplotlib就能搞定也是日常python数据分析与可视化里最常见的操作。3.2 频域特征FFT功率谱与HHT时域特征描述波形形态频域特征描述节律。单个周期做FFT意义不大一般对整段信号做功率谱再统计频带能量占比。常见做法是取0.5到10Hz的功率谱分成低频段和高频段。有研究认为“弦”对应高频能量占比偏高“滑”对应低频能量更集中。毕设里不必深究机理把它当作一个特征输入模型效果好就用不好就舍弃。FFT用scipy的welch函数求功率谱比直接np.fft更平滑参数也好控制。HHT希尔伯特-黄变换是锦上添花。它把信号做经验模态分解得到若干个固有模态函数再做瞬时频率分析。好处是适合非平稳信号坏处是计算量大、分解层数需要反复调。毕设时间不够时不用强上FFT加时域特征已经足够撑起一个完整系统。3.3 特征计算代码把波形变成一行特征向量下面这个函数输入一个单周期波形输出一个字典包含六个时域特征。把所有周期遍历一遍就得到完整的特征矩阵。def extract_features_from_cycle(cycle: np.ndarray, fs: float) - dict: cycle np.asarray(cycle, dtypefloat) # 主波峰与降中峡 peak_idx int(np.argmax(cycle)) main_amp cycle[peak_idx] # 上升支最大斜率起点到主波峰之间的一阶差分最大值 rising cycle[:peak_idx 1] rising_slope np.max(np.diff(rising)) * fs # 降中峡主波峰后第一个极小值 tail cycle[peak_idx:] trough_idx peak_idx int(np.argmin(tail)) trough_amp cycle[trough_idx] # 重搏波幅值降中峡之后的局部极大值减降中峡 dicrotic_seg cycle[trough_idx:] dicrotic_amp float(np.max(dicrotic_seg)) - trough_amp # 主波宽度半幅处宽度取上升支和下降支半幅点距离 half main_amp / 2.0 rising_cross np.argmax(rising half) falling_cross np.argmax(cycle[peak_idx:] half) peak_idx main_width (falling_cross - rising_cross) / fs return { main_amp: main_amp, rising_slope: rising_slope, trough_amp: trough_amp, dicrotic_amp: dicrotic_amp, main_width: main_width, pr: 60.0 / (len(cycle) / fs) }几处参数说明。rising_slope乘fs是因为np.diff得到的是每个采样点的差值要除以采样间隔才是真实斜率乘fs等价于除以1/fs。main_width用半幅交叉计算比数过零点更贴合波形形态。pr由周期长度推算出瞬时脉率用于迟脉和数脉的区分。特征拼好后要顺手做一次相关性检查。用pandas的corr算特征矩阵的相关系数高度相关的特征只保留一个避免模型把重复信息当成独立维度。这个步骤很简单但对后面模型训练的帮助很大。4. 分类模型从选型到训练小样本下的Python实现4.1 数据标注与数据集划分脉象标注是整个系统里最主观的一环。如果自己采数据建议找有临床经验的中医师帮忙标或者参照教材的脉象图谱严格判定。每类至少采集5个人以上、每人30个有效周期否则交叉验证的结果波动会非常大。划分数据集时有一条容易忽视的规则按人划分不按周期随机划分。同一个人的几十个周期高度相似如果随机划分同一人的数据会同时出现在训练集和测试集里测试准确率虚高答辩现场换人就崩。要把每个人作为一个整体按8:2比例分到训练集和测试集。注意按人划分之后如果训练集准确率骤降说明特征本身区分度不够需要回头补特征或换特征而不是换一个更复杂的模型硬撑。4.2 模型选型对比小样本场景下SVM和随机森林是首选。它们对几十到几百个样本的处理能力比神经网络强得多调参成本也可控。模型适合场景参数调试成本毕设推荐度SVMRBF核几十到几百样本、特征维度中等主要调C和gamma高随机森林特征较多、样本几百调树数量和深度高KNN特征空间简单调K和距离度量中多层感知机样本上千网络结构、正则化低我的习惯是先用SVM做baseline。RBF核能处理非线性可分配合StandardScaler做标准化效果一般都不差。随机森林作为对照模型两个一起跑看哪个在测试集上更稳定、方差更小。神经网络在这个数据量级上表现通常不如传统模型不建议优先尝试。4.3 训练代码与评估指标from sklearn.svm import SVC from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split, cross_val_score from sklearn.metrics import classification_report, confusion_matrix X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, stratifyy, random_state42 ) pipeline make_pipeline( StandardScaler(), SVC(kernelrbf, C10, gammascale, class_weightbalanced) ) pipeline.fit(X_train, y_train) y_pred pipeline.predict(X_test) print(classification_report(y_test, y_pred)) print(confusion_matrix(y_test, y_pred))参数说明。C10表示误分类惩罚较大适合脉象类别边界有交叉的数据gammascale让核宽度根据特征方差自动确定比写死一个gamma值省事也少一个要调的参数class_weightbalanced会在脉象类别数量不均衡时自动加权避免样本少的类别被忽略。SVM效果不好时再跑随机森林做对比from sklearn.ensemble import RandomForestClassifier rf RandomForestClassifier(n_estimators200, max_depth8, random_state42) rf.fit(X_train, y_train) print(rf.score(X_test, y_test)) print(sorted(zip(feature_names, rf.feature_importances_), keylambda x: -x[1])[:5])n_estimators200够用再增大收益很小只会拖慢训练max_depth8是为了防止小样本上深度过拟合。feature_importances_直接输出特征重要度排名写论文时可以把排名靠前的特征放进实验章节说明哪些特征对分类贡献最大这一段在答辩时很加分。5. 脉象识别系统必踩的5个坑现象、原因与排查5.1 采出来的波形全是毛刺主波都看不出来现象串口读回来的数据画出来锯齿感明显主波峰和重搏波被噪声淹没滤波之后波形仍然乱。原因最常见的是硬件共地问题。传感器和ADC没有共地或者杜邦线太长引入工频干扰。其次是手指没有放稳肌肉微抖动被传感器放大。解决先检查共地再把传感器连接线换成屏蔽线采样时手指固定在腕托上。如果波形仍然抖动把滤波下界从0.5Hz提高到1Hz牺牲一点基线性能换信噪比。5.2 训练集准确率接近满分测试集只有一半现象classification_report显示训练集表现漂亮测试集落到五成手动重新测试同一批数据又时好时坏。原因样本总量太小加特征维度偏高模型把训练数据背下来了。另一个常见原因是数据集划分方式不对按周期随机划分导致同一人的相似样本泄漏到测试集。解决先改成按人划分再看特征相关性。如果特征之间高度冗余降维后再训练。最后用交叉验证而不是单次划分来评估交叉验证均值低说明模型本身就不可靠需要回去补数据或重新设计特征。5.3 换一个人测试识别率立刻崩盘现象同事甲的测试集上准确率很高换同事乙现场采的新数据预测结果几乎随机。原因脉象信号受佩戴位置、按压压力、个体血管弹性的影响非常大。如果训练数据只采了同一批人模型学到的是“这几个人的模式”而不是“这类脉象的模式”这是脉象识别里最容易翻车的点。解决训练数据至少覆盖5个以上不同的人采集时固定传感器位置和按压力度。特征归一化时按个体做一次把幅值差异标准化让模型关注波形形态而不是绝对大小。5.4 峰值检测把噪声当主波周期切分一团乱现象find_peaks检测结果画出来有些“峰”明显不是主波周期时长忽长忽短特征计算全部错乱。原因distance和prominence参数不合适。distance太小会把重搏波当成主波prominence太小会把毛刺当成峰。解决先把滤波后的信号可视化量一下两个主波之间实际的最短间隔把distance设为间隔的一半左右。prominence设置为最大幅值的5%到10%。调试时把检测点叠加在波形上看不要盲调参数。5.5 界面实时识别卡顿拖一下进度条就掉帧现象做成图形界面后每读一帧数据都要走一遍滤波、特征提取和模型预测界面卡成幻灯片。原因把耗时计算放在主线程里同步执行。滤波加特征计算加推理在Python里串行跑足够让界面明显卡顿。解决用生产者消费者模式串口读取放一个线程处理和预测放另一个线程界面只负责刷新结果。数据处理按滑窗推进每次只对最新的一个周期做计算而不是重算整段信号。模型用joblib导出的SVM或者转成ONNX推理时间能压到毫秒级。6. 验证模型与毕设收尾混淆矩阵、模型导出和演示技巧验证模型时不要只报单次划分的准确率用5折交叉验证看均值和方差均值反映整体水平方差反映对不同批次人的敏感度。scores cross_val_score(pipeline, X, y, cv5, scoringaccuracy) print(scores.mean(), scores.std())方差太大就回去补样本量或做个体归一化。接着把训练好的pipeline导出成joblib文件界面程序启动时直接加载不用每次重新训练。答辩演示时把预处理前后的波形对比图、特征散点图、混淆矩阵三张图放进论文实验章节这三张图分别对应“信号是真的干净”“特征真的可分”“模型真的有效”比堆一张大而全的系统架构图有说服力得多。毕设时间安排上我的建议是前两周全花在信号采集和预处理上模型部分留一周就够。信号链路一旦走通后面就是特征和模型的组合实验几天能出结果。如果一上来就调模型最后大概率在信号质量上翻车。环境调试也要提前做把pycharm配置python环境这一步搞定用断点调试看中间变量比每次print一条波形快太多。我第一次做的时候图省事只采了一个人的数据结果换人测试完全崩掉后面重新采集补数据花了两周。脉象识别系统的瓶颈从来不在模型而在数据来源和特征设计上。把这两件事做扎实系统就成功了大半。希望帮到你。本文还有配套的精品资源点击获取