news 2026/10/1 4:40:04

舌象识别毕设项目:ResNet50+PyQt5完整工程落地

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
舌象识别毕设项目:ResNet50+PyQt5完整工程落地

简介:本资源是一套面向计算机专业本科生的毕业设计实战项目,聚焦中医舌诊数字化场景,基于深度学习实现舌苔图像的识别、检测与分类鉴定,配套完整GUI交互界面,适用于毕设开题、中期答辩及终期交付全流程。资源共109个文件,包含26个核心Python源码(含模型训练、推理、UI逻辑)、6个预训练.pth模型文件、7张典型舌苔样本图、2个Qt Designer生成的.ui界面文件、2个Word格式论文文档(含摘要、方法、实验与答辩PPT提纲),以及日志文件和字体资源等,整体压缩包大小为105.43MB。已有196人下载学习,项目经导师指导并获99分高分评价,代码结构清晰、注释完整、环境配置简易,小白可直接运行调试,特别适合缺乏项目经验的应届生快速上手,亦可用于课程设计或AI视觉方向的实践拓展。

1. 这不是又一个“AI看舌头”的Demo:它真能跑通从数据采集→模型训练→GUI部署的完整闭环,且已通过高校毕设答辩(99分)

你搜“舌苔识别 毕业设计”,十有八九点开的是PPT截图+几行Keras代码+一张舌图标注框——运行不了、改不了、交不了。而这份资源,是某双非院校计算机专业大四学生真实交付的毕设项目,导师签字页、答辩记录表、查重报告、99分评分表全在压缩包里。它不玩概念,不做“伪端到端”:原始舌象图来自公开中医舌诊数据集(含正常/薄白苔/黄腻苔/灰黑苔四类),用ResNet50微调完成分类,TensorFlow 2.6 + Keras实现,训练日志(events.out.tfevents.*)全部保留可回溯;最关键的是——它带一个真正能双击运行、支持拖拽图片/实时摄像头识别、结果带置信度+中医术语解释的PyQt5 GUI,不是Jupyter Notebook里敲两行predict()就完事。适合两类人:一是正卡在毕设开题/中期/答辩前两周、急需可复现、可讲解、可演示的完整项目;二是想用真实医疗影像小场景练手深度学习工程落地的新手——它把数据清洗、类别平衡、模型导出、GUI集成、打包exe这些“玄学环节”全摊开给你看,连requirements.txt里每个包的版本都锁死了(比如tensorflow==2.6.0而非2.8+,避开了TF 2.7+对Windows GPU驱动的坑)。这不是玩具,是踩过所有坑后焊死的流水线。


2. 从舌象图到分类模型:数据准备、网络选型与训练脚本实操

2.1 数据集结构与预处理逻辑:为什么必须重采样+CLAHE增强?

项目采用自建+公开数据混合集,共1247张舌象图(非公开商用,仅限学术用途),按./data/train/normal/、./data/train/yellow_greasy/等子目录组织。但原始图存在严重问题:光照不均(手机拍摄反光)、分辨率混乱(320×240到1920×1080)、舌体占比差异大(有的占图80%,有的仅30%)。直接喂给CNN会崩。所以预处理脚本preprocess.py做了三件事:

  1. 统一尺寸+中心裁切:先缩放至1024×768,再用OpenCV找舌体最大连通域,以该区域中心为锚点裁出512×512正方形;
  2. CLAHE对比度受限自适应直方图均衡化:针对舌苔纹理弱、色差小的特点,参数设为clipLimit=2.0, tileGridSize=(8,8),比全局均衡更保细节;
  3. 类别重采样:normal类321张,gray_black仅187张,用SMOTE生成合成样本(注意:仅对训练集做,验证集保持原分布)。
# preprocess.py 关键片段 import cv2 import numpy as np from sklearn.preprocessing import StandardScaler from imblearn.over_sampling import SMOTE def clahe_enhance(img): clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) ycrcb = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb) ycrcb[:,:,0] = clahe.apply(ycrcb[:,:,0]) return cv2.cvtColor(ycrcb, cv2.COLOR_YCrCb2BGR) # 注意:SMOTE只作用于训练集特征矩阵X_train,标签y_train # X_train.shape = (n_samples, 512*512*3) → 向量化后SMOTE smote = SMOTE(random_state=42, sampling_strategy='not majority') X_train_res, y_train_res = smote.fit_resample(X_train.reshape(-1, 512*512*3), y_train) X_train_res = X_train_res.reshape(-1, 512, 512, 3) # 恢复图像维度

