ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

基于PyQt5与深度学习的舌苔识别系统:从源码到毕业设计全流程

基于PyQt5与深度学习的舌苔识别系统:从源码到毕业设计全流程 简介本资源为基于深度学习的舌苔识别检测鉴定系统完整项目包面向计算机相关专业正在做毕业设计的学生、需要项目实战练习的学习者也可作为课程设计或期末大作业参考。项目经导师指导并认可通过评审分98分涵盖舌苔检测、体质辨识等核心功能配套PyQt5图形界面与训练好的模型可直接运行演示。压缩包整体约105.47MB内含Python源码、PyQt5界面文件、模型权重及毕业论文文档源码负责算法与界面逻辑论文则完整梳理课题背景、机器学习理论、需求分析与舌象数据集构建等章节便于对照理解系统设计思路。目前已有93人学习关注。读者可借此获得一套结构清晰的毕设方案掌握从数据集构建、卷积神经网络训练到GUI集成的完整流程并参考论文中的可行性分析与功能模块划分快速完成自己的项目搭建与答辩准备。1. 舌苔识别系统到底在做什么从一张手机照片到一份体质报告很多人第一次听到「舌苔识别检测鉴定系统」会以为是个噱头其实它解决的是一个很具体的问题中医舌诊依赖经验同一个舌头不同医生看结论可能不一样。把这件事交给深度学习模型核心目标不是替代医生而是把「舌质颜色、舌苔厚薄、苔色黄白、齿痕有无」这几个可量化的维度标准化。你手上如果有一份 Python 源码、一个 PyQt5 界面、一个训练好的模型权重再加上一篇毕业论文那这套东西的完整链路就是图像采集 → 预处理 → 分类/分割 → 结果展示 → 报告导出。适合谁适合做毕设的本科生、想入门医学图像分类的算法新手、以及需要快速搭一个桌面端 AI 演示工具的产品验证者。热搜里「python」「pyqt5」「深度学习」「舌苔识别」「源码」这几个词恰好对应了这套系统的四层语言、界面、算法、数据。下面我按实际能跑起来的顺序把每一层拆开讲。2. 环境与数据把「python安装」到「舌苔数据集」这条链路先打通2.1 为什么选 PyQt5 PyTorch 而不是 Web 方案做桌面端舌苔识别最常见的翻车点是选了 Web 框架结果摄像头调用、本地文件批量处理、离线演示三件事互相打架。PyQt5 的优势在于它是真正的本地窗口程序QCamera 或 OpenCV 直接读摄像头QFileDialog 批量选图推理结果用 QLabel 贴图整个链路不依赖浏览器权限。深度学习框架这边PyTorch 比 TensorFlow 更适合毕设场景动态图调试直观torchvision里 ResNet、EfficientNet 的预训练权重直接能加载改几行就能做迁移学习。热搜里「pyqt5安装」「python安装numpy库的方法」之所以高频就是因为这两步卡住的人最多。我一般会建议用 conda 建独立环境避免和系统 Python 混在一起。conda create -n tongue python3.8 -y conda activate tongue pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install opencv-python pillow numpy pyqt5逻辑说明Python 3.8 是 PyQt5 和多数预训练模型兼容性最稳的版本不建议上 3.11部分旧版 torchvision 会报DLL load failed。--index-url指定 CPU 版轮子如果你有 NVIDIA 显卡换成对应 CUDA 版本的命令即可。参数说明opencv-python负责图像读取和颜色空间转换pillow用于 PyQt5 显示时的格式桥接numpy是所有矩阵运算的底座。装完后用python -c import cv2, torch; print(torch.__version__)验证能打印版本号才算通。2.2 舌苔数据集的采集与标注口径舌苔识别不是通用图像分类数据集质量决定上限。常见做法是分两类任务一是舌体分割把舌头从嘴唇、牙齿、背景里抠出来二是舌苔属性分类薄白、厚白、薄黄、厚黄、剥落等。如果你拿不到医院级数据可以用公开的中医舌象数据集做预训练再用自己的手机拍摄数据微调。拍摄时固定三个条件自然光或环形补光灯、舌头自然伸出不超过 5 秒、镜头距离 20–30 cm。标注用 LabelImg 或 LabelMe分割任务存 PNG 掩码分类任务按文件夹分目录。import os, cv2, numpy as np from sklearn.model_selection import train_test_split def load_dataset(root): images, labels [], [] classes sorted(os.listdir(root)) for idx, cls in enumerate(classes): cls_dir os.path.join(root, cls) for fname in os.listdir(cls_dir): img cv2.imread(os.path.join(cls_dir, fname)) img cv2.resize(img, (224, 224)) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) images.append(img) labels.append(idx) return np.array(images), np.array(labels), classes X, y, class_names load_dataset(data/tongue_coat) X_train, X_val, y_train, y_val train_test_split(X, y, test_size0.2, stratifyy, random_state42) print(class_names, X_train.shape, X_val.shape)逻辑说明sorted(os.listdir)保证类别顺序固定否则训练和推理时标签对不上这是血泪经验。stratifyy让训练集和验证集类别比例一致舌苔数据往往黄苔样本少不分层会导致验证集里没有黄苔。参数说明输入尺寸 224×224 是 ResNet 系列的标准输入如果你用 EfficientNet-B0 也是 224用 B4 就要改 380。random_state42固定划分方便复现。跑完打印出类别名和形状确认没有读成空数组。3. 模型训练从迁移学习到舌苔分类的四个必调参数3.1 用 ResNet18 做迁移学习的最小训练脚本舌苔识别数据量通常不大几千张就算多的从零训练 CNN 必然过拟合。迁移学习是标准解法加载 ImageNet 预训练权重替换最后一层全连接先冻结主干训分类头再解冻后几层微调。热搜里「深度学习cnn」「动手深度学习」对应的就是这一块。我一般用 ResNet18 起步参数量 11MCPU 也能跑毕设演示足够。import torch, torch.nn as nn, torch.optim as optim from torch.utils.data import DataLoader, TensorDataset from torchvision import models, transforms transform transforms.Compose([ transforms.ToPILImage(), transforms.RandomHorizontalFlip(), transforms.RandomRotation(10), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) train_ds TensorDataset(torch.stack([transform(x) for x in X_train]), torch.tensor(y_train)) val_ds TensorDataset(torch.stack([transform(x) for x in X_val]), torch.tensor(y_val)) train_loader DataLoader(train_ds, batch_size32, shuffleTrue) val_loader DataLoader(val_ds, batch_size32) model models.resnet18(weightsmodels.ResNet18_Weights.DEFAULT) for p in model.parameters(): p.requires_grad False model.fc nn.Linear(512, len(class_names)) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.fc.parameters(), lr1e-3) for epoch in range(15): model.train() for xb, yb in train_loader: optimizer.zero_grad() loss criterion(model(xb), yb) loss.backward() optimizer.step() model.eval() correct total 0 with torch.no_grad(): for xb, yb in val_loader: pred model(xb).argmax(1) correct (pred yb).sum().item() total yb.size(0) print(fepoch {epoch1} val_acc {correct/total:.4f})逻辑说明先冻结全部参数只训fc是因为随机初始化的分类头梯度太大会破坏预训练特征。跑 5 个 epoch 后如果验证准确率上不去再解冻layer4用更小学习率微调。参数说明batch_size32在 8GB 内存的机器上安全显存不够就降到 16。lr1e-3是分类头专用微调阶段要降到 1e-4。RandomRotation(10)模拟拍摄角度偏差ColorJitter模拟不同光源这两个增强对舌苔颜色分类特别关键但亮度抖动别超过 0.3否则黄苔白苔会被混淆。3.2 舌苔分类的四个必调参数与验证指标训练舌苔模型有四个参数你不动它准确率就是上不去。第一个是输入分辨率224 对舌体整体够用但如果你要分「薄厚」这种纹理细节建议 320 或 384。第二个是类别权重黄苔样本少就设weighttorch.tensor([1.0, 1.0, 2.5, 2.5])传给 CrossEntropyLoss。第三个是学习率调度用CosineAnnealingLR比固定 lr 稳。第四个是早停 patience验证损失连续 5 轮不降就停省时间。参数推荐值作用调错后果输入尺寸224 / 320控制纹理细节太小分不清薄厚类别权重按样本反比缓解不平衡少数类全错初始 lr1e-3头/1e-4微调收敛速度太大震荡太小不动早停 patience5防过拟合训到过拟合才停验证指标别只看 accuracy。舌苔识别里「厚苔漏判」比「薄苔误判」严重所以要看每类的 recall 和混淆矩阵。用sklearn.metrics.classification_report打印重点看黄苔那一行的 recall。如果黄苔 recall 低于 0.7先查数据增强是不是把黄色洗掉了再查类别权重。4. PyQt5 界面把模型推理塞进桌面窗口的三个关键点4.1 界面布局与信号槽的最小可用结构PyQt5 做 AI 演示界面核心就三块左边显示原图右边显示结果和置信度底部一排按钮打开图片、开始识别、保存报告。热搜里「pyqt5教程」「pyqt5显示html」说明很多人卡在控件联动上。记住一个原则推理必须放在 QThread 里不能直接写在按钮的槽函数里否则界面会假死这是最常见的翻车点。from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, QWidget, QFileDialog from PyQt5.QtGui import QPixmap, QImage from PyQt5.QtCore import QThread, pyqtSignal import cv2, numpy as np, sys class InferThread(QThread): finished pyqtSignal(str, float) def __init__(self, img_path, model, class_names): super().__init__() self.img_path, self.model, self.class_names img_path, model, class_names def run(self): img cv2.imread(self.img_path) img cv2.resize(img, (224, 224)) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) / 255.0 tensor torch.tensor(img).permute(2,0,1).unsqueeze(0).float() with torch.no_grad(): prob torch.softmax(self.model(tensor), 1) conf, idx prob.max(1) self.finished.emit(self.class_names[idx.item()], conf.item()) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(舌苔识别检测系统) self.img_label QLabel(请选择舌象图片) self.result_label QLabel(等待识别) btn_open QPushButton(打开图片) btn_run QPushButton(开始识别) btn_open.clicked.connect(self.open_image) btn_run.clicked.connect(self.run_infer) layout QVBoxLayout() layout.addWidget(self.img_label) layout.addWidget(self.result_label) h QHBoxLayout(); h.addWidget(btn_open); h.addWidget(btn_run) layout.addLayout(h) container QWidget(); container.setLayout(layout) self.setCentralWidget(container) self.img_path None def open_image(self): self.img_path, _ QFileDialog.getOpenFileName(self, 选择图片, , Images (*.png *.jpg *.jpeg)) if self.img_path: pix QPixmap(self.img_path).scaled(400, 400) self.img_label.setPixmap(pix) def run_infer(self): if not self.img_path: return self.thread InferThread(self.img_path, model, class_names) self.thread.finished.connect(lambda name, conf: self.result_label.setText(f结果{name} 置信度{conf:.2%})) self.thread.start() app QApplication(sys.argv) win MainWindow(); win.show() sys.exit(app.exec_())逻辑说明InferThread继承 QThread把推理放在run里通过pyqtSignal把结果传回主线程更新 UI这样界面不会卡。finished信号带两个参数槽函数用 lambda 接收。参数说明scaled(400, 400)保持显示比例实际项目里应该用Qt.KeepAspectRatio。permute(2,0,1)把 HWC 转 CHWunsqueeze(0)加 batch 维度这两步顺序错了模型直接报维度错误。torch.softmax把 logits 转概率置信度低于 0.6 时建议界面提示「建议重新拍摄」。4.2 图像预处理与界面显示的格式桥接OpenCV 读进来是 BGRPyQt5 的 QImage 要 RGB模型训练时又是 RGB 归一化三个色彩空间来回转不统一就会出现「训练准、界面错」的玄学问题。我一般写一个统一函数所有入口都走它。另外舌体分割如果单独做了界面要显示掩码叠加图用cv2.addWeighted把原图和掩码按 0.7:0.3 混合。def preprocess_for_model(img_path): img cv2.imread(img_path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img cv2.resize(img, (224, 224)) img img.astype(np.float32) / 255.0 mean np.array([0.485, 0.456, 0.406]) std np.array([0.229, 0.224, 0.225]) img (img - mean) / std return torch.tensor(img).permute(2,0,1).unsqueeze(0).float() def cvimg_to_qpixmap(cv_img): rgb cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape qimg QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) return QPixmap.fromImage(qimg)逻辑说明preprocess_for_model里的 mean/std 必须和训练时transforms.Normalize完全一致不一致模型输出会整体偏移。cvimg_to_qpixmap里ch * w是每行字节数写错会花屏。参数说明如果你的模型训练时没做 Normalize这里也要去掉否则颜色全变。分割掩码叠加时掩码要先转成三通道再addWeighted。5. 避坑与排查舌苔识别系统最常见的五条血泪记录5.1 模型训练准确率 99% 但界面识别全错现象训练脚本打印 val_acc 0.99PyQt5 界面里同一张图识别成随机类别。原因训练时用了transforms.Normalize界面推理时只做了/255.0没减均值除标准差。解决把预处理封装成一个函数训练和推理共用别写两份。这是最高频的翻车没有之一。5.2 摄像头拍出来偏蓝黄苔被判成白苔现象白天自然光下识别正常晚上开台灯就全错。原因白平衡没锁不同色温下舌苔颜色偏移模型没见过这种分布。解决采集数据时就要覆盖多种光源训练时加ColorJitter推理端加一个简单白平衡校正用灰度世界算法把 RGB 均值拉平。5.3 PyQt5 点击识别按钮后界面卡死现象按钮按下去窗口无响应几秒后才出结果。原因推理写在主线程槽函数里阻塞了 Qt 事件循环。解决必须用 QThread 或 QThreadPool把模型推理放子线程通过信号回传结果。注意模型对象不要跨线程共享每个线程加载一份或加锁。5.4 打包成 exe 后模型文件找不到现象PyCharm 里跑得好好的PyInstaller 打包后报FileNotFoundError: model.pth。原因打包后工作目录变了相对路径失效。解决用sys._MEIPASS判断是否打包环境把模型路径拼成绝对路径或者用--add-data把模型文件打进去。5.5 舌体分割掩码边缘有锯齿影响分类现象分割出来的舌头边缘毛糙分类模型把嘴唇像素也学进去了。原因分割网络输出是低分辨率直接上采样没做后处理。解决分割后做一次形态学开运算去噪再用cv2.GaussianBlur平滑边缘最后按掩码裁剪再送分类模型。别小看这一步边缘干净后分类准确率能涨 3–5 个点。6. 进阶技巧用 Grad-CAM 让舌苔识别结果可解释毕设答辩时老师最爱问的一句话是「模型凭什么这么判」。你光给一个置信度不够得让评委看到模型关注的是舌苔区域而不是背景。Grad-CAM 是最容易落地的可解释性方法原理是拿目标类别的梯度对最后一个卷积层的特征图加权生成热力图叠加在原图上。热搜里「深度学习l2正则化pytorch代码」这类词说明大家关心训练细节但答辩现场一张热力图比十行正则化代码更有说服力。import torch.nn.functional as F class GradCAM: def __init__(self, model, target_layer): self.model model self.gradients None self.activations None target_layer.register_forward_hook(self.save_activation) target_layer.register_full_backward_hook(self.save_gradient) def save_activation(self, module, input, output): self.activations output.detach() def save_gradient(self, module, grad_input, grad_output): self.gradients grad_output[0].detach() def generate(self, input_tensor, class_idxNone): output self.model(input_tensor) if class_idx is None: class_idx output.argmax(1).item() self.model.zero_grad() output[0, class_idx].backward() weights self.gradients.mean(dim(2, 3), keepdimTrue) cam F.relu((weights * self.activations).sum(1, keepdimTrue)) cam F.interpolate(cam, sizeinput_tensor.shape[2:], modebilinear, align_cornersFalse) cam cam.squeeze().cpu().numpy() cam (cam - cam.min()) / (cam.max() - cam.min() 1e-8) return cam cam_gen GradCAM(model, model.layer4[-1]) heatmap cam_gen.generate(preprocess_for_model(test.jpg))逻辑说明register_forward_hook抓前向特征图register_full_backward_hook抓反向梯度weights是梯度在空间维度上的均值代表每个通道对目标类的重要程度。F.relu只保留正向贡献F.interpolate把热力图放大回原图尺寸。参数说明target_layer选model.layer4[-1]是 ResNet 的最后一个残差块选太浅热力图会糊选太深分辨率不够。生成后把热力图用cv2.applyColorMap转成伪彩色再和原图addWeighted叠加存成报告插图。我自己的习惯是每次改完模型结构或预处理先跑一张固定测试图看热力图有没有落在舌体上如果热区跑到背景或牙齿上说明模型学偏了这时候调数据比调参有用。舌苔识别这个方向数据质量占七成模型结构占两成界面和报告占一成。别在界面上花太多时间把预处理和数据集做扎实结果自然稳。希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表