基于YOLOv8与Python的舌象诊断系统:从数据标注到Flask部署全流程
简介这份资源面向计算机、人工智能及中医药信息化方向的本科生与研究生提供一套可直接用于毕业设计、期末大作业或课程设计的舌象诊断系统完整方案。项目以Python为开发语言结合YOLO系列深度学习模型完成舌象图像的目标检测与分类识别帮助读者解决从数据标注、模型训练到界面交互的全流程实现问题新手也能借助详细注释快速理解代码逻辑。压缩包共184个文件约42.67MB包含54个py源码文件、61张jpg舌象样本图片、40个pyc缓存文件、14个txt说明与配置、7个json标注数据、2个ui界面文件及2个md文档另附docx学习路线与ttf字体资源覆盖数据集、源码与文档说明三大模块。目前已有390人学习下载项目经严格调试部署后即可运行界面美观、操作简单、功能完善。读者可获得完整可复现的毕设级工程、标注数据集、模型权重与排错思路适合直接作为高分项目参考与二次开发基础。1. 舌象诊断系统落地从 Python 环境到 YOLOv8 推理一条能跑通的链路舌象诊断系统这个词第一次听到的人多半会愣一下——把舌头拍张照让模型告诉你体质偏寒还是偏热听起来像玄学但拆开看其实很实在它本质是一个细粒度图像分类 目标检测的复合任务。舌头区域要先从人脸里框出来再对舌质、舌苔、齿痕、裂纹这些特征做分类。Python 加 YOLO 系列模型恰好是这条链路上最成熟、最容易复现的组合。我见过太多毕设卡在“数据集找不到、标注格式对不上、训练完 mAP 只有 0.3”这三件事上所以这篇不聊虚的直接按“环境怎么搭、数据怎么标、模型怎么训、推理怎么接”走一遍。适合正在做视觉方向毕设、想找一个有业务纵深又不至于太卷的题目的同学也适合想快速验证一个医学图像小闭环的工程师。2. 舌象数据从哪来、怎么标绕开“找不到数据集”这个死结2.1 公开数据集的现实情况与自建方案先说结论目前没有一个拿来就能用的、标注质量过关的公开舌象检测数据集。网上能搜到的所谓“舌诊数据集”要么是几十张的演示图要么是分类标签混乱的爬虫产物。我一般会走两条路一是从中医舌诊相关的公开图库、医学教材配图里收集二是自己用手机在自然光下拍。数量上检测任务每个类别至少 300 张起步分类任务每类 500 张以上才有讨论价值。采集时有个血泪经验光源一致性比数量更重要。早上窗边自然光和晚上暖色台灯拍出来的舌头颜色分布能差出两个色域。如果条件允许固定一个拍摄位置、固定色温哪怕只有 200 张效果也比杂乱光源的 800 张好。舌象诊断系统对颜色极度敏感舌质红和淡红之间的差距在 RGB 空间里可能只有十几个像素值。2.2 标注规范检测框和分类标签怎么定检测阶段只标一个类tongue。别一上来就标“齿痕舌”“裂纹舌”那是分类阶段的事。检测框要紧贴舌头轮廓包括舌根部分但不要把嘴唇和下巴框进去。我见过有人把整个嘴部区域都框上结果模型学到的特征是“嘴唇牙齿”推理时一张嘴就翻车。分类标签建议按中医教材的维度拆舌质颜色淡白、淡红、红、绛、紫、舌苔颜色白、黄、灰黑、舌苔厚薄薄、厚、齿痕有、无、裂纹有、无。每个维度独立一个分类头不要揉成一个多分类。下面是一个标注文件的目录结构示例tongue_dataset/ ├── images/ │ ├── train/ │ │ ├── 0001.jpg │ │ └── ... │ └── val/ ├── labels/ │ ├── train/ │ │ ├── 0001.txt │ │ └── ... │ └── val/ └── classes.txtclasses.txt里只写一行tongue。YOLO 格式的标签文件每行是class_id x_center y_center width height全部归一化到 0-1。转换脚本网上很多但要注意别用那些默认输出 6 位小数的脚本YOLOv8 对精度不敏感但位数太多会让标签文件体积翻倍几千张图下来多出几十 MB 没必要。2.3 数据增强舌象场景下哪些能用、哪些会帮倒忙翻转、旋转、亮度微调可以用。但颜色抖动ColorJitter要慎用尤其是色相偏移。舌质红和淡红的区分全靠颜色你把色相随机偏移 0.1模型直接学废。我一般会把hsv_h设为 0.015 以内hsv_s和hsv_v可以到 0.5 左右。Mosaic 增强在舌象检测上效果不错能提升小目标舌尖区域的召回但最后 10 个 epoch 记得关掉否则框的位置会偏。3. 用 YOLOv8 训练舌体检测器参数怎么设、日志怎么看3.1 环境安装与最小训练命令Python 版本建议 3.9 或 3.10太新的 3.12 在部分 CUDA 版本上会有兼容问题。安装命令如下conda create -n tongue python3.10 -y conda activate tongue pip install ultralytics opencv-python pillow matplotlib # 如果要用 GPU 训练确认 torch 版本和 CUDA 匹配 python -c import torch; print(torch.cuda.is_available())ultralytics这个包已经把 YOLOv8 的训练、验证、推理全封装好了。确认torch.cuda.is_available()返回True再往下走否则你会用 CPU 训到天荒地老。最小训练命令yolo detect train \ datatongue.yaml \ modelyolov8n.pt \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ patience30 \ projectruns/tongue \ nameexp1tongue.yaml内容path: ./tongue_dataset train: images/train val: images/val nc: 1 names: [tongue]逻辑说明yolov8n.pt是 nano 版本参数量小适合毕设这种数据量不大的场景。如果你有 5000 张以上标注图可以换yolov8s.pt。patience30表示 30 个 epoch 验证指标不提升就早停防止过拟合。lr00.01是初始学习率YOLOv8 默认用余弦退火这个值在 batch16 时比较稳。3.2 训练日志里必须盯住的三个指标第一个是box_loss检测框回归损失。正常情况下一百个 epoch 内应该降到 0.5 以下如果一直在 1.0 以上震荡大概率是标注框有问题——要么框太大要么同一张图里有漏标。第二个是mAP50IoU 阈值 0.5 时的平均精度。舌体检测这个任务mAP50 到 0.9 以上才算能用低于 0.8 说明数据质量或标注一致性有问题。第三个是cls_loss分类损失。单类检测时这个值应该很快降到接近 0如果一直不降检查nc是不是写成了大于 1。有个玄学现象验证集 mAP 比训练集还高。别高兴太早多半是验证集太小或者和训练集分布太接近。我一般会从训练集里切 10% 出来做验证而不是单独拍一批这样指标更可信。3.3 推理脚本从图片到舌体裁剪训练完得到best.pt推理代码如下from ultralytics import YOLO import cv2 model YOLO(runs/tongue/exp1/weights/best.pt) img cv2.imread(test.jpg) results model(img, conf0.5, iou0.45) for r in results: for box in r.boxes: x1, y1, x2, y2 map(int, box.xyxy[0]) tongue_crop img[y1:y2, x1:x2] cv2.imwrite(tongue_crop.jpg, tongue_crop)conf0.5是置信度阈值舌象场景建议设高一点宁可漏检也不要误检——把嘴唇裁进去比没裁到舌头更麻烦。iou0.45是 NMS 的 IoU 阈值单类检测时这个值影响不大。裁出来的tongue_crop就是后续分类模型的输入。4. 舌质舌苔分类在裁剪图上接一个轻量分类头4.1 分类模型选型别用 ResNet50用 MobileNetV3 或 EfficientNet-B0舌象分类的数据量通常不大几千张到头了。ResNet50 这种参数量没等收敛就过拟合了。我一般用timm里的mobilenetv3_small_100或efficientnet_b0参数量在 2M 到 5M 之间在 1080Ti 上训 50 个 epoch 只要十几分钟。安装pip install timm分类训练脚本核心部分import timm import torch import torch.nn as nn from torch.utils.data import DataLoader from torchvision import transforms, datasets model timm.create_model(mobilenetv3_small_100, pretrainedTrue, num_classes5) model.to(cuda) transform transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) dataset datasets.ImageFolder(tongue_color_dataset, transformtransform) loader DataLoader(dataset, batch_size32, shuffleTrue) criterion nn.CrossEntropyLoss() optimizer torch.optim.AdamW(model.parameters(), lr1e-4, weight_decay1e-4) for epoch in range(50): model.train() for imgs, labels in loader: imgs, labels imgs.to(cuda), labels.to(cuda) optimizer.zero_grad() loss criterion(model(imgs), labels) loss.backward() optimizer.step()num_classes5对应舌质五分类。weight_decay1e-4是 L2 正则化系数防止过拟合。学习率用1e-4而不是1e-3因为预训练模型已经学好了底层特征微调时步子要小。4.2 多维度分类的组织方式舌质、舌苔颜色、厚薄、齿痕、裂纹这五个维度建议每个维度独立训一个模型而不是一个模型输出所有标签。原因很简单齿痕和裂纹是二分类舌质是五分类混在一起训会让损失函数被大类别主导。独立训的代价是推理时要跑五次但 MobileNetV3 单次推理只要几毫秒总耗时可以接受。如果非要一个模型多任务输出可以用timm的features_only模式拿特征再接五个全连接头。但毕设场景下独立模型更好解释论文里也好画图。4.3 分类结果的可解释性Grad-CAM 热力图答辩时老师一定会问“模型到底看的是舌头的哪个部分”这时候 Grad-CAM 就是后悔药。代码不复杂from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import show_cam_on_image target_layers [model.conv_head] cam GradCAM(modelmodel, target_layerstarget_layers) grayscale_cam cam(input_tensorimg_tensor) visualization show_cam_on_image(img_float, grayscale_cam[0], use_rgbTrue)热力图会告诉你模型关注的是舌面中部还是边缘。如果热力图集中在图像边框说明模型在偷懒学背景得回去检查数据增强是不是太弱了。5. 避坑与排查舌象系统落地时最容易翻车的五个点5.1 现象训练 loss 正常下降但推理时框全偏了原因标注时用了labelImg的默认保存格式输出的是 PASCAL VOC 的 XML直接改后缀成 txt 导致坐标没归一化。解决用脚本重新转换确认每行五个值都在 0-1 之间。检查方法很简单cat一个标签文件如果看到大于 1 的数就是没归一化。5.2 现象验证集 mAP 很高但手机拍的新图检测不出来原因训练集全是单反在白色背景下拍的手机图有阴影、有背景杂物。解决训练时加RandomShadow、RandomBrightness增强或者干脆在采集阶段就用手机拍一批加入训练集。领域偏移在医学图像里是头号杀手没有捷径。5.3 现象分类模型把“淡白舌”全预测成“淡红舌”原因这两类在 RGB 空间里距离太近且数据集中淡白舌样本太少。解决先统计各类别数量对少样本类做过采样或加WeightedRandomSampler。另外可以把 RGB 转到 LAB 空间L 通道对亮度不敏感a 通道对红绿差异更敏感能拉开淡白和淡红的距离。5.4 现象训练到一半 CUDA out of memory原因batch16在 8G 显存的卡上跑 640 分辨率会爆。解决降到batch8或者用imgsz416。YOLOv8 支持自动混合精度加ampTrue能省不少显存。如果还不行换yolov8n而不是yolov8s。5.5 现象推理速度太慢达不到实时原因用了yolov8x或者没开半精度。解决毕设场景yolov8n足够推理时加halfTrue用 FP16。另外把分类模型从 PyTorch 导出成 ONNX推理速度能再快 30% 左右。导出命令yolo export modelbest.pt formatonnx halfTrue6. 把检测和分类串成完整系统一个 Flask 接口的极简实现6.1 接口设计上传图片返回结构化 JSON整个系统对外只需要一个接口POST /diagnose接收图片返回舌体位置和五个维度的分类结果。Flask 代码骨架from flask import Flask, request, jsonify import cv2 import numpy as np from ultralytics import YOLO import timm import torch app Flask(__name__) det_model YOLO(best_det.pt) cls_model timm.create_model(mobilenetv3_small_100, pretrainedFalse, num_classes5) cls_model.load_state_dict(torch.load(best_cls.pth)) cls_model.eval().to(cuda) app.route(/diagnose, methods[POST]) def diagnose(): file request.files[image] img cv2.imdecode(np.frombuffer(file.read(), np.uint8), cv2.IMREAD_COLOR) results det_model(img, conf0.5) if len(results[0].boxes) 0: return jsonify({error: 未检测到舌体}) box results[0].boxes[0].xyxy[0].cpu().numpy().astype(int) crop img[box[1]:box[3], box[0]:box[2]] crop cv2.resize(crop, (224, 224)) tensor torch.from_numpy(crop).permute(2,0,1).float().unsqueeze(0).to(cuda) / 255.0 with torch.no_grad(): pred cls_model(tensor).argmax(dim1).item() return jsonify({box: box.tolist(), tongue_color: pred})逻辑说明检测模型先定位舌体裁出来后送分类模型。conf0.5保证只有高置信度才返回结果。实际部署时分类模型有五个这里只演示了一个按同样方式加载即可。6.2 前端联调时的一个小技巧前端上传的图片往往是手机直出尺寸 4000x3000直接送模型会 OOM。在接口里加一行img cv2.resize(img, (1280, 720))先降采样检测精度几乎不受影响但显存占用降一个数量级。这个技巧在嵌入式设备上同样适用。6.3 验证系统是否真的可用三个必做测试第一用训练集里没出现过的舌头图片测看框和分类是否合理。第二用一张纯风景图测看是否返回“未检测到舌体”而不是硬框一个区域出来。第三连续上传 20 张图看内存是否持续增长——Flask 默认不释放 CUDA 缓存需要在推理后加torch.cuda.empty_cache()。我自己的习惯是每次改完模型或数据先跑一遍这三个测试再提交代码。舌象诊断系统这种偏医学的应用宁可保守一点也不要给用户一个似是而非的结果。希望帮到你。本文还有配套的精品资源点击获取