简介:一套基于深度学习的舌苔识别检测鉴定系统毕业设计项目,面向计算机相关专业学生和需要Python实战练习的开发者,覆盖舌苔图像识别、检测与鉴定场景,可直接用于毕业设计、课程设计或期末大作业。项目经导师指导并获认可,评审分高达99分,代码完整、运行可靠,基础薄弱的学习者也能按文档上手运行。整套资料共109个文件,压缩包大小105.44MB,核心包括Python源码(.py)、PyQt5界面文件(.ui)、预训练模型权重(.pth)、TensorFlow训练日志(events)以及图片样本、JSON配置、论文文档(.docx)等,结构清晰,便于对照源码理解模型训练与推理流程,也可直接加载权重进行识别演示。PyQt5图形界面让操作直观,适合答辩展示;论文文档可配合撰写设计报告,节省整理时间。目前已有150人浏览学习,是完成课程项目或毕业设计的实用参考。
1. 基于深度学习的舌苔识别检测鉴定系统:不是做一个分类器,而是做一套能交给医生用的工具
看舌苔这事,中医讲究望诊,但不同医生看同一张舌像,结论不一致的情况很常见。基于深度学习的舌苔识别检测鉴定系统,要解决的不只是“这张图属于什么苔”,而是先把舌体从背景里框出来,再对舌苔的颜色、厚薄给出检测结果和置信度,最后用PyQt5把这些逻辑封装成桌面软件。这套东西适合两类人:一个是准备做毕业设计又不想只交一个demo的学生,另一个是想在诊所里做舌苔辅助判读工具的一线工程师。它不算前沿,但把数据集、训练、界面串起来,比单纯跑通一个模型有说服力得多。
2. 数据准备是这系统最耗时的部分:从手机图到YOLO标签
2.1 为什么选目标检测而不是图像分类来识别舌苔
常见误区是直接用图像分类,输入一张舌图输出“白苔/黄苔”。但舌苔检测的实际情况是:舌体在图片里占比因人而异,背景里有嘴唇、牙齿、皮肤,甚至还有手机水印。分类模型把整张图一起处理,背景干扰直接进特征,训练集一旦不干净,模型就学会看背景而不是看舌苔。目标检测模型先给出舌体位置的边界框,再在框内判断舌苔属性,这个顺序符合医生看诊的逻辑。
我一般用YOLO系做检测,原因是舌体不是小目标,它占据画面中心区域,YOLO对中大型目标的检测稳定且推理快。Faster R-CNN精度更高但训练慢,SSD轻量但小目标差,对舌苔这种“目标大、类别差异在纹理和颜色”的问题,YOLO性价比最高。你如果论文里要体现深度学习算法的对比,可以把Faster R-CNN当作对照组,但主力用YOLO,跑通成本低,答辩时的实时性数据也好看。
2.2 自建舌苔数据集:拍摄规范、类别定义和labelme标注
舌苔没有公开的ImageNet式数据集,常见做法是自建。收集阶段注意三点:光源尽量接近自然光,不要开美颜;拍舌头时让患者自然伸舌,舌尖不要卷;每张图至少包含完整舌体。保存成.jpg,按类别分文件夹。类别不是越多越好,我建议控制在5类以内,比如:正常薄白苔、白厚苔、黄薄苔、黄厚苔、剥苔。如果做毕业论文,5类足以说明问题,超过8类会让标注成本翻倍,最后还容易出现类别混淆。
标注工具用labelme。安装时有个老坑:labelme依赖PyQt5,如果你先装了PyQt5再执行pip install labelme,pip会按依赖关系重新装PyQt5,可能导致版本被改掉。我现在的做法是用独立的conda环境专门做标注:
conda create -n labelme_env python=3.8 -y conda activate labelme_env pip install labelme labelme注意,如果你的Python环境是3.10以上,PyQt5安装可能报错,建议直接用3.8或3.9,省得浪费时间。labelme打开后按P键画多边形,画完选类别,保存json。用矩形还是多边形?我建议用矩形,虽然粗糙一点,但舌体是整体区域,边界不需要像素级,矩形标注速度是多边形的一半,对检测模型足够。如果只标注舌苔区域而不标注舌体,模型就不知道“没舌苔的正常舌体”长什么样,所以舌体本身也要单独建一个类别。
2.3 把labelme的JSON转成YOLO格式:转换脚本与四个边界坑
YOLO训练需要txt文件,内容是每一行的“类别 中心点x 中心点y 宽 高”,坐标都是相对于图片宽高的0到1值。下面这段脚本把labelme的json批量转成YOLO标签:
import json import os import cv2 from glob import glob # 舌苔类别顺序,训练时的yaml文件要和这里保持一致 CLASS_NAMES = ['tongue', 'normal_thin', 'white_thick', 'yellow_thin', 'yellow_thick'] def convert(json_path, out_dir): with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) image_path = data['imagePath'] # 关键点:labelme的json里可能没有imageWidth/imageHeight字段 img = cv2.imread(os.path.join(os.path.dirname(json_path), image_path)) if img is None: print('读取原图失败,请检查路径:', json_path) return img_h, img_w = img.shape[:2] lines = [] for shape in data['shapes']: if shape['shape_type'] != 'rectangle': continue label = shape['label'] if label not in CLASS_NAMES: continue class_id = CLASS_NAMES.index(label) p1, p2 = shape['points'] x1, y1 = p1 x2, y2 = p2 x_min, x_max = min(x1, x2), max(x1, x2) y_min, y_max = min(y1, y2), max(y1, y2) if x_max <= x_min or y_max <= y_min: continue x_center = ((x_min + x_max) / 2.0) / img_w y_center = ((y_min + y_max) / 2.0) / img_h w = (x_max - x_min) / img_w h = (y_max - y_min) / img_h lines.append(f'{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}') out_name = os.path.basename(json_path).replace('.json', '.txt') with open(os.path.join(out_dir, out_name), 'w', encoding='utf-8') as f: f.write('\n'.join(lines)) if __name__ == '__main__': os.makedirs('labels', exist_ok=True) for json_file in glob(os.path.join('annotations', '*.json')): convert(json_file, 'labels') print('转换完成:', json_file)逻辑说明:脚本读取json的shapes,只处理rectangle类型,因为我们要的是矩形框。类别名称和训练用的yaml必须一一对应,否则类别错位。中心点坐标用了浮点数六位精度,足够了。
四个边界坑:
- labelme高版本json里没有图片尺寸,必须用opencv读原图,用json里的imageWidth/imageHeight在旧版本可以,新版本一用就崩。
- 图片路径不要在训练集里带中文,yaml里train路径带中文会导致opencv读图失败,玄学报错。
- 标注时不要把框贴死在舌体边缘,留3%到5%的边距,模型能学到边界上下文,检测更稳。
- 类别合集里一定要加“tongue”这个舌体类,不然正常舌体没标签,训练时被当成负样本,最后白苔黄苔全漏检。
转换完成后,按8:1:1划分训练集验证集测试集,把对应的图片和txt文件分别放到train、val、test目录。划分脚本很简单,但记得用随机种子固定,不然每次跑结果不同,论文实验就没法复现。
3. 模型训练与调参:让YOLO在舌苔数据上收敛的完整流程
3.1 网络权重和backbone选型
舌苔图像有很强的医学影像特征,没有预训练权重很难从零收敛。常见做法是下载COCO预训练权重,比如yolov5s.pt,然后用舌苔数据微调。yolov5s参数量小,推理速度快,适合毕设系统和医生桌面端;如果你显卡显存比较大,可以换yolov5m,mAP能高1到2个点,但界面帧率会降。halcon里也有深度学习目标检测,它的优点是有图形化训练工具,但Halcon的许可证和导出部署不适合与PyQt5界面集成,所以从工程角度还是用Python生态更顺。
深度学习环境配置是另一个隐形时间杀手。需要用到的包有torch、opencv、numpy、pyqt5、labelme,直接pip install到系统Python,很容易把依赖搞乱。我一般这样建训练环境:
conda create -n tongue python=3.9 -y conda activate tongue pip install torch torchvision pip install opencv-python numpy pip install "PyQt5>=5.15,<5.16"这里有一个值得说的细节:PyQt5固定到5.15系列,是因为6.x在部分屏幕缩放适配和旧代码兼容上有问题。torch安装时注意选择匹配你CUDA版本的轮子,没有GPU就装CPU版,虽然训练慢,但先把流程跑通,后面再换GPU机器。
3.2 最小可跑通的训练命令
拿到yolov5源码包后,在项目目录里建一个data/tongue.yaml,内容如下:
train: ./data/tongue/train/images val: ./data/tongue/val/images nc: 5 names: ['tongue', 'normal_thin', 'white_thick', 'yellow_thin', 'yellow_thick']然后执行训练命令:
python train.py --data data/tongue.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --device 0参数说明:--img 640是YOLO默认输入尺寸,舌体占画面主体,不需要按小目标调到1280,否则显存翻倍但收益不大。--batch大小取决于显存,6G显存用16可能OOM,调成8就好。--epochs 100算基础值,一般到第60个epoch后Loss趋于稳定,如果还在下降就加到150,早停机制会帮你判断。--device 0指第一块GPU,没有GPU就写cpu,但训练时间会很久。
训练过程的输出会显示mAP@0.5、mAP@0.5:0.95、precision、recall等指标。注意:mAP@0.5达到90%以上不难,但mAP@0.5:0.95才是对比实验的重要指标。如果mAP@0.5:0.95偏低,问题多半不是模型,而是标签的边界框不够精准,或者类别定义有歧义。这时候回头查标注,别急着换模型。
3.3 三个必须调的参数:置信度阈值、IoU阈值和增强策略
训练完成后,真正影响界面体验的是推理时的置信度阈值和IoU阈值。跑一条推理命令:
python detect.py --weights runs/train/exp/weights/best.pt \ --source test.jpg \ --conf 0.30 \ --iou 0.45 \ --save-txt --save-conf--conf控制显示哪些框。舌苔检测中,如果置信度阈值设到0.5,漏检会变多;我一般设0.3。因为舌苔类别之间视觉差异很近,白厚苔和白薄苔的边界是连续的,网络天然会给出0.3到0.6的分数,一刀切太高就漏了。--iou是NMS的IOU阈值,0.45偏严格,0.6会把重叠框合并得更干净。舌体通常只有一个主框,NMS不至于敏感。
增强策略在训练端控制yolov5的hyp.yaml。舌苔是颜色敏感的任务,默认的HSV增强会把白苔调成黄苔,导致模型学错特征。我会把hyp.yaml里的hsv_s和hsv_v从默认值调低一半,比如hsv_s从0.7调成0.2,或者直接设为0。同时保持fliplr水平翻转,因为舌苔不存在左右不对称的问题。随机平移和缩放可以保留,模拟拍摄角度变化。
如果你用yolov8,那yaml配置变成你训练代码里的参数,方法类似。yolov8比v5好的一点是内置了更多数据增强,但对舌苔这种颜色敏感数据,同样需要降低增强强度,否则黄苔白苔照样混。
4. 舌苔识别系统常见问题排查:模型、环境和界面的翻车记录
4.1 mAP一直不涨,Loss也不再下降,但结果全是背景框
现象:训练曲线看起来正常,验证集mAP却很低,检测出来的框全都框在非舌体区域。
原因:最常见的不是模型问题,而是标签只标注了舌苔区域,没有标注“正常舌体”,导致模型把没有舌苔的正常舌体当作背景去学习。第二个常见原因是标注rect框只有两个点,但转换脚本里用了shape_type != 'rectangle'过滤,有些labelme版本把矩形存成polygon,四个点,被直接跳过,标签数量严重不足。
解决:先把tongue这一类包含所有舌体矩形,再在这个框基础上去做苔质区分。如果一个舌体同时有白苔和黄苔,比如舌尖黄苔,舌根白苔,那就用两个框重叠标注,模型允许同一位置有不同类别的框。然后检查标签数量,用下面命令看每个类别有多少样本:
for f in labels/*.txt; do cut -d' ' -f1 "$f"; done | sort | uniq -c如果某一类的数量为0,说明转换脚本或标注过程漏了,回到数据准备章节修,别急着继续训练。
4.2 PyQt5装不上,或labelme安装后PyQt5版本被覆盖
现象:conda环境里明明有PyQt5,却import报错;labelme装完之后Qt界面样式变了,甚至界面代码运行崩。
原因:PyQt5和labelme都依赖Qt组件,但两者对Qt版本的锁定不同。pip解释器在安装依赖时会自动向上兼容,把PyQt5升级到最新版,而最新版可能与你的界面代码不兼容。
解决:我的做法是建两个虚拟环境,一个专门标注用,一个做界面开发。标注环境只装labelme,界面环境固定PyQt5到5.15系列:
conda create -n app python=3.9 -y conda activate app pip install "PyQt5>=5.15,<5.16" python -c "from PyQt5.QtWidgets import QApplication; print('ok')"如果你已经用了一个环境,想同时保留labelme和你的界面代码,安装labelme时加--no-deps,再手动补它依赖的qtpy和opencv,但这样麻烦,不如拆环境干净。还有一个Windows上的坑是Qt的缩放和高DPI策略,界面模糊时在代码开头加:
import os os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "1"这行要放在import QApplication之前,否则不生效。
4.3 检测框抖动,同一张图每次结果不一样
现象:同样的图片,重复运行detect.py,框的位置和类别忽高忽低,偶尔还会多出一个小框。
原因:最常见的是你用的权重是last.pt而不是best.pt,last.pt是最后一轮权重,可能还没收敛到最优。另外,推理时如果开了cudnn.benchmark且batch大小不固定,也会产生非确定性抖动。
解决:训练完明确指定best.pt。推理时把batch设为1,所有输入保持原尺寸。如果还抖动,在代码里显式关闭cudnn benchmark:
import torch torch.backends.cudnn.benchmark = False这样可以保证同一张图每次输出一致,这在论文复现实验里很重要。
4.4 舌苔颜色被增强搞乱,黄苔被识别成白苔
现象:训练集里黄苔clear,验证集却大量误判成白苔;单独测试几张又没问题,整体mAP低。
原因:数据增强里的HSV扰动过大。YOLO默认hyp.yaml里hsv_h、hsv_s、hsv_v分别对色相、饱和度和明度做随机调整,白苔在饱和度和明度上和黄苔接近,扰动后网络学到的不是“舌苔本体颜色”,而是“颜色变化范围”,直接把两类糊在一起。
解决:训练前把增强参数降到接近零。我用的是hsv_h: 0.01,hsv_s: 0.1,hsv_v: 0.2,或干脆全为0,效果立竿见影。另一个提升颜色的手段是在界面端做白平衡预处理,推理前用cv2.xphoto或简单灰度世界算法先校色。这个对拍摄端的光源差异有效,但不要在训练时对每张图都校色,否则模型会依赖校色过程。
5. 用PyQt5把模型封装成桌面诊断系统:一种最小实现和验证方法
把训练好的模型接到PyQt5界面里,核心思路是“界面只负责显示,推理放到后台线程”。因为YOLO推理虽然快,但加载模型和初始化会卡住窗口,操作起来像死机。下面是一个可跑通的最小骨架:
import sys import cv2 import torch from PyQt5.QtWidgets import QApplication, QLabel, QPushButton, QVBoxLayout, QWidget, QFileDialog from PyQt5.QtGui import QImage, QPixmap class TongueApp(QWidget): def __init__(self): super().__init__() self.model = torch.hub.load('local/yolov5', 'custom', path='best.pt', source='local') self.model.conf = 0.3 self.model.iou = 0.45 self.label = QLabel('选择舌像图片') self.btn = QPushButton('打开图片') self.btn.clicked.connect(self.open_image) layout = QVBoxLayout(self) layout.addWidget(self.label) layout.addWidget(self.btn) def open_image(self): path, _ = QFileDialog.getOpenFileName(self, '选择图片', '', 'Image Files (*.jpg *.png)') if path: img = cv2.imread(path) results = self.model(img) frame = results.render()[0] h, w, ch = frame.shape qimg = QImage(frame.data, w, h, ch * w, QImage.Format_BGR888).copy() self.label.setPixmap(QPixmap.fromImage(qimg).scaled(400, 400)) if __name__ == '__main__': app = QApplication(sys.argv) win = TongueApp() win.show() sys.exit(app.exec_())逻辑说明:torch.hub.load用本地yolov5目录加载自定义权重,避免每次联网。results.render()返回绘制了检测框的BGR图,转换为QImage显示。真正做产品还需要把框坐标、类别、置信度存到数据库或导出CSV,这样毕业论文的“系统实现”章节才有东西写。
验证方法:取50张训练时没见过的测试图,人工画框同时记录苔质类别。统计三点:检测框IoU大于0.5视为正确;苔质类别预测准确率;单张推理耗时。把这些做成表格,就是论文的实验与验证章节。如果你还觉得不够,可以加一个“不同光照下识别稳定性”的对比,分别用晴天窗边光和室内暖光灯拍摄十组,看准确率差异。
我做这类项目最深的教训是:模型训练占据的时间其实只占三分之一,数据标注和界面接入占大头,别因为训练提前结束就以为项目完事了。希望帮到你。
本文还有配套的精品资源,点击获取