简介:基于深度学习的舌苔检测毕业设计完整留档,面向人工智能、深度学习方向的本科生或研究生,适合作为医学图像处理、目标检测类课题的参考项目。包含从数据标注、模型训练到结果可视化的全流程代码。压缩包共109个文件,以Python脚本(py/pyc)、模型权重(pth)、配置文件(json)和运行界面(ui)为主,另有部分图片样本及TensorBoard训练日志,整体约105.39MB。已有829人浏览学习,被用于毕设参考和实际项目复现。资源内包含训练好的模型权重、完整Python源码及多次实验的TensorBoard事件文件,便于对比训练曲线与调参记录;同时提供UI界面和文档说明,可帮助快速理解系统架构并在此基础上进行改进,对于需要完成类似医学图像检测课题的同学具有较强参考价值。
1. 基于深度学习的舌苔检测毕设,留档 zip 里到底该有什么
“基于深度学习的舌苔检测毕设 留档.zip”这类标题在计算机视觉方向的毕设里出现频率不低。它本质上不是一个新算法题,而是把通用目标检测迁移到中医舌诊场景:输入一张自然光下拍的舌头照片,模型在画面里定位舌头区域,再判断舌苔的类型、面积或者有无。这个题目能解决的真实需求,是健康管理 App、中医体质辨识和远程问诊里的自动初筛。适合选它的人,是手里有一段完整毕设周期、希望用一个看得见效果又能讲清原理的深度学习流程来交付课题的读者。它比通用目标检测好做在指标明确、样本可自采,难点则集中在数据分布、标注口径和真实拍摄条件上。
2. 先定数据再选模型:舌苔检测的数据规范与基线骨架
不管你是打算解压别人留档的 zip 做二次开发,还是准备从零攒一套自己的工程,动手前都得先把三件事定下来:数据从哪来、标注成什么格式、基线模型用什么。这三件事互相牵连,顺序错了后面全是返工。
2.1 舌苔影像数据从哪来:三个能落地的采集路径
舌苔检测不像通用物体检测那样有海量公开数据可以直接下载。常见做法是三条路并行。
第一条路是找公开的舌象相关图像集。中医舌诊数字化有一些研究性质的图片,散见于论文附带数据和少数开放平台,但大多没有统一标注,需要自己清洗。这类数据的优点是拍摄条件相对规范,缺点是数量少、类别分布偏科,直接当训练集容易让模型过拟合到某一种打光风格上。
第二条路是自采。找实验室、宿舍或者家里的人帮忙拍,手机摄像头就够用。拍摄时要求自然伸出舌头、嘴巴张开、光线均匀,每张图尽量只保留嘴部区域。我一般会按“正常舌苔、厚腻苔、黄苔、少苔/无苔”四个粗类去收,每个类至少收 150 到 200 张。自采数据最大的价值是验证集和测试集可以单独留一批“真实手机照”,用来证明模型没在公开图上过拟合。
第三条路是爬取公开图片平台,但这一步清洗成本很高。网络图普遍存在分辨率混乱、脸部被裁切、舌头区域占比过小的问题,而且版权和质量都没法保证。我的建议是:爬来的图只做预训练风格的补充或者困难样本挖掘,不要让它占训练集的太大部分。
数据量不需要贪多。做舌苔检测这种单目标、背景相对固定的任务,500 到 800 张精标图足够把基线跑起来,后续通过增强和自采扩充到 1500 张左右,效果会有明显提升。核心原则是“宁缺毋滥”,一张框得不干净的图比十张干净图的破坏力还大。
2.2 标注格式选 VOC 还是 COCO:影响后面所有代码
标注格式直接决定你用哪套工具、跑哪个框架、后处理要不要写转换脚本。目前在舌苔检测里最常见的是两种:VOC 的 XML 格式和 COCO 的 JSON 格式。
| 格式 | 视觉标注工具 | 对 YOLO 系列的适配 | 适合场景 |
|---|---|---|---|
| VOC XML | LabelImg | 需要转成 YOLO 的 txt | 数据量小、想逐文件检查 |
| COCO JSON | labelme、X-AnyLabeling | 很多框架原生支持 | 数据量大、需要统一管理 |
毕设场景我一般推荐直接标 COCO,因为 YOLO 系的框架对 COCO 的适配最顺,后处理也少。但很多同学早期已经用 LabelImg 标了一批 XML,这时候不要手工重标,写一个脚本把 XML 转成 YOLO 训练用的 txt 即可。下面是常用的转换脚本,按“类别名到 id 的映射 + 归一化坐标”输出。
import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, out_dir, class_map): tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find('size/width').text) img_h = int(root.find('size/height').text) out_lines = [] for obj in root.iter('object'): cls = obj.find('name').text if cls not in class_map: continue # 跳过不在类别表里的标签 bnd = obj.find('bndbox') x1 = float(bnd.find('xmin').text) y1 = float(bnd.find('ymin').text) x2 = float(bnd.find('xmax').text) y2 = float(bnd.find('ymax').text) x_center = ((x1 + x2) / 2) / img_w y_center = ((y1 + y2) / 2) / img_h box_w = (x2 - x1) / img_w box_h = (y2 - y1) / img_h out_lines.append( f"{class_map[cls]} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}" ) out_file = os.path.join(out_dir, os.path.basename(xml_path).replace('.xml', '.txt')) with open(out_file, 'w', encoding='utf-8') as f: f.write('\n'.join(out_lines)) if __name__ == '__main__': # 类别名需要和标注时保持一致 class_map = {'coating': 0, 'no_coating': 1} voc_to_yolo('annotations/001.xml', 'labels/train', class_map)这个脚本的逻辑不复杂:先把 XML 里的绝对坐标读出来,除以图片宽高归一化,再按“类别 id 中心点 x 中心点 y 宽度 高度”的格式写进 txt。需要注意class_map必须和训练配置里的names顺序一致,否则训练出来的预测结果会张冠李戴。另外,YOLO 要求 txt 文件和对应图片同名,转换时不要改动文件名主体,只替换扩展名。
2.3 基线模型选型:YOLOv8 还是 RTMDet
数据定完后,模型选型要结合你的机器和毕设周期。很多同学一上来就想堆最重的模型,其实舌苔检测的难点不在“网络不够深”,而在“数据噪声太大”。
如果机器只有一张 8GB 显存的显卡,首选 YOLOv8s 或者 YOLO11n。这两个模型在单卡上训练速度快,官方预训练权重对特征提取的初始化帮助很大,能让你在两周内看到可演示的结果。RTMDet 的精度上限稍高,尤其在多尺度目标上更强,但它依赖 mmdetection 那套环境,版本兼容问题多,光配环境就能耗掉好几天。我自己的习惯是:毕设阶段用 YOLOv8s 跑通全流程,时间富余再去试 RTMDet 或者 YOLO-seg 做对比实验。
| 模型 | 参数量级 | 显存压力 | 上手难度 | 舌苔检测适配度 |
|---|---|---|---|---|
| YOLOv8n | 小 | 低 | 低 | 能跑,轻微漏检 |
| YOLOv8s | 中 | 低 | 低 | 推荐基线 |
| RTMDet-s | 中 | 中 | 高 | 上限略高,环境折腾 |
还要说一个容易被忽视的点:舌苔检测的“目标”到底框什么。有人框整个舌头,有人只框舌苔覆盖区域,两者任务难度完全不一样。框中舌头的做法更稳,因为舌头边界相对清晰;只框舌苔区域则要面对舌苔和舌体颜色接近、边界模糊的问题,模型容易学成“纹理分割”。建议基线阶段只做“舌头区域检测 + 粗分类”,把“舌苔面积占比”这类细粒度任务放到第 6 章讲的进阶方向里。
3. 用 YOLOv8 在本地跑通舌苔检测的最小训练流程:环境、命令与收敛判断
选好基线和数据,下面进入最容易被卡住的环节:本地环境配置和训练命令。舌苔检测的输入尺寸不用太大,640 分辨率完全够用,关键是数据目录写对、训练参数别太激进。
3.1 环境准备与数据目录组织
环境方面,最省心的组合是 Python 3.10 + ultralytics。下面是创建环境的常用命令。
conda create -n tongue python=3.10 -y conda activate tongue pip install ultralytics # 验证安装是否成功,能正常输出版本号即可 python -c "import ultralytics; print(ultralytics.__version__)"这里不需要手动装 PyTorch 和 CUDA,ultralytics 会拉起当前环境需要的 torch 版本。如果你机器里有 NVIDIA 显卡,建议单独确认一下torch.cuda.is_available()是不是 True,否则训练会默认走 CPU,速度慢到怀疑人生。数据目录我建议固定成下面的结构,这是 YOLO 系最不容易出错的布局。
data/tongue/ ├── images/ │ ├── train/ # 600 张 │ └── val/ # 80 张 ├── labels/ │ ├── train/ # 和 images/train 同名 │ └── val/ # 和 images/val 同名 └── tongue.yaml # 数据集配置目录结构的坑在于:YOLO 训练时只认 images 和 labels 的对应关系,不认子文件夹里的类别归档。也就是说你不能在 labels/train 里建“coating”“no_coating”子目录,所有 txt 必须平铺在 labels/train 下,txt 内容里的第一个数字才是类别 id。
3.2 训练命令与关键参数:轮数、批次、分辨率这么定
训练前先写好数据集配置文件 tongue.yaml,内容如下。
# data/tongue/tongue.yaml path: /home/user/data/tongue # 改成你的实际路径 train: images/train val: images/val nc: 2 names: 0: coating 1: no_coating配置文件的path是根目录,train和val是相对于根目录的子路径。这里容易踩的坑是把train写成绝对路径,或者把图片后缀也写进去,都会导致训练时报 Dataset not found。nc是类别数,必须和标签文件里的最大类别 id 加一对上,否则推理时类别名是空的。
然后执行训练,这是最常用的最小命令。
yolo detect train \ data=data/tongue/tongue.yaml \ model=yolov8s.pt \ epochs=150 \ imgsz=640 \ batch=16 \ device=0 \ patience=20 \ cache=True参数解释如下:model=yolov8s.pt表示加载官方预训练权重继续训练;epochs=150是最大训练轮数,配合patience=20表示验证集指标连续 20 轮不提升就提前停止;imgsz=640让图片统一缩放到 640×640;batch=16在 8GB 显存下基本能跑动,如果爆显存就先降到 8,不要改imgsz。cache=True是把数据集提前缓存到内存里,第一次跑会慢几分钟,之后每轮训练速度会明显变快。
3.3 一轮训练后怎么判断有没有收敛
训练跑完,去runs/detect/train目录下看几个关键文件。results.png里画了 box_loss、cls_loss、dfl_loss 三条曲线,如果三条曲线都趋于平坦且没有在最后几轮突然抬头,说明训练基本收敛。confusion_matrix.png能直观看到舌苔类别之间有没有互相混淆,如果“coating”被大量错分成“no_coating”,说明类别不平衡或者标注不一致。
还有一个文件容易忽略:val_batch0_pred.jpg。这是验证集第一张图在训练结束后的预测结果,框画得准不准、有没有漏框,肉眼看比指标更直接。我每次训练完都会先翻这张图,如果明显发现“框到了嘴唇上”这种低级错误,说明训练集里混了不合格标注。后续不再加轮数,而是回过去清洗数据。
4. 舌苔检测避坑:五个翻车场景与对应解法
舌苔检测的翻车点和通用检测不完全一样。下面这几条都是做这类题目时反复出现的坑,按“现象→原因→解决”列出来,希望能帮你少走弯路。
4.1 口腔暗光环境下漏检严重
现象:模型在打光均匀的训练图上表现很好,但拿到真实手机拍摄、暗光或者逆光的图片,舌头区域直接漏检。
原因:训练集太“干净”,全是受控光照下拍摄的图片,模型学到的是亮度分布而不是舌头的结构特征。深度学习模型在这种任务上很容易把亮度当捷径,暗光对它来说是一种没见过的分布。
解决:在训练集里加入曝光扰动数据增强,并专门采一批暗光照片放进训练集。YOLOv8 默认的增强策略没有很强的亮度扰动,可以在训练命令里加上hsv_v=0.5这类参数,或者直接把暗光图少量复制进训练集,让模型见过这种分布。
4.2 苔色类别严重不均导致过拟合
现象:黄苔样本很多、少苔样本很少,训练出的模型对少苔图片基本都预测成多数类,精确率很高但召回率很低。
原因:类别不平衡是老问题,舌象数据里不同苔质出现频率天然不均,教练和自采都难做到均衡。
解决:先做数据层面的多采样,把少类图片复制几份放入训练集,或者用拼接增强的方式让少类样本以不同背景出现。如果数据实在补不齐,再考虑在损失函数里给少数类加权。对毕设来说,用简单的“按类别比例重复少类样本”就够了,不需要上复杂算法,答辩时也能讲清楚。
4.3 随机旋转增强把舌头转成“横着长”
现象:训练集里出现大量旋转 90 度、舌头横在画面里的样本,真实场景里几乎不可能出现,模型收敛变慢。
原因:YOLO 默认的数据增强里有随机旋转,对通用物体问题不大,但舌头是人体器官,姿态有天然的物理约束。旋转角度太大相当于在制造噪声。
解决:在训练配置里限制旋转角度。用 ultralytics 的话,可以直接构造一个自定义的data_augment参数,或者简单一点,在训练前对图片做水平翻转和 ±15 度以内的随机旋转,不开 90 度级别的增强。本质上就是让增强后的样本仍然符合舌头的真实拍摄姿态。
4.4 验证集指标最好,真实测试却一塌糊涂
现象:训练时 val 集 mAP50 到了 0.9 以上,但拿自己预留的一批真实手机照一测,漏检和误检都很严重。
原因:验证集和训练集来自同一个数据源,分布太接近。代码里随机拆验证集时,同一批人的不同照片可能同时进了训练集和验证集,模型相当于见过答案。
解决:采集时按“人”划分数据,同一个人的所有照片要么全进训练集,要么全进验证集,不要随机打乱。最后再单独留 30 到 50 张完全没参与过训练和验证的照片做测试集。这个坑在舌苔检测里尤其常见,因为数据集往往只有几百张,随机划分很容易泄题。
4.5 推理时预处理和训练不一致
现象:训练时 mAP 正常,导出模型后单独写 Python 脚本推理,检测框整体偏移或者小目标丢失。
原因:YOLO 训练和推理时默认会对图片做 letterbox 等比缩放,同时把边长补到 640 的倍数。自己写 OpenCV 推理时如果直接cv2.resize到 640×640,会破坏图片长宽比,导致框坐标偏斜。
解决:推理时不要自己 resize,直接用 ultralytics 的model.predict()接口,它会自动做 letterbox。如果非要手写预处理,需要把 letterbox 的 padding 参数记录下来,在还原框坐标时减回去。这类问题属于典型的“训练一条龙,部署一条虫”,排查时先打印预处理后的图片尺寸和模型输入尺寸是否一致。
5. 把留档 zip 变成可演示的交付:ONNX 导出、推理脚本与界面
毕设交付和实验室 Demo 的区别在于:你必须让导师或者评审老师能直接看到效果。这一步的核心是导出模型、写一个稳定推理脚本、再套一个简单界面。很多项目 zip 解压后跑不起来,问题都出在交付物组织混乱。
5.1 导出 ONNX:把 PyTorch 权重变成通用模型文件
训练完的.pt权重只能在 PyTorch 环境里跑,评审老师的电脑上不一定有同样的环境。常见做法是用 ultralytics 自带的导出命令,把权重转成 ONNX 格式。
yolo export \ model=runs/detect/train/weights/best.pt \ format=onnx \ opset=12 \ dynamic=True \ simplify=Trueformat=onnx指定导出格式,opset=12是 ONNX 算子版本,一般不用调太高。simplify=True会调用 onnxsim 对图结构做简化,去掉一些冗余节点,推理时能快一点点。dynamic=True允许动态输入尺寸,但如果你后面要接 TensorRT 或者写 C++ 部署,动态尺寸会带来额外麻烦,导出时建议先固定 640×640,等部署流程跑通了再考虑动态。
导出后同一个目录下会生成best.onnx。注意这个文件不包含类别名,推理时需要自己维护一份类别映射表,这个信息容易在留档 zip 里丢,建议写在 README 里。
5.2 最小推理脚本:单张图片和视频流都能跑
ONNX 模型既可以用 ONNX Runtime 推理,也可以直接用 ultralytics 的接口加载。用 ultralytics 的好处是它能自动完成 letterbox 预处理和坐标还原,下面是单张图片推理的脚本骨架。
from ultralytics import YOLO import cv2 # ONNX 模型不内嵌类别名,这里手动指定 model = YOLO('runs/detect/train/weights/best.onnx') model.names = {0: 'coating', 1: 'no_coating'} img = cv2.imread('test_tongue.jpg') results = model(img, conf=0.35, iou=0.45, imgsz=640) for r in results: for box in r.boxes: x1, y1, x2, y2 = box.xyxy[0].tolist() cls = int(box.cls[0]) conf = float(box.conf[0]) label = f'{model.names[cls]} {conf:.2f}' cv2.rectangle(img, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) cv2.putText(img, label, (int(x1), int(y1) - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) cv2.imwrite('output.jpg', img)代码里conf=0.35表示置信度低于 0.35 的框会被过滤掉,iou=0.45是 NMS 的 IoU 阈值。这两个参数在舌苔检测里值得单独调:如果漏检多就把conf降到 0.25,如果同一个舌头出现三四个重叠框就把iou调到 0.5。对于视频流,把循环里的图片换成摄像头帧,每处理一帧显示结果即可,代码思路完全一样。
5.3 用 Gradio 快速搭一个演示界面
如果不想写太多前端代码,Gradio 是性价比最高的方案。一个函数调用就能生成网页端演示界面,本地跑起来后局域网内也能访问,答辩时直接现场截图或者录屏都可以。
import gradio as gr from ultralytics import YOLO model = YOLO('runs/detect/train/weights/best.onnx') model.names = {0: 'coating', 1: 'no_coating'} def detect(img): results = model(img, conf=0.35, iou=0.45) # results[0].plot() 会生成带标注框的图像 return results[0].plot() gr.Interface( fn=detect, inputs=gr.Image(type='numpy'), outputs=gr.Image(type='numpy'), title='基于深度学习的舌苔检测演示' ).launch()gr.Image(type='numpy')会把上传的图片转成 NumPy 数组传给函数,results[0].plot()返回的是画好框的 BGR 图像,Gradio 会对输出做格式转换。这个界面虽然简陋,但足够支撑“现场上传一张图、立刻看到检测结果”的演示需求。如果你的电脑没有显卡,ONNX Runtime 的 CPU 推理在这个场景也够用,单张图一般在零点几秒到一两秒之间。
到这里,一个完整的交付 zip 应该包含:训练权重 best.pt、导出的 best.onnx、推理脚本、Gradio 演示代码、数据配置 yaml 以及一份说明文档。文档里写清楚 Python 版本、依赖安装命令和启动方式,别把依赖版本藏在一堆代码里,这是留档 zip 最有价值的“后悔药”。
6. 答辩前的最后验证:指标口径、误检归因与三个进阶点
答辩前我习惯做三件事:重新算一遍测试集指标、给误检案例归因、想清楚一个能讲明白的进阶方向。这三件事都做完,这个题目才真正算闭环。
第一件事,指标口径。舌苔检测里最常用的是 mAP50 和 mAP50-95。mAP50 只要求预测框和真实框的 IoU 超过 0.5 就算命中,对毕设足够友好;mAP50-95 则考核更严,框稍微偏一点就会掉分。如果 mAP50 高但 mAP50-95 低,说明模型“大概能框中舌头,但边界不精细”,这在舌苔区域边缘模糊的情况下很常见,不用慌,能解释清楚就是加分点。另外建议报一组 Precision 和 Recall,医疗辅助场景里漏检比错检更危险,所以我会倾向于把置信度阈值调低一点,保 Recall。
第二件事,误检归因。把测试集里所有错图分成三类:漏框、框错位置、框得过大或过小。漏框多一般说明目标太小或者对比度低;框错位置很可能混入了“照到牙齿”的坏标注;框得过小则往往是因为标注时只框了舌苔中心区,没有包含整个舌头。这类归因在答辩时特别有说服力,比单说一句“模型精度达到 0.9”扎实得多。
第三件事,进阶方向。舌苔检测最自然的三个延伸点是:用 YOLO-seg 做舌苔像素级分割,直接输出舌苔和舌体的面积比;把苔色细分为黄、白、灰等多个类别,配合类别加权损失训练;对输出置信度做校准,让置信度数字更接近真实概率。你不用全做,挑一个做一个对比实验就行。我的教训是,别把进阶点塞进同一个模型里,比如一边加分割一边加细分类,最后指标波动时根本说不清是哪个改动起了作用。
做这个题目最有价值的收获,是一套从数据采集、标注、训练到交付的完整链路。你自己亲手跑完一遍,下次遇到任何检测类题目都不会慌。希望这篇记录能帮到你,也祝你的舌苔检测毕设一次跑通。
本文还有配套的精品资源,点击获取