提示:CLAHE参数clipLimit不能设太高(>3.0),否则舌苔边缘会过曝成白边;tileGridSize太小(如2×2)会导致局部噪声放大。我一般先用cv2.imshow()预览10张图调参,再批量处理。

2.2 为什么选ResNet50微调而非YOLO或ViT?医疗小样本下的务实选择

项目没跟风ViT或Swin Transformer,原因很实在:你的GPU是GTX 1660(6GB显存),数据量不到1500张,ViT-base需要至少12GB显存+2000+样本才能收敛。ResNet50是平衡点——它在ImageNet上预训练的底层卷积核已学会提取边缘、纹理、颜色块,对舌苔这种强纹理特征天然友好;且include_top=False后接GlobalAveragePooling2D,参数量比全连接层少87%,训练快、不易过拟合。关键改动有三处:

  • 输入尺寸从224×224改为512×512(适配舌象图高分辨率需求);
  • 冻结前100层(base_model.trainable = True后手动设base_model.layers[i].trainable = False for i in range(100)),只微调顶层;
  • 分类头用Dense(128, activation='relu') → Dropout(0.5) → Dense(4, activation='softmax'),4对应四类舌苔。
# model_builder.py from tensorflow.keras.applications import ResNet50 from tensorflow.keras.layers import GlobalAveragePooling2D, Dense, Dropout from tensorflow.keras.models import Model base_model = ResNet50( weights='imagenet', include_top=False, input_shape=(512, 512, 3) # 关键!改输入尺寸 ) # 冻结前100层 for layer in base_model.layers[:100]: layer.trainable = False x = base_model.output x = GlobalAveragePooling2D()(x) x = Dense(128, activation='relu')(x) x = Dropout(0.5)(x) predictions = Dense(4, activation='softmax')(x) # 四分类 model = Model(inputs=base_model.input, outputs=predictions) model.compile( optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'] )

参数说明:Dropout(0.5)是血泪经验——不加的话,验证集准确率虚高(92%),但测试新舌图时抖动极大(置信度标准差>0.3);加了之后训练慢15%,但泛化稳定(测试集准确率86.3%,标准差<0.08)。

2.3 训练日志解析:如何用TensorBoard定位过拟合与学习率陷阱?

压缩包里的events.out.tfevents.*文件就是TensorBoard日志。别删!它们能告诉你模型到底哪里不对。启动命令很简单:

tensorboard --logdir=./logs --bind_all --port=6006

然后浏览器打开http://localhost:6006。重点看三块:

  • SCALARS → accuracy & val_accuracy:如果val_accuracy在第30轮后停滞甚至下降,而accuracy继续涨,就是过拟合(项目中出现过,解决方法是早停+增加Dropout);
  • IMAGES → sample_images:随机抽16张训练图,确认预处理后的舌图是否清晰、无裁切错误(曾发现23张图因CLAHE过度导致舌体发白,手动剔除);
  • GRAPHS → model_1:检查网络结构是否和代码一致(尤其确认ResNet50的input_shape确实是(512,512,3),不是默认的(224,224,3))。

注意:TensorBoard日志文件名中的时间戳(如1652188470)对应Unix时间戳,可用在线工具转为北京时间,对照训练脚本里的datetime.now().strftime("%Y%m%d_%H%M%S"),确保日志和代码版本匹配。


3. GUI界面开发:PyQt5如何承载深度学习模型并保证响应不卡顿?

3.1 主窗口架构:QMainWindow + 多线程推理,避免GUI冻结

PyQt5主线程负责UI渲染,模型推理若在主线程跑,点击“识别”按钮后整个界面会假死3~5秒(ResNet50在CPU上推理单图约2.8s)。解决方案是QThread+信号槽机制:

  • InferenceWorker类继承QObject,封装model.predict()调用;
  • 主窗口创建QThread实例,将InferenceWorkermoveToThread;
  • 点击按钮触发worker.start(),推理完成 emitresult_signal,主窗口槽函数接收并更新UI。
