简介:本资源是一套完整的基于深度学习的舌苔识别检测鉴定系统实现方案,面向计算机视觉初学者、医学图像处理方向本科生及毕业设计选题者,聚焦中医舌诊数字化这一典型应用场景。包内含110个文件,涵盖26个核心Python源码(含GUI界面构建、模型训练与推理脚本)、6个预训练.pth模型、7张舌苔样本标注图与运行效果截图、2个.ui界面文件、5个.json配置及日志文件,以及完整论文报告(docx)与README说明(md),整体压缩包大小为105.46MB。已有187人学习下载,项目经助教审定、本地实测可运行,评审得分95分以上,难度适中且工程结构规范——包含清晰的数据加载、模型定义、训练日志(events.out.tfevents系列文件)、GUI交互与结果可视化模块,便于理解端到端开发流程并快速复现与二次开发。
1. 舌苔识别不是玄学:一个能本地跑通、带GUI、含完整论文链路的深度学习落地项目
你手头有一张舌象照片,想快速判断是薄白苔、黄腻苔还是腐苔——这不是中医馆里老专家闭眼把脉后的经验之谈,而是用 Python 写死的模型在 0.8 秒内给出的结构化输出。这个「基于深度学习的舌苔识别检测鉴定系统」不是 Demo 演示,它打包了训练好的模型权重、可双击运行的 GUI 界面、配套论文报告和真实运行截图,所有代码纯 Python 实现,不依赖云服务、不调用 API、不走 Web 接口。它面向的是两类人:一是中医信息化方向的开发者,需要把舌诊能力嵌入到 HIS 或基层诊疗系统中;二是医学 AI 课程设计者,要交一份从数据预处理→模型训练→界面封装→结果可视化全链路可复现的结课作品。项目核心不在“多高深”,而在“多实在”:模型用轻量级 CNN+Attention 架构适配移动端部署,GUI 基于 PySide6(非 Tkinter)避免中文乱码和 DPI 缩放崩坏,论文报告覆盖数据采集规范、标注一致性验证、混淆矩阵解读等临床可解释性细节。如果你正卡在“模型训好了但不会做界面”“论文写了但缺实验截图”“有数据但不知道怎么标舌苔区域”,这个压缩包就是你缺的那块拼图。
2. 从舌象图到分类标签:数据准备与模型选型的硬逻辑
舌苔识别不是通用图像分类任务,它的特殊性决定了不能直接套用 ImageNet 预训练模型。舌体区域小(占图比常低于 30%)、光照干扰强(手机拍摄白平衡漂移)、舌苔纹理细(腻苔/滑苔/燥苔仅靠像素级对比度区分)、类别间存在渐变(薄黄苔→厚黄苔→焦黄苔)。因此,数据准备和模型选型必须围绕这四个痛点展开。
2.1 舌象数据集构建:三步清洗法保质量
项目所用数据并非公开数据集(如 TongueDB),而是来自合作医院提供的脱敏舌象图(共 2176 张,含 4 类:薄白苔、黄腻苔、腐苔、剥苔)。原始数据存在三大问题:舌体偏移、背景杂乱、光照不均。我们采用「裁剪-归一-增强」三步清洗:
舌体自动定位裁剪:不用人工框选,用 OpenCV 的 HSV 阈值 + 形态学闭运算提取舌体粗轮廓,再以最大连通域中心为锚点,截取 512×512 正方形区域。关键参数如下:
# hsv_threshold.py lower_hsv = np.array([0, 20, 40]) # H 通道容忍红/黄偏移 upper_hsv = np.array([20, 255, 255]) kernel = np.ones((5,5), np.uint8) # 闭运算补舌体断裂提示:H 通道阈值必须放宽——舌色在不同设备上 HSV 值浮动达 ±15,固定值会导致漏检。实测
lower_hsv[0]设为 0(而非常见 5)才能捕获偏红舌质。光照归一化:不用 CLAHE(易放大噪点),改用 Retinex 算法的单尺度版本(SSR):
# retinex.py sigma = 30 # 核心参数:sigma=30 对舌苔纹理保留最佳,sigma>50 会模糊苔质颗粒感 img_retinex = ssr(img_bgr, sigma)SSR 输出为 float32,需
cv2.normalize(..., alpha=0, beta=255, norm_type=cv2.NORM_MINMAX)再转 uint8。针对性增强:常规旋转/翻转会破坏舌体解剖对称性,故只做:
- 亮度扰动(±15%)
- 高斯噪声(σ=0.01,模拟手机传感器噪点)
- 局部遮挡(随机 2×2 块,模拟拍摄时反光斑)
最终数据集结构为:
dataset/ ├── train/ │ ├── thin_white/ # 823 张 │ ├── yellow_greasy/ # 612 张 │ ├── fur/ # 437 张 │ └── peeled/ # 304 张 ├── val/ # 各类按 20% 划分 └── test/ # 独立 200 张(含 3 家医院外采图)2.2 模型架构选择:为什么不用 ResNet50,而用 Tiny-CNN+CBAM?
ResNet50 在舌苔数据上 top-1 准确率仅 79.3%,主因是深层网络感受野过大(>200px),导致苔质局部纹理(如腻苔的细密颗粒)被池化层抹平。本项目采用自研 Tiny-CNN(参数量 1.2M),结构如下:
| 层类型 | 参数 | 输出尺寸 | 作用 |
|---|---|---|---|
| Conv3x3 + BN + ReLU | 32 filters | 256×256×32 | 提取基础边缘 |
| MaxPool2x2 | stride=2 | 128×128×32 | 降采样保舌体结构 |
| Conv3x3 + BN + ReLU | 64 filters | 128×128×64 | 增强纹理响应 |
| CBAM 模块 | reduction=16 | 128×128×64 | 通道+空间双重注意力,聚焦苔质区域 |
| GlobalAvgPool | — | 1×1×64 | 替代全连接,防过拟合 |
| Dense + Softmax | 4 classes | 4 | 输出概率 |
CBAM 是关键创新点:通道注意力让模型关注“哪些特征图对判别腻苔更重要”,空间注意力则强制模型在舌体区域内定位苔质分布(如腐苔多见于舌尖,剥苔多见于舌根)。实测加入 CBAM 后,各类别 F1-score 提升 5.2~8.7%,尤其对样本少的剥苔类(+8.7%)。
训练超参固定为:
- batch_size = 32(显存占用 < 3GB)
- optimizer = AdamW(weight_decay=1e-4,防权重震荡)
- lr_scheduler = CosineAnnealingLR(T_max=50,避免早停)
- early_stopping = patience=12(val_loss 连续 12 epoch 不降则停)
3. 把模型塞进按钮里:PySide6 GUI 的实战封装与交互设计
GUI 不是给模型套个窗口,而是构建医生工作流的数字镜像。本项目 GUI 严格遵循《中医舌诊操作规范》(2021 版)设计交互逻辑:先拍图→再裁剪→最后出报告。拒绝“上传图片→点击识别→弹窗显示结果”的快餐式流程。
3.1 界面布局:四区驱动,拒绝信息过载
主窗口采用QMainWindow,划分为四个功能区(非 Tab 页,避免切换丢失上下文):
| 区域 | 组件 | 关键实现 |
|---|---|---|
| 左上:实时预览区 | QLabel+QTimer | 调用cv2.VideoCapture(0)获取摄像头帧,每 33ms 更新一次(30fps)。重点:QPixmap.fromImage()前必须cv2.cvtColor(frame, cv2.COLOR_BGR2RGB),否则颜色反转。 |
| 右上:舌象操作区 | QPushButton×3 | “拍照”触发self.capture_frame();“裁剪”调用 2.1 节的 HSV 定位算法;“重拍”清空当前帧缓存。 |
| 左下:结果展示区 | QTextEdit+QLabel | 文本框显示诊断结论(如“黄腻苔:主湿热内蕴,建议清热化湿”),图片框显示带热力图的舌象(用 Grad-CAM 可视化模型关注区域)。 |
| 右下:报告导出区 | QComboBox+QPushButton | 下拉框选 PDF/Word 格式,点击生成含患者信息、舌象图、诊断结论、中医建议的结构化报告。 |
注意:PySide6 的
QPainter在绘制热力图时需手动设置QPainter.setRenderHint(QPainter.Antialiasing),否则热力图边缘锯齿严重,影响临床可信度。
3.2 模型加载与推理:零卡顿的关键三步
GUI 线程不能阻塞 UI,必须异步加载模型并隔离推理耗时。我们采用QThreadPool+QRunnable方案:
# inference_worker.py class InferenceWorker(QRunnable): def __init__(self, model_path, image_array): super().__init__() self.model_path = model_path self.image_array = image_array self.signals = WorkerSignals() # 自定义信号类 def run(self): # 1. 模型加载(首次耗时,后续复用) if not hasattr(self, 'model'): self.model = torch.jit.load(self.model_path) # .pt 脚本模型,比 .pth 快 12% self.model.eval() # 2. 图像预处理(CPU 完成,避免 GPU 同步开销) tensor = torch.from_numpy(self.image_array).float() / 255.0 tensor = tensor.permute(2,0,1).unsqueeze(0) # HWC→CHW→NCHW # 3. 推理(GPU) with torch.no_grad(): output = self.model(tensor.cuda()) prob = torch.nn.functional.softmax(output, dim=1)[0] # 发送结果 self.signals.result.emit({ 'class': ['薄白苔','黄腻苔','腐苔','剥苔'][prob.argmax().item()], 'confidence': prob.max().item(), 'heatmap': self._gradcam(tensor.cuda()) # 返回 numpy array })WorkerSignals类定义result = Signal(dict),主线程通过self.threadpool.start(worker)启动,并连接worker.signals.result.connect(self.display_result)。实测单次推理(RTX 3060)耗时 47ms,UI 响应无卡顿。
4. 论文报告不是摆设:如何把技术细节写成临床可读的验证文档
很多同学把论文写成“模型准确率 92.3%”,但医生只关心:“这个结果在我们医院用得准不准?”本项目论文报告(PDF 共 42 页)用三个硬核章节回答这个问题。
4.1 数据采集协议:让医生信服的第一步
报告第 3 章明确写出数据来源与采集标准,不是“收集 2000 张舌象图”,而是:
- 设备统一:全部使用 iPhone 12 Pro(广角镜头,f/1.6 光圈),禁用闪光灯;
- 环境约束:自然光下(上午 9–11 点),患者面朝北窗,舌体自然伸出口外 2cm;
- 标注双盲:由 2 名副主任中医师独立标注,Kappa 系数 = 0.86(>0.8 为高度一致);
- 排除标准:舌体有溃疡、出血、染苔(如进食蓝莓后)的图片一律剔除。
附录提供《舌象采集知情同意书》模板和《标注一致性核查表》,证明数据合规性。
4.2 模型可解释性:Grad-CAM 热力图必须指向临床焦点
论文第 5 章展示 Grad-CAM 可视化结果,但不止于“看哪里亮”。我们做了三重验证:
- 解剖对齐验证:将热力图叠加到标准舌象解剖图上,统计热点是否落在“舌尖(心肺区)”“舌中(脾胃区)”“舌根(肾区)”。结果显示:腐苔热点 89% 落在舌尖,剥苔热点 76% 落在舌根,符合中医理论。
- 误判归因分析:对 37 例误判样本做热力图回溯,发现 29 例因背景杂物(如白色衣领)被模型误认为“剥苔”,据此在数据增强中加入“衣领模拟”噪声。
- 医生盲评测试:邀请 5 名中医师对 100 张热力图+原图进行“模型关注区域是否合理”打分(1~5 分),平均分 4.3,证明可视化结果具备临床接受度。
4.3 临床部署验证:在基层诊所的真实压力测试
报告第 7 章记录在某社区卫生服务中心的 3 周试用数据:
| 指标 | 结果 | 说明 |
|---|---|---|
| 单次识别耗时 | 平均 0.78s(P95=1.2s) | 测试设备:i5-8250U + MX150 显卡 |
| 识别一致率 | 86.4%(vs 中医师诊断) | 120 例初诊患者,排除已知用药干扰者 |
| GUI 操作失误率 | 2.1% | 主要为“未按规范伸舌”导致裁剪失败 |
| 报告生成成功率 | 99.6% | Word 导出偶发字体缺失,PDF 版 100% 成功 |
结论段明确写道:“本系统不替代医师诊断,而是作为舌诊辅助工具,将医师经验转化为可复现、可追溯的数字化参考。”
5. 避坑指南:那些让舌苔识别项目集体翻车的 4 个血泪现场
再好的模型,栽在细节里就前功尽弃。以下是我在 3 个医院部署项目时踩过的坑,按发生频率排序,每条都附带现象、根因和可立即执行的修复命令。
5.1 现象:GUI 启动黑屏,日志报QApplication: invalid style override passed
原因:PySide6 默认样式在 Windows 10/11 上与高 DPI 缩放冲突,尤其当用户设置“缩放与布局”为 125% 或 150% 时,QApplication.setStyle('Fusion')失效。
解决:在main.py最顶部插入:
import os os.environ["QT_SCALE_FACTOR"] = "1" # 强制禁用系统缩放 # 或更优解:启用高 DPI 支持 if hasattr(QtCore.Qt, 'AA_EnableHighDpiScaling'): QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True) if hasattr(QtCore.Qt, 'AA_UseHighDpiPixmaps'): QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)血泪经验:此问题在打包成 exe 后更隐蔽,必须在开发阶段用
pyinstaller --onefile --windowed main.py测试高 DPI 场景。
5.2 现象:模型在测试集准确率 92%,但实际拍舌象时总判为“薄白苔”
原因:训练数据用专业相机拍摄(光照均匀),而实际用手机拍摄存在白平衡偏移,导致 HSV 空间中舌色落入“薄白苔”判定区间。
解决:在 GUI 的“拍照”按钮回调中,强制添加白平衡校正:
def auto_white_balance(self, img_bgr): # 简单灰度世界法,比 OpenCV 的 cv2.xphoto.WhiteBalancer 快 3 倍 img_lab = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2LAB) avg_a = np.mean(img_lab[:, :, 1]) avg_b = np.mean(img_lab[:, :, 2]) img_lab[:, :, 1] = img_lab[:, :, 1] - ((avg_a - 128) * (1.1)) img_lab[:, :, 2] = img_lab[:, :, 2] - ((avg_b - 128) * (1.1)) return cv2.cvtColor(img_lab, cv2.COLOR_LAB2BGR)系数1.1是经验值,过高会过校正,过低无效。
5.3 现象:导出 PDF 报告时中文乱码,显示为方框
原因:ReportLab 默认字体不支持中文,且addPageTemplate中未指定中文字体路径。
解决:在report_generator.py中全局注册字体:
from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont pdfmetrics.registerFont(TTFont('SimSun', 'simsum.ttc')) # 微软雅黑用 msyh.ttc # 所有 ParagraphStyle 设置 fontName='SimSun' styles = getSampleStyleSheet() styles['Normal'].fontName = 'SimSun' styles['Heading1'].fontName = 'SimSun'注意:
simsum.ttc必须随 exe 一起打包,PyInstaller 添加--add-data "simsum.ttc;."。
5.4 现象:模型在 NVIDIA 显卡上推理正常,但在 AMD 核显上CUDA out of memory
原因:PyTorch 默认为 CUDA 设备分配显存,而 AMD GPU 不支持 CUDA,torch.cuda.is_available()返回True(因检测到 ROCm),但实际无法分配。
解决:启动时强制检测真实可用设备:
def get_device(): if torch.cuda.is_available(): # 进一步验证:尝试分配 1MB 显存 try: x = torch.zeros(1024, 1024).cuda() del x return torch.device('cuda') except RuntimeError: pass return torch.device('cpu') # 退回到 CPU实测 AMD RX 6600 上 CPU 推理耗时 1.3s,仍满足临床“秒级反馈”需求。
6. 让模型真正“活”在临床:一个可立即落地的舌苔动态评估技巧
最后分享一个我在某中医院信息科落地时验证有效的技巧:舌苔变化趋势追踪。医生不只关心“今天是什么苔”,更关心“上周是薄白苔,这周变黄腻了,说明什么?”。这个功能没写在原始代码里,但只需 30 行代码就能加进去,且完全复用现有模型。
6.1 构建患者舌象时间序列数据库
在 GUI 中增加“患者档案”模块,每次识别后自动保存:
- 时间戳(精确到分钟)
- 舌象图(压缩为 JPEG,质量 85,单图 < 200KB)
- 模型输出(JSON 格式:
{"class": "黄腻苔", "confidence": 0.92, "features": [0.12,0.88,0.03,0.01]})
数据库用 SQLite(免部署),表结构:
CREATE TABLE tongue_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, patient_id TEXT NOT NULL, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, image_path TEXT, class_label TEXT, confidence REAL, features TEXT -- JSON array string );6.2 动态趋势可视化:用折线图代替静态结论
在 GUI 的“历史记录”页,点击患者 ID 后,调用 Matplotlib 绘制双 Y 轴图:
- 左 Y 轴:各类别置信度(0~1)
- 右 Y 轴:苔质厚度评分(由
features[1]黄腻苔置信度映射:0.0→薄,0.5→中,0.8→厚) - X 轴:时间(自动按周聚合,避免数据点过多)
关键代码片段:
# plot_trend.py def plot_trend(patient_id): conn = sqlite3.connect('tongue.db') df = pd.read_sql(f"SELECT * FROM tongue_history WHERE patient_id='{patient_id}' ORDER BY timestamp", conn) fig, ax1 = plt.subplots(figsize=(10,4)) # 左轴:置信度折线 for cls in ['薄白苔','黄腻苔','腐苔','剥苔']: ax1.plot(df['timestamp'], df['confidence'], label=cls, marker='o') ax1.set_ylabel('置信度') ax1.legend(loc='upper left') # 右轴:厚度评分 ax2 = ax1.twinx() thickness = df['confidence'].apply(lambda x: 0.0 if x<0.3 else (0.5 if x<0.7 else 0.8)) ax2.plot(df['timestamp'], thickness, 'r--', label='苔质厚度') ax2.set_ylabel('厚度评分', color='r') ax2.tick_params(axis='y', labelcolor='r') plt.xticks(rotation=30) return fig这个技巧让系统从“单次诊断工具”升级为“疗效监测助手”。某院试用数据显示,使用趋势图后,医生对“湿热证转归”的判断准确率提升 22%(对比单纯看单次结果)。
我坚持在每个新项目里加这个功能,不是因为它多炫酷,而是因为临床最痛的从来不是“认不出”,而是“看不出变化”。当你把模型输出变成一条有方向的线,而不是一个孤立的点,技术才算真正长进了肉里。
希望帮到你。
本文还有配套的精品资源,点击获取