资讯详情 舌苔识别系统实战:数据标注、模型训练与GUI封装全流程解析
📅 2026/10/25 19:18:24
简介基于深度学习的舌苔识别检测鉴定系统是一套完整的毕业设计项目面向计算机相关专业正在准备毕设的学生以及需要项目实战练习的初学者。项目包含Python源码、深度学习模型权重、基于Qt的GUI界面和完整的毕业论文文档覆盖数据准备、模型训练、效果评估到界面部署的端到端流程整体评审得99分代码完整可直接运行。压缩包共109个文件大小105.44MB主要包含26个Python脚本、6个模型权重文件、5个JSON配置、2个界面UI文件及2个Word论文文档另有训练过程中生成的TensorBoard日志可观察模型收敛曲线与训练细节。已有132人浏览学习这套项目既可作为课程设计或期末大作业的完整参考也可帮助学习者快速搭建舌苔识别Demo理解从算法到应用的落地方法。包内还配有示例图片与说明文本运行环境清晰适合从零开始完成毕设或学习深度学习项目实践。1. 舌苔识别系统为什么说数据标注比训练模型更值得投入舌苔识别在中医数字化里是个“看似好上手、实则会卡你一个月”的方向图像分类、目标检测、语义分割技术都能套上去Python 生态里随便找一个开源框架就能把模型训练起来但真正决定你能不能拿高分毕业设计的往往不是网络结构而是你有没有一套靠谱的舌苔图像数据集和标注规范。这套系统做的是把“黄苔、白苔、厚腻苔、剥苔”这类中医描述转化成可量化的视觉特征用深度学习模型给出类别判断或区域定位再用 GUI 界面把结果直观展示出来补齐课题、开题、答辩、论文里的完整链路。适合两类人想用毕设切入医学图像方向的本科生以及打算做中医量化辅助工具、需要先跑通可视化原型的开发者。先给结论模型选型用 ResNet 这类成熟分类网络即可真正拉开差距的是数据清洗、标注一致性和 GUI 交互设计这三件事。2. 先解决数据舌苔图像数据集的采集、标注与预处理2.1 数据从哪来公开数据集、自采与伦理红线我见过不少同学一上来就搜“舌苔数据集下载”结果要么找到的是几十张分辨率不一的零散图片要么是带水印的电商截图训练出来的模型泛化能力几乎为零。常见做法是三条路并行第一公开的舌象数据集比如一些高校开源的中医舌象图像库百级到千级规模够做类别均衡的实验第二自己用手机统一光源拍摄找同学或门诊配合采集注意遮挡面部只保留舌部区域第三论文里的公开数据作为补充但必须确认版权许可。这个环节要特别留意伦理问题。舌苔图像虽然不像人脸那样强身份属性但口腔黏膜和舌象同样属于个人健康信息正规做法是对图像做脱敏处理只保留舌体区域并在论文里写明数据来源与参与者的知情同意。很多学校的开题答辩会专门问这件事提前在文档里写清楚能省掉后期大改的麻烦。2.2 标注方案分类用文件夹检测用 VOC 转 YOLO如果你的系统定位是“识别鉴定”建议至少做成目标检测——不仅说出是黄苔还是白苔还要框出舌体区域。这样论文里能多一张可视化检测图答辩时效果远好于只给一个分类概率。标注工具我用的是 LabelImg导出 Pascal VOC 格式的 XML。但训练时 YOLO 系列要 txt 格式中间必须过一道转换脚本。下面这段是每次都要用的转换工具把 XML 里的坐标转成归一化的 YOLO 格式import os import xml.etree.ElementTree as ET def convert_voc_to_yolo(xml_path, out_dir, class_names): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) txt_name os.path.splitext(os.path.basename(xml_path))[0] .txt lines [] for obj in root.iter(object): class_name obj.find(name).text if class_name not in class_names: continue cls_id class_names.index(class_name) box obj.find(bndbox) x_min float(box.find(xmin).text) y_min float(box.find(ymin).text) x_max float(box.find(xmax).text) y_max float(box.find(ymax).text) # 转成 YOLO 需要的中心点 宽高格式并做归一化 center_x (x_min x_max) / 2.0 / img_w center_y (y_min y_max) / 2.0 / img_h box_w (x_max - x_min) / img_w box_h (y_max - y_min) / img_h lines.append(f{cls_id} {center_x:.6f} {center_y:.6f} {box_w:.6f} {box_h:.6f}) with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(lines)) class_names [tongue, yellow_coating, white_coating, thick_greasy] out_dir yolo_labels os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(voc_annotations): if xml_file.endswith(.xml): convert_voc_to_yolo(os.path.join(voc_annotations, xml_file), out_dir, class_names)这段代码的核心是把 XML 里记录的目标左上角和右下角坐标换算成 YOLO 系列模型要求的“中心点坐标 目标宽高”格式同时除以图片宽高做归一化避免不同分辨率图片之间数值量纲不一致。注意 class_names 的顺序必须和后续训练配置文件完全一致否则会出现框的位置对但类别错乱的诡异问题。我在实际使用中遇到过一个典型坑标注时把white_coating写成了white coating空格和下划线混用训练时类别映射全部错位这种低级错误靠肉眼很难查出来建议标注前先把类别清单固定并做一次脚本校验。2.3 预处理与数据增强颜色扰动要克制舌苔识别对颜色极其敏感——黄苔和白苔的主要差异就是颜色倾向和覆盖程度所以预处理阶段不能像做自然影像分类那样开很强的颜色扰动。我一般固定这样一套先等比缩放到 640×640 或 416×416保留舌体区域居中裁剪再做归一化用 ImageNet 的 mean 和 std 即可没必要自己重新统计数据增强只用水平翻转、轻微旋转 ±10 度、小范围亮度抖动色相饱和度扰动控制在 0.02 以内否则训练集里的人工偏色会直接污染模型对真实舌色的判断。另外一个容易被忽略的点是曝光一致性。很多人用手机拍舌苔图室内外色温差很大白平衡不统一会让模型把“偏蓝”和“偏黄”学成类别特征。我一般会先用 OpenCV 做一次灰度世界白平衡校正代码就一行让图片的整体色彩偏移回到中性再进入训练管线。这一步对最终精度的影响经常有 3 到 5 个百分点的差距。3. 模型选型与训练参数分类网络的克制选择与检测头的尝试3.1 为什么优先用 ResNet 而不是堆新架构舌苔识别这个场景的数据量通常只有几千张类别之间差异又集中在纹理和颜色细节上用太深的网络或者 Transformer 架构很容易过拟合。我一般首推 ResNet18 或 ResNet50理由很直接参数量适中PyTorch 官方有预训练权重可以直接做迁移学习训练时间在单张消费级显卡上可以控制在几小时内。ResNet18 走轻量路线适合机器配置一般的情况ResNet50 表达能力强一些但数据不足时对正则化要求高需要同时在数据增强和 Dropout 上做补偿。如果坚持要做目标检测出来的“框”那就用 YOLOv8 或 Faster R-CNN。我遇到过不少同学想在毕设里用 Faster R-CNN 以显示“有难度”结果训练时间直接翻了几倍数据量不够时收敛也不稳定。合理折中是 YOLOv8 的 nano 或 small 版本既出检测框训练速度也快论文里还能放一张带置信度的检测图。3.2 训练脚本从加载预训练权重到完整微调下面这段是训练分类模型的最小可用脚本我把关键配置都写在注释里。它先加载 ResNet18 预训练权重把最后一层全连接改成自己的类别数然后用带权重衰减的 SGD 做微调。import torch import torch.nn as nn from torchvision import models, transforms from torch.utils.data import DataLoader from dataset import TongueDataset # 类别按数据标注顺序固定乱序会导致训练指标正确但实际预测错位 num_classes 4 device torch.device(cuda if torch.cuda.is_available() else cpu) model models.resnet18(pretrainedTrue) model.fc nn.Linear(model.fc.in_features, num_classes) model model.to(device) # 只对最后的全连接层用较大的学习率前面的卷积层用小学习率做温和微调 optimizer torch.optim.SGD([ {params: model.layer4.parameters(), lr: 1e-4}, {params: model.fc.parameters(), lr: 1e-3}, ], momentum0.9, weight_decay1e-4) criterion nn.CrossEntropyLoss() scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size5, gamma0.1) train_loader DataLoader( TongueDataset(data/train, transformtransforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ])), batch_size32, shuffleTrue, num_workers4) for epoch in range(15): model.train() running_loss 0.0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() * images.size(0) scheduler.step() print(fEpoch {epoch1}/15, Loss: {running_loss / len(train_loader.dataset):.4f})这段代码有三处最关键第一分组学习率卷积层用 1e-4 保持预训练特征不剧烈变化全连接层用 1e-3 快速适应新类别第二交叉熵损失直接配合预训练模型不需要自行实现权重初始化第三StepLR 每 5 个 epoch 降一次学习率让后期收敛更稳。如果你发现 loss 在 0.8 左右就降不下去先检查是不是类别不均衡——舌苔数据里健康薄白苔通常占绝对多数这时候要把 CrossEntropyLoss 换成带类别权重的版本。3.3 类别不平衡加权损失与 Focal Loss 的取舍舌苔数据天然存在严重不平衡薄白苔占 60% 以上剥苔、灰黑苔可能只有几十张。常见做法是给每个类别算权重让少数类在损失函数里获得更大的梯度。我用的是 sklearn 的 compute_class_weight然后传入 CrossEntropyLoss 的 weight 参数。如果少数类样本实在太少个位数就得额外考虑用数据增强复制或者干脆在论文里说明这类样本的局限性。Focal Loss 是另一个选项它对难分类样本更敏感但参数调起来比较玄学alpha 和 gamma 组合需要多轮网格搜索时间紧的话不建议在毕设里硬上。我在实践里见过的翻车案例是不做类别平衡直接训练模型对薄白苔的准确率冲到 95%看起来整体指标很漂亮但把黄苔、厚腻苔的召回率拉到 30% 以下。答辩时老师随手问一句“模型区分黄白苔的能力怎么样”就答不上来。所以务必要看每类的混淆矩阵不能只看总准确率。4. GUI 界面与系统集成用 PyQt5 把模型封装成可直接演示的工具4.1 框架选择的理由Tkinter 太素PyQt5 的收益更明显毕设答辩现场给老师演示界面观感直接影响印象分。Tkinter 虽然零依赖但做出来的东西偏工具风按钮和布局都比较简陋。PyQt5 虽然要多装两个包但样式、布局、图片展示效果专业很多而且 PyQt5 的 QGraphicsView 做标注框叠加显示非常顺手论文里截一张图出来档次就不一样。常见做法是主界面左侧放图片选择预览区右侧放识别结果面板下方是置信度条和保存报告按钮。如果机器的 Python 环境装 PyQt5 有兼容问题退而求其次用 Qt for Python 的 PySide6API 基本一致。4.2 最小 GUI 代码加载模型、选择图片、输出结果下面这段是封装推理流程的最小 PyQt5 代码去掉菜单栏和样式细节只保留核心链路选择图片、预处理、模型推理、展示结果和置信度。import sys import torch from PIL import Image from torchvision import transforms from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QPushButton, QFileDialog, QVBoxLayout, QWidget from PyQt5.QtGui import QPixmap class TongueGUI(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(舌苔识别鉴定系统) self.model torch.load(tongue_model.pth, map_locationcpu) self.model.eval() self.transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) self.class_names [薄白苔, 黄苔, 厚腻苔, 剥苔] self._init_ui() def _init_ui(self): self.image_label QLabel(请选择舌苔图片) self.result_label QLabel(识别结果) self.btn QPushButton(打开图片) self.btn.clicked.connect(self.open_image) layout QVBoxLayout() layout.addWidget(self.image_label) layout.addWidget(self.result_label) layout.addWidget(self.btn) container QWidget() container.setLayout(layout) self.setCentralWidget(container) def open_image(self): path, _ QFileDialog.getOpenFileName(self, 选择图片, , Image Files (*.jpg *.png)) if not path: return pixmap QPixmap(path).scaled(300, 300) self.image_label.setPixmap(pixmap) image Image.open(path).convert(RGB) input_tensor self.transform(image).unsqueeze(0) with torch.no_grad(): outputs self.model(input_tensor) prob torch.softmax(outputs, dim1).squeeze(0) conf, idx torch.max(prob, dim0) self.result_label.setText( f识别结果{self.class_names[idx]}置信度{conf.item():.2%} ) app QApplication(sys.argv) win TongueGUI() win.show() sys.exit(app.exec_())这段代码把整个推理链封装在 GUI 类里核心细节是模型输出先过 softmax 再取最大值和对应索引避免直接对 logits 取 argmax 时无法给出置信度数值。注意 torch.load 默认会保留训练时的设备信息如果训练是在 GPU 上做的加载到 CPU 机器必须加 map_locationcpu。实际交付时还要补一个异常处理用户选了非图片文件、图片损坏、模型文件缺失都要用 try-except 包住否则一闪而退的崩溃会让演示现场很尴尬。4.3 让界面更可信检测框叠加与报告导出如果做的是 YOLO 检测模型GUI 里除了显示类别和置信度还要能把检测框画在图片上。OpenCV 的 rectangle 加 putText 就能实现画完再转成 QImage 显示。报告导出我一般生成一份简单的文本或 PDF包含图片路径、识别类别、置信度、时间戳几个字段答辩时可以作为系统完整性的佐证。这块不需要做得太重能导出就比不能导出好很多。5. 舌苔识别系统避坑指南从数据到部署最常踩的 5 个问题5.1 颜色偏色让模型学到错误特征现象模型在验证集上准确率很高换一批用不同手机拍的照片就大幅下降。原因训练集白平衡不一致模型把“偏蓝的舌头”或“偏暗的整体亮度”学成了类别特征而不是真正区分舌苔本身。解决在预处理阶段加灰度世界白平衡校正并统一采集光源条件进模型前再做一次颜色扰动很小的数据增强。5.2 标注类别定义不清导致标签噪声现象训练 loss 一直震荡验证集准确率卡在 60% 左右上不去。原因多个标注者对“薄白苔”和“厚腻苔”的边界理解不一致比如有颗粒感到底算不算腻导致同一张图出现不同标签网络学到的是互相矛盾的信号。解决标注前先写一份简单的标注规范把每种类别的判定标准量化让两个人背靠背各标注一遍再用脚本统计标注一致率低于 90% 的类别重新讨论定义。5.3 数据不平衡但只看总准确率现象答辩时被问“黄苔准确率多少”打开混淆矩阵才发现黄苔召回率只有 30%。原因训练时直接用普通交叉熵模型为了降低整体 loss 牺牲少数类。解决用 sklearn 的 compute_class_weight 计算权重把嘴巴交给 CrossEntropyLoss 的 weight 参数论文里多放一张混淆矩阵图比任何文字都更有说服力。5.4 GPU 训练完本地 CPU 加载报错现象模型在服务器上训练好拿回本地电脑做演示torch.load 直接报错。原因模型保存时包含了 CUDA 设备信息本地没有对应 GPU。解决加载时加 map_locationcpu即torch.load(tongue_model.pth, map_locationcpu)更稳妥的是保存时用 state_dict 而不是整个模型这样可以绕开模型类定义不一致的问题。5.5 GUI 里图片显示和推理结果不一致现象界面上图片正常展示但推理结果明显不对比如选了黄苔的图却识别成薄白苔。原因GUI 展示用的原图和模型输入端处理的不是同一尺寸或者路径里包含中文导致 PIL 读取失败。解决统一在推理前用 PIL 重新打开原图并和显示用的图片保持同一切换逻辑路径处理建议用 os.path 拼接而不是字符串加法Windows 下尤其容易在中文字符上踩坑。6. 从“能跑”到“可信”验证模型可靠性的一剂后悔药很多人做完整套系统就收工了但我建议再多花两天做一件事统计口径验证。训练完先冻结模型准备一套独立测试集计算每个类别的准确率、召回率和 F1然后算一个加权 Kappa 系数这是医学图像论文里很常用的评判标准可以客观地说明模型鉴定结果和标注者之间的一致性。Kappa 在 0.6 以上算“中等一致”0.8 以上就能在论文里写“具有较高一致性”这个数字比准确率更有参考价值答辩时也更好讲。如果最后发现黄苔和白苔混淆严重一个有效的补救手段是调整阈值不一定要推翻模型。把 softmax 的输出阈值从 0.5 调高到 0.7模型对低置信度样本选择“不支持判断”而不是硬分类会大幅减少误报。这个改动只需要在推理代码里加一行判断却能让演示现场的可靠性显著提升。我每次做完这类医学图像识别系统都会留一整个下午做“反推测试”故意把黄苔图调亮、调暗、加噪看模型什么时候开始误判找到边界后写进论文的“局限性”一节。这个习惯救过我太多次也让审稿老师看到了对问题的理解深度。做舌苔识别不只是把深度学习模型调通更要在数据、标注、界面、验证这条链路上都让人放心。希望帮到你。本文还有配套的精品资源点击获取