# gui/main_window.py from PyQt5.QtCore import QThread, QObject, pyqtSignal from PyQt5.QtWidgets import QMainWindow, QLabel, QPushButton class InferenceWorker(QObject): result_signal = pyqtSignal(dict) # 发送 {'label': 'yellow_greasy', 'confidence': 0.92} def __init__(self, model, img_array): super().__init__() self.model = model self.img_array = img_array def run(self): # 预处理:归一化+expand_dims img_norm = self.img_array.astype('float32') / 255.0 img_batch = np.expand_dims(img_norm, axis=0) pred = self.model.predict(img_batch)[0] label_idx = np.argmax(pred) labels = ['normal', 'thin_white', 'yellow_greasy', 'gray_black'] self.result_signal.emit({ 'label': labels[label_idx], 'confidence': float(pred[label_idx]) }) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.model = load_model('./models/best_model.h5') # 加载已训练模型 self.thread = QThread() self.worker = None def on_recognize_clicked(self): # 从QLabel获取当前显示的舌图(已转为numpy array) img_array = self.get_current_image_as_array() self.worker = InferenceWorker(self.model, img_array) self.worker.moveToThread(self.thread) self.worker.result_signal.connect(self.update_result_display) self.thread.started.connect(self.worker.run) self.thread.start()

关键细节:img_array.astype('float32') / 255.0必须在子线程内做,不能在主线程预处理好再传——因为PyQt5的QPixmap对象不能跨线程传递,必须传原始numpy数组。

3.2 中医术语解释模块:硬编码规则表 vs 轻量级知识图谱

GUI右侧的“中医解读”区域不是简单打印'yellow_greasy',而是映射到临床意义。项目采用轻量级规则表(非大模型),因为毕设不允许外网调用API。diagnosis_rules.py定义:

DIAGNOSIS_MAP = { 'normal': { 'term': '正常舌象', 'description': '舌质淡红,舌苔薄白,干湿适中,颗粒均匀。', 'clinical_significance': '脏腑功能协调,气血充盈,胃气充足。' }, 'yellow_greasy': { 'term': '黄腻苔', 'description': '舌苔色黄而黏腻,颗粒细腻致密,刮之难去。', 'clinical_significance': '湿热内蕴,多见于脾胃湿热、肝胆湿热证。' }, # ... 其他两类 }

UI中调用:

# 在update_result_display槽函数里 rule = DIAGNOSIS_MAP.get(result['label'], {}) self.label_term.setText(rule.get('term', '未知')) self.text_desc.setPlainText(rule.get('description', '')) self.text_significance.setPlainText(rule.get('clinical_significance', ''))

为什么不用JSON或数据库?因为毕设答辩要求“零依赖、一键运行”。硬编码Python字典,打包进exe后无需额外文件,且修改术语只需改.py文件,比改JSON更易调试。

3.3 打包为独立exe:PyInstaller的隐藏坑与必备参数

用pyinstaller --onefile --windowed --add-data "models;models" --add-data "data;data" main.py打包后,exe双击闪退?常见原因有三:

  1. TensorFlow DLL缺失:PyInstaller无法自动抓取TF的C++动态库,需手动指定路径:
    pyinstaller --onefile --windowed ^ --add-binary "C:\Users\XXX\AppData\Roaming\Python\Python38\site-packages\tensorflow\libtensorflow_cc.dll;." ^ --add-binary "C:\Users\XXX\AppData\Roaming\Python\Python38\site-packages\tensorflow\libtensorflow_framework.dll;." ^ --add-data "models;models" --add-data "data;data" main.py
  2. PyQt5插件路径错误:添加--add-binary指向PyQt5的plugins目录(如C:\Python38\Lib\site-packages\PyQt5\plugins);
  3. 模型加载路径硬编码:GUI代码里写load_model('./models/best_model.h5'),但exe解压后工作目录是临时文件夹,应改用:
    import sys import os def resource_path(relative_path): if getattr(sys, 'frozen', False): base_path = sys._MEIPASS else: base_path = os.path.abspath(".") return os.path.join(base_path, relative_path) model = load_model(resource_path('models/best_model.h5'))

血泪经验:打包前务必用--debug参数生成log,闪退时看log末尾报错——90%是DLL找不到或路径不对。我第一次打包失败,log里ImportError: DLL load failed while importing _pywrap_tensorflow_internal,折腾3小时才找到正确的DLL路径。


4. 避坑指南:99分毕设背后踩过的7个真实雷区

4.1 现象:训练时loss降得很快,但val_accuracy卡在60%不上升

原因:数据集划分时未按类别分层(stratify),导致验证集里gray_black类只有5张图,而训练集有180+张,模型根本没见过足够灰黑苔样本。
解决:用sklearn.model_selection.train_test_split时加stratify=y参数,确保各类在训练/验证集中比例一致。

4.2 现象:GUI中摄像头实时识别帧率极低(<2fps)

原因:OpenCV的cv2.VideoCapture(0)默认使用V4L2后端,在Windows上性能差;且每帧都做CLAHE+resize,CPU扛不住。
解决:① 改用DirectShow后端:cap = cv2.VideoCapture(0, cv2.CAP_DSHOW);② 将CLAHE移至模型输入预处理(即只对送入模型的单帧做),摄像头预览用原始图;③ 降低预览分辨率:cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640); cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)。

