尧图精选

深度学习舌苔检测系统实战:从数据预处理到YOLOv8+ResNet落地

🕒 发布时间:2026/10/1 23:53:48 📁 来源:尧图网络
简介该资源为一套完整的深度学习舌苔检测系统项目主要面向计算机视觉方向的高校学生与科研人员适用于人工智能、电子信息、自动化等专业的毕业设计或课程设计场景。项目以Python为主要开发语言集成PyTorch训练与推理链路核心任务是利用卷积神经网络对舌苔图像进行识别与分类具备从数据预处理、模型训练到可视化界面的完整流程。压缩包共110个文件包括26个Python源码文件、6个pth模型权重文件、5个json配置、2个ui界面文档以及论文、开题报告等docx文档总体积约105.46MB训练图像与TensorFlow事件记录文件同步提供便于研究训练过程与模型调优。当前已有71人学习下载适合需要参考完整项目方案、撰写论文或快速搭建检测原型的读者使用可直接在现有代码基础上修改扩展。1. 收到“深度学习舌苔检测系统含开题报告论文.zip”先别急着跑先看它值不值得复现每年论文季总有人手里捏着这样一个压缩包深度学习舌苔检测系统含开题报告论文.zip。它既不是拿来就能跑的“一键项目”也不是纯粹的“交差作品”。舌苔检测这个方向在中医信息化和基于深度学习的口腔疾病图像识别系统里是典型的“医生看着费眼、标注又贵、模型却意外好骗”的场景。传统中医舌诊靠医生肉眼看苔色、苔质主观且难量化而用深度学习模型把舌体从照片里框出来再对舌苔颜色分类是能写满一篇论文、也能真落成演示系统的闭环项目。适合谁视觉方向做毕设的学生以及想验证目标检测加图像分类双任务在医疗图像上跑不跑得通的一线工程师。这个包好不好不在于代码里贴了多少层卷积而在于你能否把它变成一份能讲清“为什么这么设计、怎么调、怎么反驳答辩老师”的实证。2. 数据与预处理是决定成败的上游工程舌象数据集如何切、裁、增强拿到这种项目的第一件事不是打开模型代码而是先整理数据。这块做不好后面所有训练结果都是空中楼阁。2.1 舌苔图像的三个脏数据来源光照、舌体区域切片、标注噪声先看数据从哪来。常见做法是老师给一台相机去门诊拍几百张舌头照片或者从开放数据集找一部分每张图有人手工用矩形框标注过舌体。这两种来源各有各的问题。第一类脏数据是光照。门诊拍摄环境不像实验室可控有的照片在口腔灯直射下拍舌面出现大片高光高光区域的颜色完全发白。如果这些图片落在训练集里深度学习模型会把“亮斑”和“白苔”学在一起等推理时遇到真实白苔反而因为缺少高光而犹豫。这就是模型被脏数据欺骗的最直接解释。第二类是舌体区域切片。常见做法是先用目标检测把舌体框出来再裁剪把裁剪结果交给分类网络。这一步最怕框歪框往上偏一点图中就多出一排牙齿往下偏一点就会带上嘴唇阴影。分类网络并不具备“自动忽略背景”的能力它会抓取牙齿的白色纹理当特征而测试集里一旦出现露齿样本分类结果就跟着崩。第三类是标注噪声。舌苔分类的标签本来就主观黄苔和白苔的分界线不是几何边界医生不同、标准不同标签就会抖。一个黄苔和灰苔都沾边的小样本被标成某一类模型被迫在这个样本上死记硬背这会让它在真实数据上复现错误的判断。预处理不能消除标注噪声但可以在增强阶段用较低的颜色扰动守住边界不让噪声被进一步放大。一句话结论预处理阶段决定这个系统能不能跑过答辩模型结构反而不是最危险的一环。2.2 用albumentations做数据增强的最小可运行配置以分类任务为例先把增强管线搭起来。下面这套配置是小数据集上最常用的一套每一行都踩过坑。import albumentations as A import cv2 # 适用于舌苔分类任务的最小增强集 train_transform A.Compose([ A.HorizontalFlip(p0.5), # 舌体近似左右对称可安全翻转 A.Rotate(limit15, p0.6), # 小幅旋转模拟采集时头部轻微歪斜 A.ColorJitter( brightness0.15, contrast0.15, saturation0.15, p0.5 ), A.RandomBrightnessContrast( brightness_limit0.1, contrast_limit0.1, p0.5 ), A.Resize(height224, width224) ]) # 验证集只做Resize不做任何随机增强 valid_transform A.Compose([ A.Resize(height224, width224) ])逻辑说明这套管线把几何扰动和颜色扰动分开控制。Rotate的limit只给到15度因为舌体被裁出来之后旋转超过15度就会把舌尖旋出画面裁剪区域混入嘴唇或牙齿HorizontalFlip对舌体是安全操作但前提是样本里没有明显的“舌尖朝左”和“舌尖朝右”的语义区别否则不建议开。ColorJitter的saturation控制在0.15以内舌苔分类极度依赖颜色黄苔和白苔的差距核心就在色相饱和度或亮度抖动过狠会让黄苔样本在增强后变成白苔样本这种自我矛盾的数据比不增强更危险。参数说明p是概率不是幅度。0.5的概率意味着批量训练时约一半样本不做该变换另一半做这个比例能防止整个batch的分布被过度平滑。RandomBrightnessContrast的brightness_limit0.1比ColorJitter的0.15更保守原因是舌苔图像的高光区域在亮度增强后会产生假白斑。另外不要在增强管线里加GaussianBlur或GaussianNoise舌苔纹理本来就弱一旦模糊厚苔和薄苔的分界会彻底消失模型只能靠颜色猜。提示训练集和验证集必须走不同管线。验证集只做Resize随机增强一旦落到验证集上验证准确率会上下乱跳你无法判断是模型在进步还是数据在干扰。另一个常见做法是把舌体从矩形框里再抠成圆形只保留舌面中央区域丢掉四周的唇齿阴影。这个方法确实能提升分类准确率但代价是引入新的切割误差圆形掩码的边缘一旦切到舌根厚苔区域会被切掉一半分类结果反而失真。我一般保留矩形框只在预处理里把图像四周等比裁掉5%再缩放到224。这个方案实现简单也不引入新的误差源作为深度学习实战项目案例来说性价比最高。2.3 按类别切分数据集固定随机种子与类别均衡参数拿到数据之后第一件事不是建模是切分。舌苔数据集的类别不均衡往往很严重白苔样本常是黄苔的5到8倍灰苔和厚苔的数量更少。如果全局随机切分稀有类别可能整个掉进训练集或验证集验证指标的波动会大得让对比实验没法看。import random import shutil from pathlib import Path random.seed(42) # 固定随机种子保证论文实验可复现 src Path(data/tongue_all) train_dir Path(data/train) valid_dir Path(data/valid) for cls in [white, yellow, gray, thick, thin]: images list((src / cls).glob(*.jpg)) random.shuffle(images) valid_size int(len(images) * 0.2) valid images[:valid_size] train images[valid_size:] (train_dir / cls).mkdir(parentsTrue, exist_okTrue) (valid_dir / cls).mkdir(parentsTrue, exist_okTrue) for img in train: shutil.copy(img, train_dir / cls / img.name) for img in valid: shutil.copy(img, valid_dir / cls / img.name)逻辑说明这个脚本的核心是“按类别分层切分”而不是对整个文件夹一次性shuffle。以白苔和黄苔为例白苔300张、黄苔40张全局切分后验证集可能出现白苔60张、黄苔8张黄苔类别统计上的置信区间极大分层切分后至少保证每个类别都按同比例进验证集比例虽然仍不均衡但分布与训练集一致。参数说明random.seed(42)是复现实验的第一步论文里写“数据集以固定随机种子按8:2分层切分”他人就能还原你的划分。0.2的验证比例对医疗小数据集属于保守值如果总数只有300张建议换成5折交叉验证而不是把验证比例降到0.1。交叉验证时同样要按类别分层否则每一折的类别比例都不一致五折的均值会非常不稳定。跑这些脚本前先把深度学习环境配置好用Miniconda建虚拟环境Python 3.8以上pip install albumentations opencv-python torch torchvision。CPU也能完成增强和预处理并不依赖GPU。如果是租用服务器跑深度学习记得把数据集和代码放在同一块数据盘上避免训练时反复跨节点读写图像文件。从预处理进入模型之前先对切分后的图像做一次人工巡检随机抽50张训练图和20张验证图用matplotlib拼成网格看看有没有切错类别、有没有带着牙齿、有没有全图高光。这一遍巡检花不了半小时但能避免后边训练出来的模型解释不清为什么对某张图判断错误。3. 用YOLOv8搭检出、ResNet管分类两阶段方案与训练脚本数据管干净之后才轮到模型选型。这里最常见的落地方案是两阶段流水线先用YOLOv8把舌体框出来再把裁剪区域交给ResNet分类。3.1 先检出舌体再分舌苔两阶段方案为什么比端到端稳舌苔检测系统有两个任务把舌体从照片里框出来给框出来的舌面分类。把两个任务串成一条流水线落地最稳。为什么不用端到端分类直接让分类网络看整张图模型不知道应该看舌头哪个部位。牙齿的釉白和舌苔的白在图像上都是“白色高纹理区域”分类网络会把牙齿统计进“白苔支持区”嘴唇的红色区域则会干扰黄苔判断。两阶段方案把定位和分类解耦检测模型负责把舌头从背景里拆出来分类模型只学舌头上的纹理和颜色背景干扰被裁剪环节挡掉。为什么不用单阶段检测直接输出类别比如把苔色类别直接加进YOLO的class列表一个框同时给出位置和苔色。因为工程上数据不平衡问题会被放大。YOLO的损失函数在一个batch里平衡分类损失和定位损失舌苔类别严重不均衡时模型更倾向于把稀有类别忽略边界框回归得再准也没用。两阶段方案允许单独为分类阶段做类别重采样和loss加权处理不均衡的手段多得多。下表是选型时的对比视角方案背景干扰类别不均衡处理训练成本答辩说服力端到端CNN分类高模型自己找特征只能靠loss加权低一般单阶段检测类别并入检测框低难单独处理中较弱两阶段检测分类低可分别重采样中高强能画出两条pipeline图答辩时评审老师大概率会问“为什么不直接检测分类一步到位”上面这张表就是答辩底稿。两阶段看着多绕一层但每一级的错误来源都可解释这对医疗场景非常重要。3.2 用预训练权重微调的命令与关键超参检测阶段用YOLOv8做微调因为它的生态最省事。先写一个舌体检测的数据配置再给训练命令。# tongue_detect.yaml path: dataset/tongue_detect train: images/train val: images/valid names: 0: tongueyolo detect train \ modelyolov8n.pt \ datatongue_detect.yaml \ imgsz640 \ epochs80 \ batch16 \ lr00.005 \ optimizerAdamW \ projectruns/ \ nametongue_yolov8逻辑说明这里选了yolov8n的预训练权重n是nano版本参数量最小。舌体在照片里占的面积很大检测本身的难度不高nano级别的模型容量足够换s或m版本只会让训练时间翻倍边际收益很小。imgsz640是通行默认值舌体检测不必用1280大图边缘模糊的舌体区域不需要太精细的边界。lr00.005对微调目标检测属于温和值预训练权重里的特征不会被快速破坏。如果训练集只有几百张把epochs降到50到60再配一个早停patience10到15防止在验证集上过拟合。参数说明batch16在8G显存下配合imgsz640刚好压线显存不够时优先降imgsz到512而不是降batch低batch会让BN层的统计噪声变大训练不稳定。AdamW比SGD在检测任务上收敛快配合余弦学习率衰减后半段用小学习率微调网络细节。另一个容易被忽略的是modelyolov8n.pt这个文件会在第一次训练时自动下载如果服务器连不上官方源提前在本地把权重下载好放进项目目录避免训练跑到一半因为网络问题挂掉。分类阶段用ResNet18微调脚本如下import torch import torch.nn as nn from torchvision import models device torch.device(cuda if torch.cuda.is_available() else cpu) # 5类舌苔分类white, yellow, gray, thick, thin model models.resnet18(pretrainedTrue) model.fc nn.Linear(512, 5) model model.to(device) cls_counts torch.tensor([320.0, 60.0, 40.0, 120.0, 100.0]) class_weights 1.0 / cls_counts class_weights class_weights / class_weights.sum() # 归一化到和为1 criterion nn.CrossEntropyLoss(weightclass_weights) optimizer torch.optim.AdamW( model.parameters(), lr1e-4, weight_decay1e-5 )逻辑说明先把ResNet18最后一层的全连接层从1000类换成5类输出维度对应五个苔质类别。然后设置类别权重做法是“反向频率”样本数多的白苔权重低样本数少的灰苔权重高。CrossEntropyLoss拿到weight参数后计算损失时会给稀有类别更大的梯度让模型不直接忽略它。但要注意类别权重不会凭空造出信息如果灰苔总共只有40张且存在标注噪声模型大概率仍会把它学成一个“胆小”的类别推理时置信度普遍偏低。这种情况要回去补数据而不是继续加权重。参数说明AdamW的lr1e-4搭配weight_decay1e-5属于分类任务微调的常用区间学习率再调高预训练特征很快被打乱。常见的训练策略是先冻结backbone只训fc层10个epoch让新的分类头先收敛第11个epoch解冻backbone把整体学习率下调到1e-5继续。冻结backbone的写法是把model.requires_grad_(False)再把model.fc.parameters()设为requires_gradTrue。保存权重时不要看准确率要看验证集macro-F1best_f1 0.0 for epoch in range(100): train_one_epoch(model, train_loader, criterion, optimizer) val_loss, val_f1 evaluate(model, valid_loader, criterion) if val_f1 best_f1: best_f1 val_f1 torch.save(model.state_dict(), best_resnet18.pt) if epoch 20 and val_f1 best_f1 - 0.05: print(early stop) break逻辑说明以验证集macro-F1而不是准确率作为保存权重的依据是因为类别不均衡下准确率会骗人。f1是精确率和召回率的调和平均稀有类别被忽略时macro-F1会立刻掉下来。早停条件是连续多个epoch里F1没有创新高这个触发时机要等训练至少跑完20个epoch再做判断避免前几个epoch的随机波动触发误停。3.3 开题报告和论文里最值钱的三个图loss曲线、混淆矩阵、PR曲线这个压缩包里既然带开题报告和论文那写报告就是硬需求。论文里最值钱的三张图不是网络结构图而是这三张。第一张是训练集和验证集的loss曲线。横轴epoch纵轴loss训练loss持续下降但验证loss在第30个epoch后反弹这就是过拟合的可视化证据。论文里写“采用早停策略最终选择在第28个epoch保存权重”配合曲线图就非常有说服力。第二张是混淆矩阵。它比准确率能多讲太多东西白苔和黄苔互相混淆说明颜色边界模糊灰苔经常被分成厚苔说明分类模型把“颜色暗”和“厚度大”两个特征学混了。答辩时能指着混淆矩阵说“这两个类别的误差主要来自标注边界而不是模型结构”评审老师就明白你真正分析过数据。第三张是PR曲线尤其当验证集类别不均衡时。白苔样本多准确率自然高PR曲线能展示稀有类别在低置信度阈值下到底有多少误检。开题报告里写“在灰苔类别上mAP0.5达到0.72”比写“总体准确率93%”更经得起追问。至于深度学习对比试验怎么做这是毕设里最常见的疑问。做法是控制成一个变量baseline用直接对原图做分类的ResNet实验组用“YOLOv8检测裁剪ResNet分类”第三组是“YOLOv8检测但只resize不裁剪”用来验证裁剪的作用。三组实验用完全一样的切分方式、随机种子、epoch数和batch任何超参都不要动。跑完放一张表格列准确率、macro-F1和单张推理时间对比试验这部分就稳了。4. 避坑训练舌苔检测模型时绕过这五个坑能省两周时间4.1 过拟合loss降了、验证集准确率卡在60%不动现象训练集loss一路降到0.05训练集准确率到98%验证集loss在第25个epoch开始反弹准确率卡在60%附近不动。原因舌苔数据集太小模型容量相对过剩。ResNet18在小数据集上很容易把白苔样本的“光泽”直接记住而不是学习“苔色”这种真正稳定的特征。解决先冻结backbone只训fc层观察验证集准确率是否能上到80%以上能上说明特征提取没问题继续解冻微调上不去就要怀疑标签噪声太大。再配合增强管线降低过拟合把Rotate的p从0.6提高到0.8并且添加RandomGamma的gamma_limit(80,120)模拟不同曝光。还是压不住就换更小的模型换成MobileNetV3-Small比换成ResNet34更合理后者只会加重过拟合。看到验证loss反弹时先拉出每个epoch的完整日志看一眼如果训练loss仍在下滑而验证loss反弹说明模型在死记训练集如果验证loss从第5个epoch就开始震荡不降那是学习率偏大或标签噪声太重和过拟合无关。4.2 类别不均衡模型把整张验证集全猜成白苔现象训练完成后验证集准确率显示75%但打开混淆矩阵一看黄苔、灰苔、厚苔所在行的召回率接近0模型几乎把所有样本都预测成白苔。原因准确率在这里是“骗人”的指标。白苔占比本来就高全猜白苔也能拿70%以上准确率。损失函数没有对稀有类别做任何补偿。解决用class_weights反向频率加权重训分类模型另一个立竿见影的做法是过采样把黄苔和灰苔的样本在训练集里复制两到三份配合light增强让重复样本不完全相同。注意过采样只能在训练集做验证集必须保持原始分布否则验证指标会虚高。额外提醒过采样后的数据要打乱顺序再送进DataLoader否则同一个样本会连续出现多次造成batch内分布单一BN层统计被带偏。4.3 光照伪影暗光样本把“黄苔”识别成“灰苔”现象训练时验证集指标还过得去一到部署现场换了一个采集设备拍出来的照片偏暗偏黄模型的黄苔、灰苔输出置信度开始乱跳甚至把正常舌苔预测成灰苔。原因训练集来自固定门诊环境模型学到的颜色统计是“这台相机下的”。新设备的白平衡、曝光曲线完全不一样颜色分布整体移位。解决部署前做一次色彩归一化用标准色卡校正或者直接在训练阶段把ColorJitter的brightness范围从0.15扩大到0.25并加RandomGamma模拟不同相机的gamma曲线。如果条件允许用目标设备拍50张真实样本加入训练集做微调这是治本的方法。实际场景换个设备就翻车原因就是数据域没有对齐这类问题靠调模型结构是解决不了的。4.4 旋转增强时只转了图没转标注框训练直接崩掉现象用YOLOv8做检测训练loss曲线在某个epoch后突然跳升或者训练loss一直降不下去上下震荡。原因训练前用自定义脚本把图像旋转了但bounding box没有同步旋转。旋转后的框和舌头位置不对齐检测模型一直在学一个“盒子”和“图形”对不上的错误对应关系。解决检测任务不要手写增强直接用ultralytics内置的增强管线或者用albumentations的BboxParams同步变换。写法是import albumentations as A transform A.Compose( [A.Rotate(limit15, p0.6)], bbox_paramsA.BboxParams(formatyolo, label_fields[class_labels]) )逻辑说明加上bbox_params之后旋转图像时标注框会跟着旋转并自动做越界裁剪。旋转增强后要检查有没有框被裁出图外albumentations会把完全出界的框标记为None这批样本要丢弃不能带着空标签进模型。训练前用可视化脚本把增强后的图和框画出来逐张看一编能挡掉大半这类无语的bug。4.5 推理环境不匹配CPU上跑一张图要等10秒现象训练用的服务器是GPU模型跑起来很流畅换到现场的笔记本或CPU服务器上一张640乘640的图推理时间超过10秒根本没法做演示。原因训练时为了指标选了YOLOv8s或m版本这些模型在CPU上没有优化卷积计算量大推理慢。解决演示环境用yolov8n配合imgsz512推理时间通常能降一半以上。再把模型导出为ONNX格式并用onnxruntime推理CPU上的速度还能再快一截。更激进的做法是INT8量化但INT8量化后舌苔分类的准确率容易掉建议只对检测模型量化分类模型保留FP32反正分类模型小推理不是瓶颈。这五个坑会在这类项目的血泪经验里反复出现。如果你在训练中遇到验证集指标上下剧烈震荡先别怀疑模型结构回到数据管线里查找是不是切分种子没固定或是验证集也加了随机增强。这类“玄学”问题绝大多数出在数据管线而不是网络结构上。5. 验证与落地用F1和mAP验收再用ONNX把推理从10秒压到1秒5.1 用F1和mAP验收不看单项准确率训练结束后模型的“准确率”只是热身真正要打印的是每个类别的precision、recall、F1以及检测模型的mAP0.5和mAP0.5:0.95。打印一段这样的结果看white precision 0.93 recall 0.91 f1 0.92 yellow precision 0.68 recall 0.61 f1 0.64 gray precision 0.54 recall 0.47 f1 0.50 tongue detection mAP0.5 0.982如果灰苔的F1只有0.50这不是模型的锅而是数据只有40张的锅。验证集里的灰苔约8张任何模型在8张样本上的F1波动都很大。论文里写这类指标时要标注“灰苔样本量过少结果仅供方向参考”这样评审老师才觉得你知道自己在说什么。另一个验收点是把测试集的错误样本全部打印出来做一次人工复检。错得离谱的大概率是标签标错了而不是模型理解错了反向修正数据集比换模型更有效。5.2 用ONNX导出模型把推理速度压到可演示水平演示系统不能依赖GPU。常见做法是把训练好的检测模型导出为ONNX用onnxruntime在CPU上跑。导出命令yolo export modelruns/tongue_yolov8/weights/best.pt formatonnx opset12再用onnxruntime加载运行import cv2 import onnxruntime as ort session ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) image cv2.imread(tongue_sample.jpg) # 预处理、推理、输出解析的细节在ultralytics文档中有标准实现逻辑说明导出时指定opset12是为了兼容旧版onnxruntime答辩或现场用的电脑上安装的版本往往较旧。导出为ONNX后输入尺寸固定为640乘640如果部署时想提速到512导出时就要把imgsz也改成512而不是在推理代码里临时缩放否则模型输入尺寸不匹配会直接报错。速度目标检测模型在CPU上从10秒压到2秒以内分类模型因为输入只有224乘224本身很快不需要额外优化。这条路的终点不是训完就完事而是做成一个能现场演示的小系统摄像头采集一帧画面YOLO框出舌体裁剪区域送进分类网络画面左上角显示苔色判断和置信度。答辩老师看到这一幕比看十页网络结构图更能相信你真的做出来了。我自己的习惯是演示脚本里加一个“保存输出图片”的按钮每跑一帧就存一张带标注的结果图。这样即使现场摄像头出问题手里还有一叠按真实流程跑出来的图片这是最经得起追问的验证方式。希望这篇笔记能帮你把这个压缩包变成一份真正能讲清楚的毕业设计。还是那句老话先把数据管干净再谈模型结构最后用验证指标守住底线。深度学习这一行模型可以换数据管线才是最容易翻车也最值得投入的地方。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →