资讯动态

舌苔图像识别毕业设计:ResNet18+PyQt5全栈实现

发布时间:2026/10/15 1:58:21 来源:尧图企业网站定制
简介本资源是一套面向计算机专业本科生的高分毕业设计实战项目聚焦中医舌诊数字化场景提供基于深度学习的舌苔识别、检测与鉴定全流程解决方案。适用于正在开展毕设、课程设计或期末大作业的学生尤其适合缺乏医学图像项目经验但具备Python基础的学习者小白亦可快速上手部署与调试。压缩包共109个文件含26个核心Python源码涵盖数据预处理、YOLOv5模型训练与推理、GUI界面逻辑、6个预训练.pth模型权重、7张典型舌苔样本图及2个.ui界面文件辅以论文文档、JSON配置与字体资源整体105.44MB结构清晰、模块解耦。已有132人下载学习资源经导师指导并获99分高分评审代码完整可运行附带TensorBoard日志文件events.out.tfevents便于复现训练过程与效果分析是兼具学术规范性与工程落地性的优质教学实践素材。1. 舌苔识别不是玄学一个跑通即用的毕业设计级深度学习系统99分答辩现场实测可用你手头正赶着计算机专业大四毕设 deadline导师刚甩来一句“得有实际模型、能跑通、带界面、可演示”别慌——这不是又一个“GitHub抄来改个 import 就交差”的半成品而是一个从数据预处理、CNN主干训练、TensorBoard日志监控、到PyQt5 GUI封装全链路闭环的舌苔识别系统。它不是demo是真实拿99分答辩通过的高分毕业设计输入一张手机拍的舌苔图3秒内输出“薄白苔/黄腻苔/厚黄苔/剥落苔”四类判定置信度条热力图可视化连答辩PPT里的模型结构图、混淆矩阵、ROC曲线都已配好。适合零基础但肯动手的学生不需要调参直觉不需要自己爬图连requirements.txt里每个包的版本都锁死了包括torch 1.12.1 torchvision 0.13.1 PyQt5 5.15.9连Windows下pip install失败的坑都提前填好了。如果你正在为“毕设没东西可交”、“模型训不出结果”、“GUI界面卡死打不开”、“论文图表不会画”发愁——这项目就是为你写的后悔药。2. 模型选型与训练逻辑为什么用ResNet18而不是ViT四个关键决策点拆解2.1 舌苔图像特性决定模型必须“轻量鲁棒”舌苔识别和通用图像分类有本质区别尺度敏感但纹理主导舌体在图中占比常为30%~70%边缘模糊但苔质颗粒感、裂纹、湿润度才是判别核心光照干扰极强手机拍摄无专业光源白平衡漂移、反光斑点、阴影遮挡高频出现类别极度不平衡临床数据中“薄白苔”占65%以上“剥落苔”可能仅占5%直接softmax易过拟合主流类部署端约束明确毕设演示需在学生笔记本i5-8250U GTX1050上实时响应不能依赖A100或云服务。提示项目没用ViT或Swin Transformer不是因为它们不行而是训练数据仅327张每类80~90张ViT在小数据上极易过拟合且推理延迟超1.2秒答辩演示时会卡顿。ResNet18在验证集上F1-score达0.91参数量仅11.7MGPU显存占用1.2GB是平衡精度与落地性的务实选择。2.2 数据增强策略针对舌苔特性的5种定制化操作原始数据集共327张舌象图来源某中医院合作提供脱敏数据按7:2:1划分为train/val/test。但直接划分会导致验证集偏差——比如某位医生拍摄的“黄腻苔”全在test里模型根本没见过该拍摄风格。因此作者在data_loader.py中嵌入了医学影像友好型增强链# augmentations.py from torchvision import transforms from PIL import Image import numpy as np import cv2 class TongueAugmentation: def __init__(self, p0.5): self.p p def __call__(self, img): # 1. 随机Gamma校正模拟不同手机白平衡非简单亮度调整 if np.random.random() self.p: gamma np.random.uniform(0.7, 1.3) inv_gamma 1.0 / gamma table np.array([((i / 255.0) ** inv_gamma) * 255 for i in np.arange(0, 256)]).astype(uint8) img cv2.LUT(np.array(img), table) # 2. 局部对比度归一化增强苔质纹理CLIP算法简化版 if np.random.random() self.p: img cv2.createCLAHE(clipLimit2.0, tileGridSize(8,8)).apply(np.array(img.convert(L))) img Image.fromarray(img).convert(RGB) # 3. 模拟舌体偏移随机裁剪后缩放回224x224防模型只认中心区域 if np.random.random() self.p: w, h img.size crop_w, crop_h int(w*0.85), int(h*0.85) left np.random.randint(0, w - crop_w) top np.random.randint(0, h - crop_h) img img.crop((left, top, leftcrop_w, topcrop_h)).resize((224,224)) # 4. 高斯噪声σ0.01对抗手机传感器噪点 if np.random.random() self.p: img_np np.array(img) noise np.random.normal(0, 0.01, img_np.shape) img_np np.clip(img_np noise*255, 0, 255).astype(np.uint8) img Image.fromarray(img_np) # 5. 颜色扰动HSV空间微调避免模型对特定色温过拟合 if np.random.random() self.p: img_hsv cv2.cvtColor(np.array(img), cv2.COLOR_RGB2HSV) img_hsv[:,:,0] (img_hsv[:,:,0].astype(int) np.random.randint(-10,10)) % 180 img_hsv[:,:,1] np.clip(img_hsv[:,:,1].astype(int) np.random.randint(-15,15), 0, 255) img Image.fromarray(cv2.cvtColor(img_hsv, cv2.COLOR_HSV2RGB)) return img参数说明p0.5表示每种增强独立触发概率避免叠加过度失真Gamma校正范围[0.7,1.3]覆盖常见手机白平衡偏差iPhone默认偏暖华为偏冷CLAHE的clipLimit2.0是经验值大于3.0会放大噪点小于1.5则纹理增强不足裁剪比例0.85保证舌体主体不被切掉同时迫使模型关注全局构图HSV扰动中H通道±10°对应色相偏移约6%远低于人眼可辨阈值±15°但足够打破模型对固定色温的依赖。2.3 损失函数与优化器解决类别不平衡的Focal Loss实战配置标准交叉熵损失在舌苔四分类中导致“薄白苔”准确率98%、“剥落苔”仅62%。作者改用Focal Lossα0.75, γ2.0并在train.py中实现梯度裁剪余弦退火# train.py 关键片段 from torch.nn import functional as F def focal_loss(logits, targets, alpha0.75, gamma2.0, reductionmean): ce_loss F.cross_entropy(logits, targets, reductionnone) pt torch.exp(-ce_loss) focal_weight (alpha * (1-pt)**gamma) focal_loss focal_weight * ce_loss return focal_loss.mean() if reduction mean else focal_loss # 训练循环中 optimizer torch.optim.AdamW(model.parameters(), lr1e-4, weight_decay1e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max50) for epoch in range(50): for batch_idx, (data, target) in enumerate(train_loader): optimizer.zero_grad() output model(data) loss focal_loss(output, target, alpha0.75, gamma2.0) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) # 防梯度爆炸 optimizer.step() scheduler.step()为什么α0.75α是平衡因子对少数类剥落苔赋予更高权重实验发现α0.5时少数类提升但多数类下滑明显α0.9时训练震荡0.75是验证集macro-F1最高点γ2.0是经典设定过大如3.0会导致easy样本梯度趋近于0模型收敛变慢。2.4 TensorBoard日志解析如何从events.out.tfevents文件读懂训练是否健康项目附带10个events.out.tfevents.*文件对应不同训练轮次这是TensorBoard的二进制日志。新手常误以为“有日志训好了”其实关键要看三组曲线曲线名称健康指标异常表现排查方向Loss/trainLoss/val两条线同步下降val loss在第30轮后趋于平稳±0.02波动val loss在20轮后突然上扬过拟合需增Dropout或早停Accuracy/trainAccuracy/valval acc稳定在0.89~0.92与train acc差距3%train acc0.98, val acc0.75数据泄露如train/val划分未打乱LearningRate平滑下降至1e-6无突跳曲线呈锯齿状或骤降学习率调度器配置错误本地启动TensorBoard命令确保在项目根目录# 创建logs软链接指向tensorboard日志目录 ln -sf ./runs ./logs # 启动TensorBoard端口6006自动打开http://localhost:6006 tensorboard --logdir./logs --bind_all # 若提示ModuleNotFoundError: No module named tensorboard执行 pip install tensorboard2.11.2 # 严格匹配训练时版本注意所有events文件生成于Windows环境主机名LAPTOP-ACFSLO5L但TensorBoard跨平台兼容Linux/Mac用户无需转换格式。3. GUI界面开发PyQt5不是炫技而是让答辩老师3秒看懂你的成果3.1 界面架构设计为什么不用Streamlit而选PyQt5毕设答辩场景有硬性要求离线可用学校机房禁外网Streamlit依赖Flask服务端无法本地双击运行响应确定PyQt5窗口启动1.5秒Streamlit首次加载需编译JS超时易被质疑“卡顿”控制粒度需精确控制按钮状态如“检测中”禁用、“重置”清空热力图、嵌入Matplotlib动态图、拖拽图片区域——PyQt5信号槽机制比Streamlit回调更可控打包便捷PyInstaller一键打包为单exe答辩U盘拷贝即用Streamlit需额外部署conda环境。项目采用模块化布局左侧图片区QLabelQDragEnterEvent支持拖图、中部按钮区QPushButton状态管理、右侧结果区QTextEdit显示文字结果QGraphicsView嵌入热力图。核心逻辑在tongue_gui.py中解耦为ImageProcessor、ModelInference、ResultDisplay三个类。3.2 图片拖拽与预处理解决“拖进来黑屏”的底层原因新手运行GUI常遇问题拖入图片后界面空白。根源在于PyQt5的QPixmap不支持直接加载OpenCV BGR格式或PIL RGBA模式。作者在ImageProcessor.load_image()中做了三重适配# tongue_gui.py from PyQt5.QtGui import QPixmap, QImage from PIL import Image import numpy as np import cv2 def load_image(self, file_path): try: # 步骤1用PIL读取统一转RGB解决RGBA透明通道问题 pil_img Image.open(file_path).convert(RGB) # 步骤2转numpy array再转BGR供OpenCV预处理裁剪/归一化 np_img np.array(pil_img) bgr_img cv2.cvtColor(np_img, cv2.COLOR_RGB2BGR) # 步骤3舌体粗定位基于HSV肤色提取轮廓筛选 hsv cv2.cvtColor(bgr_img, cv2.COLOR_BGR2HSV) mask cv2.inRange(hsv, np.array([0, 30, 40]), np.array([20, 255, 255])) contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if contours: largest_contour max(contours, keycv2.contourArea) x, y, w, h cv2.boundingRect(largest_contour) # 裁剪舌体区域并缩放至224x224 cropped bgr_img[y:yh, x:xw] processed_img cv2.resize(cropped, (224, 224)) else: # 无舌体轮廓时直接缩放整图兜底策略 processed_img cv2.resize(bgr_img, (224, 224)) # 步骤4转回QImage供PyQt5显示注意BGR-RGB转换 rgb_img cv2.cvtColor(processed_img, cv2.COLOR_BGR2RGB) h, w, ch rgb_img.shape bytes_per_line ch * w qt_img QImage(rgb_img.data, w, h, bytes_per_line, QImage.Format_RGB888) return QPixmap.fromImage(qt_img), processed_img # 返回QPixmapnumpy array供模型推理 except Exception as e: self.status_label.setText(f图片加载失败: {str(e)}) return None, None关键点说明PIL.Image.open().convert(RGB)强制消除Alpha通道避免QPixmap加载RGBA时崩溃HSV肤色范围[0,30,40]~[20,255,255]专为亚洲人舌体肤色标定实测比通用肤色范围更准cv2.findContours后取最大轮廓过滤噪点如背景杂物、手指边缘最终QImage.Format_RGB888是PyQt5唯一稳定支持的格式用Format_BGR888会显示异常色偏。3.3 热力图可视化Grad-CAM不是贴图而是可解释性证据答辩时老师必问“你怎么证明模型真的在看舌苔而不是看背景” Grad-CAM热力图就是答案。项目在gradcam.py中实现ResNet18的梯度加权类激活映射# gradcam.py import torch import torch.nn.functional as F from torch.autograd import Function class GradCAM: def __init__(self, model, target_layer): self.model model self.target_layer target_layer self.gradients None self.activations None # 注册钩子获取目标层梯度和特征图 target_layer.register_forward_hook(self._forward_hook) target_layer.register_backward_hook(self._backward_hook) def _forward_hook(self, module, input, output): self.activations output def _backward_hook(self, module, grad_input, grad_output): self.gradients grad_output[0] def __call__(self, input_tensor, class_idxNone): self.model.eval() output self.model(input_tensor) if class_idx is None: class_idx torch.argmax(output, dim1).item() # 清零梯度反向传播目标类得分 self.model.zero_grad() output[0, class_idx].backward(retain_graphTrue) # 计算权重全局平均池化梯度 weights torch.mean(self.gradients, dim(2, 3), keepdimTrue) # 加权激活图 cam torch.sum(weights * self.activations, dim1, keepdimTrue) cam F.relu(cam) # ReLU保留正向贡献 # 归一化到0-1 cam - torch.min(cam) cam / torch.max(cam) if torch.max(cam) ! 0 else 1 return cam.squeeze().cpu().detach().numpy() # 在GUI中调用 def generate_heatmap(self, img_tensor, pred_class): cam GradCAM(self.model, self.model.layer4[-1]) # ResNet18 layer4最后一层 heatmap cam(img_tensor.unsqueeze(0), pred_class) # 叠加热力图到原图 img_np img_tensor.permute(1,2,0).cpu().numpy() img_np (img_np - img_np.min()) / (img_np.max() - img_np.min()) # 归一化 heatmap_resized cv2.resize(heatmap, (img_np.shape[1], img_np.shape[0])) # 伪彩色叠加 heatmap_colored cv2.applyColorMap(np.uint8(255*heatmap_resized), cv2.COLORMAP_JET) overlay cv2.addWeighted(img_np, 0.5, heatmap_colored/255.0, 0.5, 0) return overlay参数说明target_layerself.model.layer4[-1]ResNet18中最后一个残差块特征抽象程度高热力图更聚焦语义区域torch.mean(gradients, dim(2,3))是Grad-CAM核心对空间维度求均值得到通道权重cv2.COLORMAP_JET是医学影像常用配色红高响应蓝低响应比viridis更易被答辩老师理解addWeighted(..., 0.5, 0.5, 0)中0.5权重保证原图细节与热力图强度平衡避免“一片红”失真。3.4 按钮状态机防止“连续点击导致模型崩掉”的并发控制GUI中用户狂点“检测”按钮易引发线程冲突模型推理未结束又触发新推理。作者用QThreadPoolQRunnable实现异步任务队列并禁用按钮# tongue_gui.py from PyQt5.QtCore import QThread, QObject, pyqtSignal, QThreadPool, QRunnable class InferenceWorker(QRunnable): def __init__(self, model, image_tensor, class_names): super().__init__() self.model model self.image_tensor image_tensor self.class_names class_names self.signals WorkerSignals() def run(self): try: with torch.no_grad(): output self.model(self.image_tensor.unsqueeze(0)) probs torch.nn.functional.softmax(output, dim1)[0] pred_idx torch.argmax(probs).item() confidence probs[pred_idx].item() # 生成热力图 heatmap self.generate_heatmap(self.image_tensor, pred_idx) self.signals.result.emit({ class: self.class_names[pred_idx], confidence: confidence, heatmap: heatmap, probs: probs.cpu().numpy() }) except Exception as e: self.signals.error.emit(str(e)) class WorkerSignals(QObject): result pyqtSignal(object) error pyqtSignal(str) # 主窗口中 def on_detect_clicked(self): if not hasattr(self, current_image) or self.current_image is None: self.status_label.setText(请先加载图片) return # 禁用按钮防止重复提交 self.detect_btn.setEnabled(False) self.detect_btn.setText(检测中...) # 提交任务到线程池 worker InferenceWorker(self.model, self.current_image, self.class_names) worker.signals.result.connect(self.display_result) worker.signals.error.connect(self.handle_inference_error) QThreadPool.globalInstance().start(worker) def display_result(self, result): # 显示结果 self.result_text.setText( f预测类别{result[class]}\n f置信度{result[confidence]:.3f}\n f各分类概率{, .join([f{n}:{p:.3f} for n,p in zip(self.class_names, result[probs])])} ) # 显示热力图 h, w, ch result[heatmap].shape bytes_per_line ch * w qt_img QImage(result[heatmap].data, w, h, bytes_per_line, QImage.Format_RGB888) self.heatmap_view.setPixmap(QPixmap.fromImage(qt_img)) # 恢复按钮 self.detect_btn.setEnabled(True) self.detect_btn.setText(开始检测)设计逻辑QThreadPool.globalInstance()复用全局线程池避免频繁创建销毁线程开销worker.signals.result.connect()是PyQt5推荐的线程安全通信方式比QThread子类更简洁按钮setEnabled(False)和setText(检测中...)提供明确用户反馈杜绝“点不动怀疑程序卡死”的体验。4. 避坑指南99分毕设背后踩过的7个真实坑每个都让你重装Python4.1 PyTorch版本错配为什么torch 1.13.1会报错“no attribute layer4”现象运行python main.py报错AttributeError: ResNet object has no attribute layer4但代码明确写了model.layer4[-1]。原因PyTorch 1.13.1重构了ResNet源码layer4属性名改为_modules[layer4]且内部结构微调。本项目训练时用的是torch1.12.1模型权重文件best_model.pth与1.13.1不兼容。解决严格锁定版本pip uninstall torch torchvision -y pip install torch1.12.1cu113 torchvision0.13.1cu113 -f https://download.pytorch.org/whl/torch_stable.html注意cu113表示CUDA 11.3若你用CPU版替换为cpuWindows用户务必用-f指定官方源否则pip可能安装旧版。4.2 PyQt5中文路径崩溃当图片路径含“舌苔”二字时程序闪退现象双击exe或运行GUI拖入D:\毕设\舌苔样本\001.jpg后立即崩溃cmd窗口显示UnicodeEncodeError: gbk codec cant encode character \u820c。原因Windows默认编码GBKPyQt5的QFileDialog.getOpenFileName()返回路径含中文时部分底层C库无法处理UTF-8路径。解决强制路径转UTF-8并标准化# 在tongue_gui.py的open_file_dialog方法中 def open_file_dialog(self): options QFileDialog.Options() file_name, _ QFileDialog.getOpenFileName( self, 选择舌苔图片, , Images (*.png *.jpg *.jpeg), optionsoptions ) if file_name: # 关键修复将路径转为UTF-8标准化字符串 file_name os.path.normpath(file_name.encode(utf-8).decode(utf-8)) self.load_image(file_name) # 后续load_image接收标准化路径4.3 TensorBoard日志损坏events文件打不开提示“Data loss: not an sstable”现象执行tensorboard --logdir./runs后浏览器空白终端报错Data loss: not an sstable。原因TensorBoard日志文件被文本编辑器误打开并保存如用记事本编辑events文件破坏二进制结构。解决删除整个./runs目录重新运行训练脚本python train.py会自动生成新日志永久规避在Windows资源管理器中右键events文件 → “属性” → 勾选“只读”防止误编辑。4.4 GPU显存不足明明有4GB显存却报“CUDA out of memory”现象train.py运行到第3轮就崩溃报错RuntimeError: CUDA out of memory. Tried to allocate 2.40 GiB。原因ResNet18默认batch_size32但项目数据集小327张作者实际用batch_size16而你可能误改了config.py。解决检查config.py中BATCH_SIZE 16若为32则改回若仍不足添加torch.cuda.empty_cache()到训练循环开头for epoch in range(50): torch.cuda.empty_cache() # 强制清空缓存 for batch_idx, (data, target) in enumerate(train_loader): # ... 训练代码4.5 GUI界面黑屏PyQt5窗口打开但图片区全黑无报错现象python tongue_gui.py启动窗口但左侧图片区域纯黑拖图无反应。原因PyQt5 5.15.9与某些显卡驱动尤其是NVIDIA 470版本存在兼容问题需启用软件渲染。解决在tongue_gui.py最顶部添加import os os.environ[QT_QPA_PLATFORM] offscreen # 或 minimal避免硬件加速冲突或运行时加环境变量set QT_QPA_PLATFORMoffscreen python tongue_gui.py4.6 模型加载失败torch.load()报错“unexpected keyword argument map_location”现象GUI中点击检测控制台报错TypeError: load() got an unexpected keyword argument map_location。原因torch.load()在旧版PyTorch1.10中不支持map_location参数而项目代码写了torch.load(path, map_locationdevice)。解决升级PyTorch或修改加载逻辑# 替换原load代码 # model.load_state_dict(torch.load(model_path, map_locationdevice)) # 改为 checkpoint torch.load(model_path) if state_dict in checkpoint: model.load_state_dict(checkpoint[state_dict]) else: model.load_state_dict(checkpoint)4.7 论文图表导出失真Matplotlib保存的ROC曲线PDF在答辩PPT里糊成一片现象plot_roc_curve.py生成的roc_curve.pdf插入PPT后文字模糊、线条锯齿。原因Matplotlib默认DPI100PDF导出未设置矢量渲染。解决在绘图脚本末尾添加plt.savefig(roc_curve.pdf, bbox_inchestight, dpi300, formatpdf) # 关键参数bbox_inchestight防止裁剪标签dpi300保证印刷质量血泪经验答辩前务必用Adobe Acrobat打开PDF确认文字清晰度PowerPoint直接插入PDF比PNG更保真。5. 毕设答辩加分技巧三招让老师眼前一亮不是“做出来了”而是“做明白了”5.1 把“模型准确率”翻译成临床语言制作舌苔判别依据对照表答辩时老师不关心你F1-score是0.91还是0.92但会问“这个‘黄腻苔’判定依据是什么是颜色还是纹理” 项目论文文档中已内置clinical_interpretation.md但你需要把它变成答辩PPT里的一页硬核内容。我建议用这张表模型输出类别对应中医术语核心视觉特征模型关注点典型临床意义热力图验证示例截图位置薄白苔正常舌象舌面均匀浅红苔质薄而润颗粒细腻气血充盈胃气正常热力图均匀覆盖舌体中部边缘弱响应黄腻苔湿热内蕴苔色偏黄表面油亮反光颗粒粘连成片脾胃湿热痰浊阻滞热力图强响应集中在舌中后部苔厚区域厚黄苔实热证苔色深黄堆积厚重干燥少津阳明腑实热结肠腑热力图高亮区域呈块状舌根部响应最强剥落苔胃阴不足舌面局部无苔露出淡红舌质边界清晰胃阴亏虚津液不能上承热力图在剥落区呈冷色蓝周围苔厚区为暖色红操作指南打开paper/figures/heatmap_examples/目录挑4张典型热力图每类1张用PPT插入图片文本框按上表排版关键动作答辩时指着热力图说“您看模型对‘剥落苔’的响应集中在无苔区域边缘这和中医‘苔剥处为胃阴亏虚之征’完全吻合——不是猜的是看见的。”5.2 预留“故障演示”环节主动展示模型局限性反而显专业所有AI模型都有边界毕设答辩最忌讳“我的模型100%准确”。我建议在演示最后加30秒“压力测试”准备一张强反光舌象图用手机闪光灯直拍舌面运行GUI加载该图模型大概率输出“黄腻苔”因反光被误判为油亮立刻切换PPT页“这是模型当前局限——对极端光照敏感。解决方案已在论文第4.3节提出下一步将引入Retinex图像增强在预处理阶段抑制反光。”从那以后我每次答辩都强制走一遍这个“故意翻车”环节。老师眼睛会亮起来因为这证明你不仅会调参更懂临床落地的真实约束。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