4.3 现象:打包exe后,点击“识别”按钮无反应,控制台无报错

原因:PyInstaller未打包h5py依赖的zlib.dll和hdf5.dll,导致load_model()静默失败。
解决:在spec文件中添加binaries=[('path/to/zlib.dll', '.'), ('path/to/hdf5.dll', '.')],或用--add-binary命令行参数显式包含。

4.4 现象:论文里写的“准确率92.3%”,但自己跑测试集只有84.1%

原因:原始论文用的是model.evaluate()在训练集上测的(错误!),正确做法是用独立测试集(test_data)且关闭dropout(model.evaluate(test_data, verbose=0))。项目已修正,测试集准确率86.3%±0.7%(5次随机划分均值)。
解决:严格区分train/val/test三集合,测试集绝对不可参与任何训练过程(包括数据增强参数调整)。

4.5 现象:舌图拖入GUI后显示变形(拉伸成宽屏)

原因:QLabel.setPixmap()未设置Qt.KeepAspectRatio,图片被强制填满控件。
解决:在设置pixmap后加label.setScaledContents(False),并用pixmap.scaled(width, height, Qt.KeepAspectRatio)预缩放。


5. 模型导出与跨平台部署:如何让导师电脑上也顺利运行?

5.1 从HDF5到SavedModel:兼容性升级的关键一步

项目原始模型保存为best_model.h5(Keras原生格式),但它在不同TensorFlow版本间兼容性差(TF 2.6保存的h5在TF 2.9可能load失败)。毕设答辩时导师电脑装的是TF 2.8,为保万无一失,必须导出为SavedModel格式:

# export_model.py import tensorflow as tf from model_builder import build_model # 重建模型结构 model = build_model() # 注意:不加载权重,只建结构 model.load_weights('./models/best_model.h5') # 单独加载权重 # 导出为SavedModel tf.saved_model.save( model, './models/saved_model', # 输出目录 signatures={ 'serving_default': model.call.get_concrete_function( tf.TensorSpec(shape=[None, 512, 512, 3], dtype=tf.float32, name='input_image') ) } )

导出后,./models/saved_model目录下会有assets/、variables/、saved_model.pb三个部分。GUI代码中加载方式改为:

# 替换原来的 load_model() model = tf.keras.models.load_model('./models/saved_model')

优势:SavedModel是TensorFlow官方推荐格式,跨版本兼容性极强;且支持TensorRT加速(虽毕设未用,但留了接口)。

5.2 环境隔离方案:conda vs venv,为什么最终选venv?

项目最初用conda创建环境(conda create -n tongue python=3.8),但打包时发现conda环境路径硬编码在.pth文件里,PyInstaller无法正确解析。改用venv后问题消失:

# 推荐流程 python -m venv env_tongue env_tongue\Scripts\activate.bat # Windows pip install -r requirements.txt # 确保requirements.txt里明确写出版本号 tensorflow==2.6.0 keras==2.6.0 pyqt5==5.15.6 opencv-python==4.5.5.64

requirements.txt内容必须锁定版本,尤其tensorflow和pyqt5——pyqt5>=5.15在某些Windows系统上会安装6.x,导致QThread信号槽失效(PyQt6 API变更)。

5.3 导师电脑无GPU?CPU推理优化三板斧

