
简介一套面向毕业设计与医学图像处理学习的舌象智能诊断系统资源基于YOLO深度学习框架与Python脚本语言构建涵盖图像采集、特征提取、模型训练到结果分析完整流程。资源共二百二十一个文件包含五十四个程序源码与四十个编译后文件、六十余张标注图片、界面文件、配置文档以及技术说明文档压缩包整体约四十二点七六兆字节目录结构清晰便于检索。已有六十七人浏览学习适用于高校毕业设计、课程实践及中医辅助诊断研究。项目采用模块化架构核心算法独立封装并集成光照补偿、色彩校正等预处理增强技术可实现舌体轮廓与舌苔分布等关键特征的精准识别。配套标注数据集与详细注释代码便于初学者理解模型训练流程和系统二次开发具备较强的工程参考价值。1. 舌象智能诊断为什么一张舌头照片到了 YOLO 手里比肉眼看更稳舌象是中医望诊里信息量最大的部位但也是主观性最强的一项同一个舌头在白天和晚上、在不同医生眼里结论能差出一档。把舌象图片交给 YOLO 系列目标检测模型配合 Python 做后处理和规则映射就能把“舌质淡红、苔薄白”这类判断固化成可重复的流程。这个基于 YOLOv 深度学习的舌象智能诊断系统核心价值不是替代中医而是做一个不会疲劳、不随光线乱变的辅助诊断入口检测舌头区域、提取舌色苔色、输出结构化报告。含完整源码和数据集意味着你可以从零跑通全流程很适合正在做毕设、想入门医学图像 AI、或者打算做健康管理产品原型的从业者。下面我会按真实项目落地的顺序把数据准备、模型训练、封装推理和典型踩坑一次讲透。2. 舌象检测/识别的任务拆解与数据集准备先想清楚要检测还是分类2.1 舌象诊断到底要模型输出什么定位、分类还是分割很多第一次接触舌象 AI 的人会把任务直接当成“给一张舌图输出一个结论”。但真正做下来你会发现YOLO 最适合也最应该承担的是第一层任务舌体目标检测。舌象诊断的完整链路通常是这样的输入一张含面部或口腔的图片先由 YOLO 检测出舌体矩形框再从框内裁剪舌头区域交给后面的分类模型判断舌色淡白、淡红、红、绛红等、苔色白/黄/灰黑、苔质薄/厚/腻/剥等最后用规则引擎或公式模型把分类结果组合成文本报告。为什么第一层必须用检测而不是直接整图分类因为整图分类会受到嘴唇、牙齿、皮肤暗影的干扰而这些区域在生理上本身就带颜色深度学习非常容易被它们带偏。YOLO 把舌体剪裁出来后分类模型的输入就干净得多训练时也不需要为舌体位置做额外扩增。另外很多人还会纠结要不要直接上 YOLO 的实例分割模型YOLOv8-seg / YOLOv5-seg。如果只是做舌色苔色判断分割并不是必须的检测框裁剪已经足够。但如果你要做舌面面积、裂纹长度、齿痕比例这类定量分析分割就是必要的。我的建议是先跑通检测 分类的最低可用版本再用分割模型去升级不要一上来就追全功能。2.2 舌象数据的采集与标注一张舌头照片怎么变成 YOLO 能吃的标签目标检测的数据标注格式常见的有 COCO、VOC 和 YOLO 三种。YOLO 训练时最省事的是直接在 ultralytics 框架下使用 YOLO 格式。标注工具用 LabelImg 或 Label Studio 都可以我一般用 LabelImg打开图片画矩形框类别写 Tongue保存后自动生成同名的.txt文件。YOLO 格式的标签内容是一行五个数字class_id x_center y_center width height注意这些都是相对于图片宽高的比例值范围在 0-1 之间。举个例子一张 640x480 的图片若舌体框左上角坐标是 (150, 100)右下角是 (400, 350)则换算为# class_id0 (Tongue), x_center(150400)/2/6400.4297, y_center(100350)/2/4800.4688 # width(400-150)/6400.3906, height(350-100)/4800.5208 0 0.4297 0.4688 0.3906 0.5208标注时有一条血泪经验舌体边界要抠到舌头轮廓最大外接矩形不要把嘴唇下缘包进去。否则后续裁剪分类时模型拿到的舌头区域里混入唇色训练出的舌色分类器会在嘴唇边缘产生伪特征。每张图标注耗时大约 30 秒到 1 分钟300 张图半天能完成。2.3 一套可复用的舌象数据集应该长什么样数据集来源不外乎两个公开数据集和自己采集。公开的舌象图像集不算特别多国内高校和医学图像竞赛偶尔会放出脱敏的舌图Kaggle 上也有 Tongue Segmentation 相关的开源样本。如果做毕设或预研先找几百张开源舌图足够验证流程如果做产品则需要联合医疗机构做合规采集并且每张图都要有两位及以上中医师标注舌质苔色做一致性检验。拿到原始图片后不管来源如何都要整理成一个固定结构。我常用的是这种组织方式也强烈建议你复制tongue_dataset/ ├── images/ │ ├── train/ # 训练图片如 00001.jpg │ ├── val/ # 验证图片 │ └── test/ # 测试图片 ├── labels/ │ ├── train/ # 与图片同名的 .txt 标签 │ ├── val/ │ └── test/ └── tongue.yaml # 交给 YOLO 的数据配置tongue.yaml的内容很简单它告诉 YOLO 去哪里找图、有几个类别# 训练和验证数据的绝对或相对路径 path: ./tongue_dataset train: images/train val: images/val test: images/test nc: 1 names: [Tongue]注意path建议用绝对路径或者固定在项目根目录下跑训练命令否则 YOLO 容易找不到数据。另外.txt标签必须和图片文件同名一个常见翻车点是 LabelImg 保存时自动改了后缀导致标签文件没有被 YOLO 读取却不报错只是 mAP 一直为 0。3. 用 YOLOv8 在本地训练舌象检测模型从配置到收敛的关键参数3.1 为什么选 YOLOv8 而不是更深的分类网络舌象诊断里检测网络用 YOLO 几乎是毫无疑问的。YOLOv8 是 ultralytics 目前最成熟的目标检测框架内置了训练、验证、导出、推理一条龙适合快速验证YOLOv5 仍在大量老项目中使用但新项目我建议直接用 YOLOv8。YOLOv8 提供 n / s / m / l / x 五个尺寸舌象检测属于中小目标且环境相对简单用yolov8n或yolov8s就足够推理速度在 CPU 上也能做到单张几十到几百毫秒。为什么不直接用 ResNet 或者 EfficientNet 这类分类网络做端到端“舌象状态分类”因为分类网络输出的是全局概率它必须把舌体在画面中裁到归一位置才能学习好你在数据预处理时就得做舌体对齐而这个对齐往往比模型本身更费劲。YOLO 把“找到舌头在哪”和“识别舌头”融合在一个网络里训练时只需标注框模型自动学习舌体的空间特征部署时还能把坐标一并输出后续裁剪、归一化、报告都有依据。3.2 开始训练安装 ultralytics、准备数据、跑通最小命令环境部分不需要自己搭 Darknet直接装ultralytics包即可它内部会处理 YOLOv8 的依赖。Python 3.8-3.11 我都跑通过建议用 Python 3.9 或 3.10。安装命令如下# 建议先创建虚拟环境 python -m venv tongue_env source tongue_env/bin/activate # Windows 下执行 tongue_env\Scripts\activate # 安装 ultralytics 会自动带 torch如已有 CUDA 版 torch 可以跳过 pip install ultralytics # 验证安装 yolo --help接着在项目目录下新建train.py内容是最小训练脚本from ultralytics import YOLO # 加载预训练权重。yolov8n.pt 会在第一次运行时自动从 ultralytics 官方源下载 model YOLO(yolov8n.pt) # 训练。data 指向上一章创建的 tongue.yaml results model.train( datatongue_dataset/tongue.yaml, epochs120, imgsz640, batch16, lr00.01, patience20, projectruns/tongue, nameexp1, device0, )这里每个参数都值得解释一下。epochs120对几百张图的数据集来说偏大但配合patience20的早停模型会在验证集损失连续 20 轮没有下降时自动停止所以设大一点没关系。imgsz640是 YOLO 默认输入尺寸舌体在图像中通常占比较高640 够用如果舌头周围有很多干扰用 960 会提升精度但训练显存和推理时间都会涨。batch16根据你的 GPU 显存调整8GB 显存跑 YOLOv8n 可以到 16如果显存紧张就先降到 4。lr00.01是当前主流框架的默认初始学习率对迁移学习比较友好。3.3 三个必调参数imgsz、batch 和早停以及预训练权重的陷阱训练舌象检测模型时需要重点关注三个直接影响收敛质量的参数。第一个是imgsz。舌象数据往往来源于手机拍摄舌体在图像中占 1/3 到 1/2不算小目标但如果你把图片缩得太小比如 320舌苔纹理和舌质颜色这些细腻信息就丢了。我试过 320、640、960 三档640 和 960 的 mAP 差距可能在 2-3 个点但 960 训练时间长一半。建议先跑 640 看整体流程再跑 960 做精度微调。第二个是batch。batch 太小会使得 BNBatch Normalization统计量不稳定舌象数据本身颜色差异大batch 小于 8 时验证集 loss 会周期性震荡。如果显存不足不要一味调小 batch可以用batch-1让 YOLO 自动检测可用显存并选择最大 batch但自动选择很保守未必是最优。第三个是patience。很多新手把 epochs 写到 300然后去看全流程实际上你的数据可能 50 轮就收敛了。设置patience30后模型只保存验证集指标最好的那一次权重最后一个 epoch 的权重不一定最好。训练完成后runs/tongue/exp1/weights/best.pt就是你要的模型文件。有一个预训练陷阱要专门提醒YOLOv8 默认下载的yolov8n.pt是 COCO 80 类预训练权重它的 backbone特征提取部分已经学会通用形状特征直接迁移到舌体检测完全没问题。但如果你误用了yolov8n-cls.pt分类版本训练时模型结构不同会导致维度报错反之用 YOLOv5 的.pt权重放在 YOLOv8 里也会因为结构不匹配报错。记得统一版本。4. 用 Python 把训练好的模型封装成舌象诊断小系统推理、裁剪与报告输出4.1 推理脚本加载模型处理单张图片并返回舌头坐标训练得到best.pt后下一步就是脱离训练脚本做一个独立的推理入口。推理代码几乎可以一行调起 YOLO但要拿到可用的坐标和类别还需要解析结果对象。下面是我常用的最小推理脚本from ultralytics import YOLO import cv2 model YOLO(runs/tongue/exp1/weights/best.pt) def detect_tongue(image_path): img cv2.imread(image_path) results model(img, conf0.35, iou0.45, verboseFalse) boxes results[0].boxes if len(boxes) 0: return None # 取置信度最高的一个框作为舌体区域 best boxes[0] x1, y1, x2, y2 best.xyxy[0].tolist() # 像素坐标 conf best.conf.item() cls int(best.cls.item()) return { box: [int(x1), int(y1), int(x2), int(y2)], confidence: round(conf, 4), class_id: cls } # 示例诊断一张图片 result detect_tongue(test_img.jpg) print(result)这段代码的要点是model(img)的输入可以直接是 numpy 数组不用先写文件conf0.35表示置信度低于 0.35 的框会被滤掉舌象场景下舌头通常很大且显眼0.35 够用但如果图片里有嘴唇和舌头紧密贴近可以把 conf 调高到 0.5 以减少误检。iou0.45是 NMS 阈值目标重叠严重时调低到 0.3 会更有区分力。4.2 舌色与苔质分析从检测框里裁剪出舌头再交给分类模型拿到检测框后下一级分类网络需要单独训练。分类网络可以用最轻量的 torchvision 模型也可以用 YOLO 自带的分类头不过我更推荐用 torchvision 里的 resnet18 或 mobilenet_v3_small方便控制输入尺寸。下面是裁剪与预处理代码import cv2 import torch from torchvision import transforms from PIL import Image def crop_tongue(image_path, box, margin0.1): img cv2.imread(image_path) x1, y1, x2, y2 box # 在原框基础上外扩 margin避免裁剪时舌头边缘被切掉 h, w img.shape[:2] dx int((x2 - x1) * margin) dy int((y2 - y1) * margin) x1 max(0, x1 - dx) y1 max(0, y1 - dy) x2 min(w, x2 dx) y2 min(h, y2 dy) cropped img[y1:y2, x1:x2] return cropped # 加载舌色/苔质分类模型此处以 resnet18 为例 # 假设你已经用自己的数据训练好了一个二输出分类模型tongue_color_classifier.pt # 训练代码见后文提示这里只演示推理链路 device torch.device(cuda if torch.cuda.is_available() else cpu) model torchvision.models.resnet18(num_classes4) model.load_state_dict(torch.load(tongue_color_classifier.pt, map_locationdevice)) model.eval().to(device) transform transforms.Compose([ transforms.ToPILImage(), transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) def predict_tongue_color(cropped_bgr): rgb cv2.cvtColor(cropped_bgr, cv2.COLOR_BGR2RGB) tensor transform(rgb).unsqueeze(0).to(device) with torch.no_grad(): probs torch.softmax(model(tensor), dim1)[0] return probs.cpu().numpy().tolist()这里有个容易忽略的细节YOLO 训练时用 BGR 读入图片还是 RGB 读入取决于你的数据管道但 ultralytics 内部会转为 RGB 处理。而 torchvision 的模型默认期望 RGB 输入所以推理时一定要把 OpenCV 读进来的 BGR 转成 RGB否则分类准确率会明显下降——这是典型的“黑匣子翻车现场”。关于分类模型的训练你同样可以用 ultralytics 的分类模式跑yolo classify train data... model...然后保存 pt 文件。如果你手头没有预训练分类器可以先直接用颜色直方图聚类做一个 baseline验证整个链路。4.3 输出诊断建议把舌色和苔质组合成结构化报告模型输出的都是概率最后一句“舌淡红苔薄白”需要通过规则引擎组合。规则不需要复杂甚至一张查表就能完成。下面是一个最小化的规则映射示例# 舌色概率列表对应[淡白, 淡红, 红, 绛红] # 苔色概率列表对应[白苔, 黄苔, 灰黑苔, 少苔] tongue_color_labels [pale-white, pale-red, red, deep-red] coating_labels [white, yellow, gray-black, scant] def top_label(probs, labels): idx probs.index(max(probs)) return labels[idx], probs[idx] def make_report(tongue_probs, coating_probs): tongue, tongue_conf top_label(tongue_probs, tongue_color_labels) coating, coating_conf top_label(coating_probs, coating_labels) report f舌质{_label_zh(tongue)}苔{_label_zh(coating)} suggestions [] if tongue pale-white: suggestions.append(提示气血偏虚建议规律作息、避免生冷) elif tongue red: suggestions.append(提示热象建议清淡饮食、少熬夜) if coating yellow: suggestions.append(湿热偏重注意饮食油腻) return report, suggestions def _label_zh(key): return { pale-white: 淡白, pale-red: 淡红, red: 红, deep-red: 绛红, white: 白, yellow: 黄, gray-black: 灰黑, scant: 少 }[key]这里的关键是报告语句要符合中医描述习惯而不是直接把英文标签堆上去。规则内容可以由中医师提供你只需要维护一个映射表。后续如果数据量变大可以用一个简单的评分卡模型替代硬规则但在数据量不够大时规则比模型更可靠也更好向医生解释。5. 舌象检测训练的 5 个典型踩坑症状、原因与改法5.1 数据集只有几百张训练 loss 降不下去却不知道哪里错现象训练几十轮后损失值仍然在 2.0 以上验证集 mAP 始终低于 0.1。原因最常见的是标签和数据不匹配标签文件里的 class_id 写成了 0但 yaml 里 nc1、names 列表第一个是 Tongue这种情况一般没问题真正的大坑是标签文件中的坐标值超过了 0-1 范围或者用 VOC 标注后的坐标没有归一化就喂给 YOLO。另一个原因是学习率过高舌象检测属于域迁移从 COCO 到医学图像特征差异较大lr00.01 还可以但超过 0.02 容易在初始阶段把预训练权重冲掉。解决先用脚本扫描标签值范围确认所有中心点和宽高均在 0-1 之间然后把 lr0 降到 0.005把warmup_epochs保留默认最后用带预训练权重的yolov8n.pt重新训练不要从随机权重开始。5.2 舌头和嘴唇被检测成同一个框舌色判断被唇色带偏现象检测框能包住舌头但框的上边界顶到嘴唇下沿框内面积一半是嘴唇。后续分类器训练时学到的“舌色”其实是唇色。原因标注时舌根部分被嘴唇遮挡标注人员习惯把可见的舌头区域连同嘴唇一起框进去。从数据上看这类框的 IoU 没问题但语义不干净。另外 NMS 后只保留最大框时嘴唇和舌头挨得太近YOLO 把两者当成了同一目标。解决标注规范里明确规定舌体框只画到舌尖到舌根可见边界宁可把舌根切掉也不要包住嘴唇。推理时对检测结果再做一次内缩把box的上下边界各自缩小 5%-10%这样裁剪区域更靠近舌面中央。如果你用的是分割模型直接取分割掩膜的外接矩形能避免大部分嘴唇误入。5.3 测试时把深肤色或暗光环境下的舌头漏检现象在明亮图片上检测准确光线偏暗或者肤色较深的人脸照片上检测不到舌头或置信度很低。原因训练集里没有覆盖足够的明暗和肤色变化。舌象本身的颜色和肤色有较大重叠YOLO 学到的可能只是“亮红色区域”而不是舌头形状结构。解决在训练阶段开启数据增强尤其是 HSV 通道的随机扰动。ultralytics 默认开启了几种增强但不够可以自己在数据加载阶段把图片整体亮度随机乘 0.6-1.4或者用 OpenCV 做 CLAHE 对比度增强后再送进网络。推理时也先对图片做 CLAHE把光照归一化到相对统一的范围。统计上这能把暗光漏检率降低一半以上。5.4 训练中显存爆掉换小模型后精度下降严重现象8GB 显存跑 YOLOv8s 时 batch16 直接 OOM改成 batch4 后训练过程震荡换 YOLOv8n 后精度下降 4 个点让人怀疑是模型太小。原因batch4 对 BN 来说统计噪声过大舌象内部颜色差异大导致验证集 loss 抖动加剧YOLOv8n 的特征通道本身就窄如果输入分辨率又不变小模型对舌苔纹理的拟合能力确实弱。解决换 YOLOv8n 的同时把imgsz从 640 提到 480让有效感受野和算力匹配同时开启梯度累积batch8, accumulate2等效 batch16。另外把cacheTrue打开减少数据加载瓶颈这样反而可能比硬跑大模型更快收敛。5.5 CPU 部署推理慢单张图接近秒级现象把模型放在 Windows CPU 电脑上跑一张图推理耗时 800ms 以上无法做成实时反馈。原因直接用 PyTorch 框架推理模型未做任何优化。YOLOv8n 参数量约 3.2M在 CPU 上走 PyTorch 的动态图路径效率低。解决导出为 ONNX再用 OpenVINO 或 ONNXRuntime 跑推理。ultralytics 提供了现成接口model.export(formatonnx, opset12)然后使用 onnxruntime 库加载。注意导出时指定imgsz和你训练时一致的 640否则推理尺寸不一致会掉精度。更低级的做法是启用量化model.export(formatonnx, int8True)但舌象颜色对量化敏感建议优先保持 FP32 精度只做图优化。6. 进阶给舌象诊断模型加一个简易 Web 接口并量化实测效果模型训练和推理脚本跑通后要想真正投入日常试用最好封装成一个 HTTP 服务这样可以直接用浏览器上传图片看结果。我常用 Flask 搭一个最简接口前端不用做用 curl 就能验证from flask import Flask, request, jsonify import base64, cv2, numpy as np from your_inference import detect_tongue, crop_tongue, predict_tongue_color, make_report app Flask(__name__) app.route(/tongue-diagnosis, methods[POST]) def diagnose(): file request.files[image] img_bytes file.read() nparr np.frombuffer(img_bytes, np.uint8) img cv2.imdecode(nparr, cv2.IMREAD_COLOR) # 1. 检测舌体 det detect_tongue(img) if det is None: return jsonify({error: no tongue detected}), 400 # 2. 裁剪 分类 cropped crop_tongue(img, det[box]) tongue_probs predict_tongue_color(cropped) coating_probs predict_tongue_coating(cropped) # 3. 生成报告 report, suggest make_report(tongue_probs, coating_probs) return jsonify({ box: det[box], confidence: det[confidence], report: report, suggestions: suggest }) if __name__ __main__: app.run(host0.0.0.0, port5000)这个接口把前面所有模块串起来测试时用curl -F imagetest.jpg http://127.0.0.1:5000/tongue-diagnosis就能看到 JSON 结果。上线前记得做两件事第一对 50 张持有医生标注的测试图统计分类准确率和检测 mAP把结果写进项目 README这不仅是为了验证更是为了给使用方一个信任依据第二用多线程压测接口确认 CPU 下吞吐能到多少再决定是否需要用消息队列做异步处理。我自己的教训是不要跳过评估直接给人演示。舌象 AI 最怕的不是模型不够准而是“演示效果时好时坏说不清为什么”。用固定测试集、固定光照条件、记录每张图的置信度才能让这个系统从“玄学”变成“可解释的工具”。希望这篇笔记能帮你把舌象诊断的整条链路一次跑通少走我当年走过的弯路。祝你顺利。本文还有配套的精品资源点击获取