ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

舌苔识别系统毕设:从数据集到GUI的完整落地路径

舌苔识别系统毕设:从数据集到GUI的完整落地路径 简介这份资源是面向计算机相关专业大四学生与项目实战学习者的毕业设计完整方案主题为基于深度学习的舌苔识别检测鉴定系统配套GUI界面、论文资料与可运行源码适合作为毕设、课程设计或期末大作业的参考模板。压缩包共109个文件约105.43MB其中26个py源码文件承载模型训练与界面逻辑6个pth权重文件与10个pyc缓存支撑推理运行另有2个ui界面文件、2个docx论文文档、7张jpg与2张png示例图、5个json配置及ttc字体等辅助资源目录结构清晰便于按模块查阅。目前已有196人学习下载说明该方案在同类毕设选题中具备一定参考价值。读者可据此获得完整的深度学习舌苔分类实现思路、可复现的训练权重、图形化操作界面以及论文撰写素材既能对照源码理解数据预处理、模型构建与评估流程也能借助现成界面快速演示识别效果降低从零搭建的门槛。1. 舌苔识别系统毕设从数据集到 GUI 的完整落地路径很多做毕设的同学一上来就卡在选题上觉得深度学习方向要么太卷要么太虚。但舌苔识别这个题目其实是个被低估的切入点——它同时踩中了中医数字化和计算机视觉两个方向数据集虽然不大但公开可获取模型不需要从零训练GUI 界面用 PyQt 或 Gradio 就能搭起来论文也有足够的对比实验可写。我去年带过两个学生做类似方向一个做舌体分割一个做舌苔分类最后都顺利通过了答辩。核心思路很清晰用预训练 CNN 做特征提取在舌苔数据集上微调输出分类结果再用 GUI 把推理流程串起来。适合有 Python 基础、学过深度学习入门课、需要在两到三个月内完成毕设的本科生。下面我把整条链路拆开讲包括数据集怎么找、模型怎么选、GUI 怎么接、论文里哪些实验必须做。2. 数据集与预处理舌苔图像从哪来、怎么洗2.1 公开舌象数据集的获取与筛选舌苔识别第一个翻车点就是数据。网上能搜到的舌象数据集主要有几个来源一是中医舌诊相关的公开研究数据集通常几百到几千张标注了舌质、舌苔、舌形等属性二是自己用手机或相机在自然光下拍摄但这种方式一致性差后期清洗成本极高。我一般建议优先用公开数据集常见做法是从学术论文的补充材料或一些高校实验室的公开页面获取格式多为 JPG 或 PNG分辨率在 500×500 到 2000×2000 之间。拿到数据后先做一轮人工筛选把模糊、过曝、舌头没伸出来的图删掉。这一步没有捷径我让学生用 Python 写了个简单的批量预览脚本快速过一遍。筛选标准就三条舌体完整可见、光线均匀、舌苔区域没有被牙齿或嘴唇大面积遮挡。筛完通常剩 70% 到 80% 可用。2.2 用 OpenCV 做舌体区域裁剪与归一化原始舌象图里舌头只占一部分直接送进网络会引入大量背景噪声。常见做法是先做舌体分割再把舌体区域裁出来缩放到统一尺寸。如果不想上分割模型可以用颜色阈值加轮廓检测的土办法效果对毕设够用。import cv2 import numpy as np import os def extract_tongue_region(img_path, save_path, target_size(224, 224)): 基于 HSV 颜色空间提取舌体区域并裁剪 img_path: 原始图像路径 save_path: 裁剪后保存路径 target_size: 统一缩放尺寸默认 224x224 适配主流 CNN img cv2.imread(img_path) if img is None: return False # 转 HSV舌体在红色到粉色区间 hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 红色有两个区间合并 lower1 np.array([0, 40, 50]) upper1 np.array([10, 255, 255]) lower2 np.array([160, 40, 50]) upper2 np.array([180, 255, 255]) mask cv2.inRange(hsv, lower1, upper1) cv2.inRange(hsv, lower2, upper2) # 形态学去噪 kernel np.ones((5, 5), np.uint8) mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) mask cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) # 找最大轮廓 contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return False cnt max(contours, keycv2.contourArea) x, y, w, h cv2.boundingRect(cnt) # 留一点边距 pad 10 x max(0, x - pad) y max(0, y - pad) w min(img.shape[1] - x, w 2 * pad) h min(img.shape[0] - y, h 2 * pad) crop img[y:yh, x:xw] crop cv2.resize(crop, target_size, interpolationcv2.INTER_AREA) cv2.imwrite(save_path, crop) return True # 批量处理 src_dir raw_tongue dst_dir processed_tongue os.makedirs(dst_dir, exist_okTrue) for fname in os.listdir(src_dir): if fname.lower().endswith((.jpg, .png, .jpeg)): extract_tongue_region( os.path.join(src_dir, fname), os.path.join(dst_dir, fname) )这段代码的逻辑是先把图像转到 HSV 空间因为舌体的红色/粉色在 HSV 里比 RGB 更容易用阈值分离。两个红色区间合并是为了覆盖从淡红到深红的舌苔变化。形态学闭运算填补 mask 内部小孔开运算去掉背景噪点。找最大轮廓是因为舌体通常是画面里最大的红色连通区域。裁剪时留 10 像素边距避免切掉舌边缘。最后统一缩放到 224×224这是 ResNet、VGG、MobileNet 等主流骨干网络的默认输入尺寸。参数方面HSV 阈值需要根据你的数据集实际色调微调。如果舌苔偏白或偏黄红色区间可能覆盖不全可以适当放宽 S 和 V 的下限。target_size 如果后面用 EfficientNet 可以改成 300 或 380但毕设用 224 足够显存占用小训练快。提示颜色阈值法对偏白舌苔效果一般如果数据集里白苔占比高建议手动标注 50 到 100 张图训练一个轻量分割模型比如 U-Net 小版本IoU 能到 0.85 以上比调阈值省心。2.3 数据增强与类别不平衡处理舌苔分类通常分几类薄白苔、厚白苔、黄苔、灰黑苔、剥落苔等。公开数据集里薄白苔可能占一半以上直接训练模型会偏向多数类。常见做法是先用数据增强扩增少数类再用加权损失函数。from torchvision import transforms from torch.utils.data import DataLoader, WeightedRandomSampler import torch # 训练集增强随机翻转、旋转、颜色抖动 train_transform transforms.Compose([ transforms.RandomHorizontalFlip(p0.5), transforms.RandomVerticalFlip(p0.3), transforms.RandomRotation(15), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2, hue0.05), transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) # 验证集只做缩放和归一化 val_transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) # 假设 dataset 已经构建好labels 是每个样本的类别索引 def make_weighted_sampler(labels): class_counts torch.bincount(torch.tensor(labels)) class_weights 1.0 / class_counts.float() sample_weights class_weights[torch.tensor(labels)] sampler WeightedRandomSampler( weightssample_weights, num_sampleslen(sample_weights), replacementTrue ) return sampler增强策略里水平翻转和垂直翻转对舌象都合理因为舌头左右对称、上下翻转后纹理分布不变。旋转 15 度以内模拟拍摄角度偏差。颜色抖动是关键因为不同设备、不同光线拍出来的舌苔颜色差异很大适度抖动能让模型对色偏更鲁棒。Normalize 用的 ImageNet 均值方差因为后面用预训练模型必须和预训练时的归一化保持一致。WeightedRandomSampler 的作用是让每个 batch 里少数类样本被采到的概率提高相当于在数据层面做平衡。如果不想用 sampler也可以在损失函数里加 class_weight效果类似。我一般两个都上先 sampler 再在 CrossEntropyLoss 里设 weight双保险。3. 模型选型与训练MobileNetV3 还是 ResNet503.1 骨干网络对比与毕设场景下的选择依据毕设不是发论文不需要追求 SOTA。选骨干网络看三个指标参数量、训练速度、在中小数据集上的过拟合倾向。ResNet50 参数量 25M 左右在几千张图上容易过拟合训练一轮要几分钟。MobileNetV3-Large 参数量 5.4M推理快对小数据集更友好。EfficientNet-B0 参数量 5.3M精度通常比 MobileNetV3 高一点但训练时对学习率更敏感。我一般让学生先用 MobileNetV3-Large 跑 baseline因为它在 ImageNet 上预训练权重好找torchvision 直接加载改分类头就能用。如果 baseline 精度不够再换 EfficientNet-B0。ResNet50 除非数据集超过一万张否则不推荐训得慢还容易过拟合。骨干网络参数量输入尺寸毕设推荐度主要问题MobileNetV3-Large5.4M224高精度上限略低EfficientNet-B05.3M224/300高学习率敏感ResNet5025M224中小数据集过拟合VGG16138M224低参数太多训练慢3.2 迁移学习训练脚本与关键超参设置import torch import torch.nn as nn import torch.optim as optim from torchvision import models from torch.utils.data import DataLoader # 加载预训练 MobileNetV3-Large model models.mobilenet_v3_large(weightsmodels.MobileNet_V3_Large_Weights.IMAGENET1K_V1) # 替换分类头假设 5 类舌苔 num_classes 5 model.classifier[3] nn.Linear(model.classifier[3].in_features, num_classes) # 冻结特征提取层前几层只训练后面 for name, param in model.named_parameters(): if features.0 in name or features.1 in name or features.2 in name: param.requires_grad False device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) # 损失函数带类别权重 class_weights torch.tensor([1.0, 2.0, 2.5, 3.0, 3.5]).to(device) criterion nn.CrossEntropyLoss(weightclass_weights) # 优化器只更新 requires_gradTrue 的参数 optimizer optim.AdamW( filter(lambda p: p.requires_grad, model.parameters()), lr1e-4, weight_decay1e-4 ) # 学习率调度余弦退火 scheduler optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max30) # 训练循环 def train_one_epoch(model, loader, criterion, optimizer, device): model.train() total_loss 0 correct 0 total 0 for imgs, labels in loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() outputs model(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() _, preds outputs.max(1) correct (preds labels).sum().item() total labels.size(0) return total_loss / len(loader), correct / total # 假设 train_loader 已经用前面的 transform 和 sampler 构建好 # for epoch in range(30): # loss, acc train_one_epoch(model, train_loader, criterion, optimizer, device) # scheduler.step() # print(fEpoch {epoch1}, Loss: {loss:.4f}, Acc: {acc:.4f})关键参数说明lr 设 1e-4 是因为用了预训练权重太大容易破坏已学特征太小收敛慢。weight_decay 1e-4 防过拟合。CosineAnnealingLR 让学习率从 1e-4 余弦下降到接近 0比 StepLR 更平滑毕设里通常 30 到 50 个 epoch 就够。冻结前几层是因为浅层特征边缘、颜色通用性强不需要重新学还能省显存。class_weights 的设置逻辑是少数类给高权重具体数值看类别频率频率越低权重越高。我一般让权重和类别样本数成反比再归一化到 1 到 4 之间。如果用了 WeightedRandomSampler这里的权重可以设温和一点比如都设 1.0 到 2.0。注意训练时如果发现 loss 震荡厉害先检查学习率是不是太大再检查 batch size 是不是太小。毕设常见 batch size 是 16 或 32显存不够就降到 8但要把学习率相应调小。3.3 训练过程监控与早停策略毕设答辩时老师常问“你怎么知道模型没 overfit”。标准回答是看训练集和验证集的 loss 曲线。如果训练 loss 持续下降但验证 loss 先降后升就是过拟合需要加 dropout、加 weight_decay 或早停。# 早停实现 class EarlyStopping: def __init__(self, patience7, min_delta0.001): self.patience patience self.min_delta min_delta self.counter 0 self.best_loss None self.early_stop False def __call__(self, val_loss): if self.best_loss is None: self.best_loss val_loss elif val_loss self.best_loss - self.min_delta: self.counter 1 if self.counter self.patience: self.early_stop True else: self.best_loss val_loss self.counter 0patience 设 7 的意思是验证 loss 连续 7 轮没下降就停。min_delta 0.001 是容忍微小波动。这个策略能省训练时间也能防止过拟合。我一般还会保存验证集上最好的模型权重最后用那个权重做测试和 GUI 集成。4. GUI 界面与推理集成PyQt5 还是 Gradio4.1 GUI 框架选型与毕设展示需求匹配毕设 GUI 的核心需求是打开界面、选图片、点按钮、显示结果。PyQt5 做出来像桌面软件适合答辩时本地演示。Gradio 做出来是网页界面适合远程展示或录屏。如果学校要求必须桌面程序选 PyQt5如果只是演示Gradio 开发快得多。我一般让学生用 PyQt5因为答辩时老师能看到完整的窗口、按钮、图片显示区域显得工作量足。Gradio 虽然快但界面太简单容易被问“你这界面是不是没做多少东西”。4.2 PyQt5 集成模型推理的完整代码import sys import torch from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, QWidget, QFileDialog) from PyQt5.QtGui import QPixmap, QImage from PyQt5.QtCore import Qt from torchvision import transforms, models import torch.nn as nn from PIL import Image class TongueApp(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(舌苔识别检测系统) self.setGeometry(100, 100, 800, 600) self.model self.load_model() self.transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) self.class_names [薄白苔, 厚白苔, 黄苔, 灰黑苔, 剥落苔] self.init_ui() def load_model(self): model models.mobilenet_v3_large(weightsNone) model.classifier[3] nn.Linear(model.classifier[3].in_features, 5) model.load_state_dict(torch.load(best_tongue_model.pth, map_locationcpu)) model.eval() return model def init_ui(self): central QWidget() self.setCentralWidget(central) layout QVBoxLayout() self.img_label QLabel(请选择舌象图片) self.img_label.setAlignment(Qt.AlignCenter) self.img_label.setMinimumSize(400, 400) self.img_label.setStyleSheet(border: 1px solid gray;) self.result_label QLabel(识别结果等待输入) self.result_label.setAlignment(Qt.AlignCenter) btn_layout QHBoxLayout() self.select_btn QPushButton(选择图片) self.select_btn.clicked.connect(self.select_image) self.predict_btn QPushButton(开始识别) self.predict_btn.clicked.connect(self.predict) btn_layout.addWidget(self.select_btn) btn_layout.addWidget(self.predict_btn) layout.addWidget(self.img_label) layout.addWidget(self.result_label) layout.addLayout(btn_layout) central.setLayout(layout) def select_image(self): path, _ QFileDialog.getOpenFileName(self, 选择图片, , Images (*.png *.jpg *.jpeg)) if path: self.current_path path pixmap QPixmap(path).scaled(400, 400, Qt.KeepAspectRatio) self.img_label.setPixmap(pixmap) def predict(self): if not hasattr(self, current_path): self.result_label.setText(请先选择图片) return img Image.open(self.current_path).convert(RGB) tensor self.transform(img).unsqueeze(0) with torch.no_grad(): output self.model(tensor) prob torch.softmax(output, dim1) conf, pred prob.max(1) self.result_label.setText( f识别结果{self.class_names[pred.item()]} 置信度{conf.item():.2%} ) if __name__ __main__: app QApplication(sys.argv) window TongueApp() window.show() sys.exit(app.exec_())这段代码的逻辑load_model 里用和训练时相同的结构加载权重注意 weightsNone 因为要加载自己的权重。transform 必须和验证集一致否则推理结果会偏。predict 里用 torch.no_grad() 关闭梯度计算省内存。softmax 把输出转成概率取最大值的类别和置信度。界面部分用 QVBoxLayout 垂直排列图片区、结果区、按钮区QFileDialog 选文件QPixmap 显示图片。参数方面best_tongue_model.pth 是训练时保存的最佳权重路径class_names 要和训练时的类别顺序一致否则标签会错位。置信度显示用百分比更直观答辩时老师能一眼看到模型有多确信。提示如果推理时发现结果和训练时验证集精度差很多先检查 transform 是不是一致再检查图片是不是 RGB 三通道最后检查类别顺序有没有搞反。4.3 推理速度优化与批量测试毕设演示时如果一张图要等好几秒体验很差。MobileNetV3 在 CPU 上单张推理大概 50 到 100 毫秒已经够快。如果用了 ResNet50CPU 上可能 300 毫秒以上建议转 ONNX 或 TorchScript 加速。# 转 TorchScript 加速推理 model.eval() example torch.randn(1, 3, 224, 224) traced torch.jit.trace(model, example) traced.save(tongue_model_traced.pt) # 加载时 model torch.jit.load(tongue_model_traced.pt)TorchScript 的好处是脱离 Python 解释器开销推理快 20% 到 30%。毕设里如果老师问“怎么优化推理速度”这就是一个加分回答。5. 避坑与常见问题舌苔识别毕设里最容易翻车的 5 个点5.1 数据集类别标注不一致导致精度虚高现象训练时验证集精度 95%但换一批新图测试掉到 60%。原因标注时不同人对“薄白苔”和“厚白苔”的界限理解不同导致训练集和验证集分布不一致模型学到了标注噪声。解决标注时定死标准比如“舌苔厚度超过舌面 1/3 算厚苔”找两个人交叉标注不一致的样本直接丢弃。验证集要单独从同一批标注里随机抽不能和训练集混在一起。5.2 图像预处理和训练时不一致现象GUI 里识别结果总是某一类置信度还很高。原因GUI 里忘了做 Normalize或者 Resize 尺寸和训练时不一样。解决把训练时的 transform 封装成一个函数训练和推理都调用同一个函数避免手写两套。检查方法是拿一张训练集里的图分别走训练 transform 和推理 transform看输出是否一致。5.3 显存不足导致训练中断现象训练到一半报 CUDA out of memory。原因batch size 太大或者没有用 torch.no_grad() 做验证。解决先把 batch size 降到 8验证时加 with torch.no_grad()再不行就冻结更多层。毕设显卡通常是 6G 或 8GMobileNetV3 加 batch size 16 一般够用。5.4 GUI 打包后模型加载失败现象PyQt5 程序在 PyCharm 里能跑打包成 exe 后报找不到模型文件。原因打包时没有把 .pth 文件一起打进去或者路径用了相对路径。解决用 PyInstaller 打包时加 --add-data best_tongue_model.pth;.代码里用 sys._MEIPASS 获取临时目录路径。或者干脆把模型转成 TorchScript 再打包兼容性更好。5.5 论文里缺少对比实验和消融实验现象答辩时老师问“为什么用 MobileNetV3 不用 ResNet”答不上来。原因只跑了一个模型没有对比。解决至少跑三个骨干网络MobileNetV3、EfficientNet-B0、ResNet50在同一数据集上对比精度和推理时间。再做一组消融不加数据增强、不加类别权重、不加迁移学习看精度掉多少。这些表格放进论文第四章工作量就撑起来了。6. 论文实验设计与答辩加分技巧6.1 必须做的三组实验和对应表格毕设论文里实验部分至少要有三组第一组是不同骨干网络的对比表格列精度、参数量、单张推理时间第二组是消融实验验证数据增强、类别权重、迁移学习各自的贡献第三组是混淆矩阵看哪些类别容易混。混淆矩阵特别有用如果“薄白苔”和“厚白苔”混得多说明这两类本身界限模糊答辩时主动提出来老师会觉得你分析到位。实验组变量指标预期结论骨干对比MobileNetV3 / EfficientNet-B0 / ResNet50Acc / Params / TimeMobileNetV3 性价比最高消融有无增强 / 有无权重 / 有无预训练Acc预训练贡献最大混淆矩阵5 类舌苔各类召回率薄白苔和厚白苔易混6.2 答辩演示的流程设计和常见追问演示流程建议先打开 GUI选一张测试图点识别展示结果和置信度。然后切到论文翻到实验表格讲清楚为什么选这个模型。老师常问的问题有“数据集多少张”“类别怎么分的”“过拟合怎么处理的”“GUI 和模型怎么通信的”。提前把答案写进答辩稿每个答案控制在三句话以内。我自己的习惯是答辩前把测试集里模型分错的图单独挑出来分析原因。如果老师问到错误案例能说出“这张图舌苔偏黄但光线偏暖模型误判为薄白苔说明颜色特征受光照影响大”这比背稿子强得多。做毕设不是交差是把一个完整链路跑通以后做项目也是这套流程。希望帮到你。本文还有配套的精品资源点击获取
返回列表