导师电脑大概率是核显(Intel UHD Graphics),连CUDA都不装。此时CPU推理速度是瓶颈。项目已内置优化:

优化项实现方式效果
模型量化使用tf.lite.TFLiteConverter转为int8模型推理速度提升2.3倍,精度损失<0.5%(测试集85.8%→85.4%)
OpenMP线程数限制os.environ['TF_NUM_INTEROP_THREADS'] = '2'
os.environ['TF_NUM_INTRAOP_THREADS'] = '4'
防止多线程争抢,CPU占用率从100%降至65%,风扇不狂转
预热推理GUI启动时自动执行一次model.predict(np.zeros((1,512,512,3)))首次识别延迟从3.2s降至1.1s(避免JIT编译耗时)
# gui/app.py 开头 import os os.environ['TF_NUM_INTEROP_THREADS'] = '2' os.environ['TF_NUM_INTRAOP_THREADS'] = '4' # 启动时预热 dummy_input = np.zeros((1, 512, 512, 3), dtype='float32') _ = model.predict(dummy_input) # 丢弃结果,只为触发编译

从那以后我每次给导师演示前,都强制走一遍“预热→拍舌图→识别→看中医解读”全流程,确保答辩现场不翻车。哪怕多花10秒,也比答辩时卡住30秒强——那30秒,够你重写整个毕设了。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 4:38:44

帝国CMS二次开发:Word文档一键解析发布组件实现详解

做帝国CMS二次开发这几年&#xff0c;最让我头疼的需求不是采集、不是模板标签&#xff0c;而是编辑部门天天喊的“我在Word里排版好了&#xff0c;能不能直接传上去一键发布”。帝国的后台编辑器虽然能用&#xff0c;但Word文档往编辑器里一粘贴&#xff0c;格式全乱、图片丢了…

作者头像 李华
网站建设 2026/10/1 4:38:20

吃透C++模板核心:非类型模板参数与分离编译实战解析

老实说&#xff0c;我在把 C 泛型编程从“会用模板写容器”推进到“吃透非类型模板参数和分离编译”这个阶段时&#xff0c;是被两个编译错误逼出来的。当时我接手一个工具库&#xff0c;头文件里只写了函数模板的声明&#xff0c;定义放在 .cpp 文件里&#xff0c;单文件编译全…

作者头像 李华
网站建设 2026/10/1 4:38:17

单细胞测序10X:从NCBI SRA到Cell Ranger全流程

单细胞测序这两年最不缺的就是公开数据&#xff0c;GEO 上随便检索一个关键词&#xff0c;动辄就是几十个 GSE 数据集。但真正让刚入门的人卡住的&#xff0c;往往不是分析本身&#xff0c;而是第一步&#xff1a;把 NCBI 上的 10X 原始数据拿下来&#xff0c;整理成 Cell Rang…

作者头像 李华
网站建设 2026/10/1 4:37:05

iOS 上跑 Windows 程序:Wine 兼容层 Madeira 实战指南

1. 项目缘起&#xff1a;为什么要在 iOS 上折腾 Wine 兼容层第一次听到“Madeira”这个名字&#xff0c;很多人会以为是葡萄牙那座盛产葡萄酒的海岛&#xff0c;但在我们这行里&#xff0c;它指向的是另一件事——把 Windows 应用搬到 iOS 设备上跑起来的那套兼容方案。核心思路…

作者头像 李华
网站建设 2026/10/1 4:36:58

YOLOv5实现施工人员反光服与安全帽联合检测

简介&#xff1a;本资源是一套面向AI安全监控领域的YOLOv5目标检测实战数据集与完整训练工程&#xff0c;专为计算机视觉初学者、工地智能监管系统开发者及工业安全算法工程师设计&#xff0c;解决施工场景中反光服、安全帽等关键防护装备的自动识别与佩戴合规性检测问题。压缩…

作者头像 李华
网站建设 2026/10/1 4:36:24

大模型原生广告指南:GEO如何让品牌成为答案

1. 从“抢位置”到“进答案”&#xff1a;大模型原生广告到底是不是新物种聊“大模型原生广告”之前&#xff0c;先讲一个最近观察到的现象&#xff1a;广告行业过去二十年在干一件事——在用户的阅读路径上占领视觉焦点。门户时代是横幅&#xff0c;搜索时代是关键词竞价&…

作者头像 李华