news 2026/9/28 14:03:22

基于深度学习的舌苔识别检测系统:数据标注、模型训练与部署实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于深度学习的舌苔识别检测系统:数据标注、模型训练与部署实践

简介:一套面向计算机专业毕业设计、课程设计及期末大作业场景的基于深度学习的舌苔识别检测鉴定系统,包含深度学习模型、PyQt5界面与毕业论文文档,覆盖从图像采集、预处理、特征提取到识别鉴定、结果展示的完整流程;既能用于课堂项目演练,也可作为本科毕设范本,适合具备基础语法、希望快速上手完整课题的中级学习者。资源共109个文件,压缩包约105.44MB,核心包含26个Python脚本、10个二进制模块、6个模型权重文件、7张舌苔样本图、5个配置与2个界面布局文件,另附论文文稿,便于理解设计思路与直接复用。已有150人学习下载,项目曾获导师指导下的高分评价(99分),代码完整性高、可运行性强。整体目录结构清晰,能够帮助学习者快速掌握舌苔图像识别系统的工程实现要点,并借助现成界面和模型权重快速完成毕业论文的实验部分。

1. 舌苔识别检测系统:为什么说它是毕设里少有的“能落地”选题

舌苔识别检测鉴定系统,本质上是把中医望诊里最依赖经验的“看舌头”这件事,转成一套计算机视觉流程:输入一张舌尖或舌面图像,模型输出苔色、苔质、舌色等分类结果,顺带把舌体区域从背景里框出来。这套东西在毕业设计里属于典型的“深度学习 + 行业应用”组合,比单纯的图像分类题多一层检测任务,比纯目标检测题多一个专业场景,答辩时既有模型 talk 又有业务 talk,素材足够撑起一篇完整论文。

但真正让这个选题值得做,不是因为它“看起来高级”,而是它在数据层面有一条现实路径。舌苔图像不需要像医学影像那样依赖医院 PACS 系统,自己用手机拍摄、从中医公开教材扫描件里截取、用标注工具画框,就能攒出一份可用的训练集。对绝大多数本科生和研究生来说,这是少数不依赖外部资源就能独立完成的视觉项目之一。

适合谁做?有 Python 基础、跑过至少一次 CNN 分类实验、愿意花两周时间折腾数据标注和 PyQt5 界面的人。如果你现在只会调库、没亲手训过模型,这个题也能做,但要把预期放低——第一步不是追求准确率,而是先把“图像输入→模型推理→界面显示”这条链路跑通,后面才有优化的资本。本文按“数据怎么来 → 模型怎么选 → 界面怎么写 → 坑怎么填”的顺序,整套方案直接照抄即可,参数也给了可复现的默认值。

2. 舌苔数据从哪来:采集、标注与增强的三条可行路径

2.1 数据采集的三种来源与数量底线

舌苔识别系统的训练数据没有公开的标准数据集,这是第一个现实问题。常见的做法是从三个方向凑数据,而不是指望一次性找到完整数据集。

第一种来源是中医诊断学教材的电子版插图。人民卫生出版社、中国中医药出版社的教材里,舌象章节通常配有几十张典型舌图,覆盖淡红舌、红舌、紫舌、胖大舌、齿痕舌等常见类型。这些图像分辨率不高,但胜在类别标注是现成的,教材文字会直接告诉你“这是薄白苔”“这是黄腻苔”。能截多少截多少,一百多张总能凑到。

第二种来源是自行拍摄,找同学、朋友帮忙,在自然光下用手机后置摄像头拍舌头,每张图拍摄时记录舌象特征。这种方式采集的图像质量参差不齐,但真实场景里系统面对的就是这种图,所以反而对提升模型泛化能力有帮助。注意拍摄角度,舌头自然伸出、舌面平展、光线均匀,不要让舌尖上翘,否则舌面反光严重,后面训练时模型会把反光当成特征。

第三种来源是公开医学图像库。部分中医药大学的学位论文附录里会放舌象图,PubMed Central 上也有少量中医舌诊相关论文附带图像素材。这些来源涉及版权和伦理问题,使用时要谨慎,毕业论文里注明图片来源即可,不要打包发布。

数量底线是多少?分类任务单类别至少 300 张,检测任务(框出舌体区域)至少 500 张图、每张图一个边界框。少于这个量,模型训练时验证集波动会非常剧烈,准确率可能从 90% 跳到 60%,你无法判断是模型问题还是数据问题。如果实在凑不够,宁可减少类别数量,比如从“薄白苔、白腻苔、黄腻苔、剥苔”四类降到“正常、白苔、黄苔”三类,也比硬上八分类导致每个类别只有几十张图强得多。

2.2 用 LabelImg 做目标检测标注:安装与标注规范

检测任务需要框出舌体位置,这一步用 LabelImg 完成。它依赖 PyQt5,很多人在安装环节就卡住了——不是 LabelImg 本身的问题,是 PyQt5 在 Python 3.10 之后的部分版本上安装时容易出状况,后面避坑章节里详细展开。先看正常流程:

# Python 3.8/3.9 环境最稳 pip install labelImg # 启动标注工具 labelImg # 如果启动报错,指定图片目录和预定义的类别文件 labelImg ./data/raw_images ./data/classes.txt

启动后界面很直观:左上角是图片列表,中间是画布,右侧是标注框属性面板。按W键进入框选模式,按住鼠标左键从舌体左上角拖到右下角,松开后弹出类别选择框,选中tongue,按Ctrl+S保存。每一张图生成的 XML 文件与图片同名,保存在同一目录下,里面记录的是归一化后的边界框坐标和类别名。

标注规范有三条硬性要求。第一,舌体边界框要包含整个舌面、舌尖和舌根可见部分,但不要把嘴唇、牙齿框进去,这些区域会引入背景噪声。第二,如果一张图里同时出现舌面和舌下静脉——有的采集图为了展示舌下络脉会拍舌底——不要标这种图,舌底和舌面特征差异太大,混在一起训练会导致检测框在两者之间摇摆。第三,每张图标注完成后检查一下 XML 里的坐标值,确保 xmin < xmax、ymin < ymax,LabelImg 偶尔会生成反坐标。

2.3 数据增强配置:让 500 张图撑起 3000 张的效果

舌苔图像的数量上限基本就在几百张,硬加卷积层、加深网络都没用,这时候数据增强就是唯一的“后悔药”。用 PyTorch 的torchvision.transforms做在线增强,在每次训练迭代时随机变换输入图像,相当于让模型看到更多样本。

import torchvision.transforms as T train_transform = T.Compose([ T.RandomResizedCrop(size=(224, 224), scale=(0.7, 1.0)), T.RandomHorizontalFlip(p=0.5), T.RandomRotation(degrees=10), T.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.2, hue=0.05), T.ToTensor(), T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) val_transform = T.Compose([ T.Resize((224, 224)), T.ToTensor(), T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])

RandomResizedCrop是这里最关键的增强手段,它会随机裁剪一块区域再缩放到 224×224,模拟舌体在画面中位置不固定、大小不一的情况。scale=(0.7, 1.0)表示裁剪区域占原图面积的 70% 到 100%,不要设得太低,否则会把舌体切碎。RandomRotation只旋转 10 度,舌象采集时舌头方向基本是正的,旋转太多会产生不符合真实分布的训练样本。ColorJitter里的亮度调整用一个经验值:brightness 不超过 0.3,超过之后舌色会失真,模型学到的是亮度变化而不是舌色特征。

验证集不要做任何随机增强,只做 Resize 和 Normalize,否则评估指标会在不同 epoch 之间跳变,你分不清是模型变好了还是随机裁剪碰巧让验证集变简单了。

3. 模型选型与训练调参:从 ResNet 到轻量级方案的取舍

3.1 舌苔识别的模型选型:为什么首选 ResNet50 而不是 Vision Transformer

舌苔识别的基础方案是“迁移学习 + CNN 分类”,检测部分用 Faster R-CNN 或 YOLO 做舌体定位。对于毕业设计而言,ResNet50 是最稳的选择,不是因为它最好,而是因为它在“数据量少 + 训练时间短 + 效果可接受”三个约束下最容易达到平衡。

ResNet50 有 2560 万参数,用 ImageNet 预训练权重做初始化,迁移到舌苔分类任务上,500 张训练图也能训出像样的结果,在简单的四分类任务上准确率可以去到 88% 到 93%。Vision Transformer(ViT)在同数据量下表现反而不如 ResNet——它需要更多数据来学习图像的位置编码和全局注意力模式,小数据集上容易欠拟合。EfficientNet 是一个可以考虑的替代,它通过神经架构搜索得到,相同精度下参数量比 ResNet 少,但迁移学习时对预训练权重的依赖更强,PyTorch 官方权重只有 ImageNet 版本,在舌苔这种域差异较大的场景上不一定比 ResNet 表现好。

检测部分的选择同理。Faster R-CNN 精度高但推理速度慢,YOLOv5 速度快且在小数据集上更容易过拟合到高 mAP。建议用 YOLOv5s,depth_multiple=0.33,模型体量小,一张 640×640 的图在 CPU 上跑推理大约 1 到 2 秒,UI 里做实时检测时不会让界面卡死太久。

3.2 分类模型的训练代码:迁移学习的关键参数含义

用 PyTorch 加载预训练 ResNet50、冻结前几层、只微调最后一两个残差块,这是小数据集迁移学习的标准做法。

import torch import torch.nn as nn from torchvision import models model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1) # 冻结前 5 个残差块 for name, param in model.named_parameters(): if "layer4" not in name and "fc" not in name: param.requires_grad = False # 替换全连接层,num_classes 根据你的苔色/舌色类别数设置 num_classes = 4 model.fc = nn.Linear(model.fc.in_features, num_classes) optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-4, weight_decay=1e-4) scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, mode="min", factor=0.5, patience=3, verbose=True ) criterion = nn.CrossEntropyLoss()

layer4是 ResNet50 最后一个残差块,冻结 layer4 之前的全部参数,只微调最后一个块和新的全连接层。这样做的原因是 ImageNet 学习到的浅层特征是通用边缘、纹理,对舌苔同样有效;而 layer4 学到的高级语义特征更贴近 ImageNet 的类别分布,需要针对舌苔重新适应。lr=1e-4比从头训练常用的 1e-3 低一个量级,因为随机初始化的全连接层需要相对大的学习率,但预训练权重的微调幅度要小,这个值在实际训练中经过验证是安全的。

ReduceLROnPlateau的patience=3表示验证集损失连续 3 个 epoch 不下降就将学习率减半。训练 30 到 40 个 epoch,batch_size 设 16 或 32,如果训练损失下降但验证损失震荡,首先降低学习率而不是增加数据。

3.3 检测模型的训练命令与关键指标解读

YOLOv5 的训练用官方仓库的命令行工具,从 GitHub 拉代码后直接训练,这份产物里配套的源码包里一般也是这同一套流程。

# 拉取仓库(注意:如果你拿到的源码是打包好的,直接用里面的 train.py) git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt # 准备数据集目录结构 # dataset/ # images/ # train/ # 训练图片 # val/ # 验证图片 # labels/ # train/ # 与图片同名的 .txt 标注文件 # val/ # 训练 100 个 epoch,batch_size 8,输入分辨率 640 python train.py --img 640 --batch 8 --epochs 100 \ --data ../tongue_dataset.yaml --weights yolov5s.pt --device 0

YOLO 的标注格式是每个 .txt 文件一行一个目标:class x_center y_center width height,四个坐标值都用图片宽高归一化到 0 到 1。LabelImg 保存的是 VOC XML 格式,需要转换成 YOLO 格式,转换脚本几十行就能写完,网上搜“voc_to_yolo.py”会有大量现成实现,但要注意边界框坐标的归一化分母是图片原始宽高,不是缩放后的尺寸。

训练结束后看两个指标:P是精确率,表示模型框出来的舌体位置有多少是对的;R是召回率,表示真实舌体有多少被找到了。舌苔检测场景里,P 和 R 要兼顾,如果 R 高但 P 低——即框了很多“疑似舌体”的区域——会导致后续分类模块读入误检区域,全系统准确率下降。device 0表示用第一块 GPU,如果你没有 GPU,去掉这个参数即可,但训练时间会从几十分钟拉长到数小时。

4. PyQt5 界面集成:把模型推理封装进桌面应用

4.1 界面功能拆解:图片选择、推理显示、结果保存

PyQt5 界面是这个系统里最直观的一部分,答辩时评委看的就是这里。界面需要四个核心区域:图像显示区、检测结果区、操作按钮区、历史记录区。

图像显示区用QLabel实现,通过QPixmap加载图片后做等比缩放显示。检测结果区用一个QTextEdit或QTableWidget展示模型输出的类别、置信度和边界框坐标。操作按钮区至少包含“打开图片”“开始检测”“保存结果”三个按钮。历史记录区按时间顺序列出每次检测的图片路径和结果,方便演示时连续检测多张图不用重复打开文件。

核心交互流程是:用户点击“打开图片”选择一张舌象图,程序加载图片并显示在 QLabel 上;点击“开始检测”,后端调用训练好的模型完成推理,把检测框画在原图上(用 OpenCV 的cv2.rectangle画边界框、cv2.putText写类别),同时把结构化结果写入结果区;用户点击“保存结果”,程序把带框图片和文本结果写入指定目录。

def on_detect_clicked(self): if self.image_path is None: return # 读取图像并预处理 img = cv2.imread(self.image_path) img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) pil_img = Image.fromarray(img_rgb) # 分类模型推理 tensor = self.val_transform(pil_img).unsqueeze(0).to(self.device) with torch.no_grad(): output = self.model(tensor) pred_idx = torch.argmax(output, dim=1).item() confidence = torch.softmax(output, dim=1)[0][pred_idx].item() # 在界面上显示结果 self.result_text.setText( f"识别结果: {self.class_names[pred_idx]}\n" f"置信度: {confidence:.2%}" )

这段代码的时序是:先读图,再做预处理(Resize、转张量、归一化),然后 forward 推理得到类别索引和置信度。注意unsqueeze(0)是把单张图变成 batch size 为 1 的四维张量,形状是[1, 3, 224, 224],模型要求输入是四维。

4.2 用 QThread 避免界面卡顿:推理放主线程是最大败笔

新手最容易翻车的点就是把模型推理直接写进按钮的槽函数里。模型推理在 CPU 上耗时 1 到 3 秒,在这段时间里 Qt 的事件循环被阻塞,界面表现为“假死”——用户点了按钮没反应,窗口标题栏显示“未响应”,如果此时强行拖动窗口,Windows 甚至会弹出“是否结束进程”的提示框。

正确的做法是把推理放到QThread的子线程里执行,推理期间主线程继续处理界面消息,进度条可以转、按钮可以响应。实现方式有两种:继承QThread重写run()方法,或者使用QObject + moveToThread。对于单个推理任务,用继承QThread的方式更直接。

class InferenceThread(QThread): result_ready = pyqtSignal(tuple, float) def __init__(self, image_path, model, transform, device): super().__init__() self.image_path = image_path self.model = model self.transform = transform self.device = device def run(self): # 推理代码与 on_detect_clicked 中的一致 img = cv2.imread(self.image_path) # ... 预处理与 forward ... self.result_ready.emit((class_name, bbox), confidence)

在主界面的按钮槽函数里启动这个线程,并用result_ready信号连接一个更新界面的槽函数。跨线程更新界面必须走信号槽机制,不能在线程里直接调用self.result_text.setText(),因为 UI 控件不是线程安全的,直接调用会造成随机崩溃或状态错乱。模型对象在线程间共享时,要确保推理时不写模型本身的状态(如不调用model.train()、不更新 batch norm 的 running_mean),否则两个线程打架会出奇怪的指标波动。

4.3 显示缩放与中文乱码:两个必踩的显示坑

第一个坑是图像缩放。QLabel 显示图片时,如果用QPixmap.scaled(self.label_width, self.label_height)会破坏图片的宽高比,导致舌体被横向或纵向拉伸,影响用户对检测框位置的判断。要计算等比缩放的目标大小:

def fit_image_to_label(pixmap, label_width, label_height): w, h = pixmap.width(), pixmap.height() ratio = min(label_width / w, label_height / h) new_w, new_h = int(w * ratio), int(h * ratio) return pixmap.scaled(new_w, new_h, Qt.KeepAspectRatio, Qt.SmoothTransformation)

第二个坑是中文乱码。PyQt5 控件默认字体可能不支持中文字符,在 Windows 上表现为方框或问号。在窗口初始化时设置全局字体:

from PyQt5.QtGui import QFont app.setFont(QFont("Microsoft YaHei", 9))

这一行必须在创建任何控件之前执行,否则已创建的控件不会响应字体更新。macOS 下换成 "PingFang SC",Linux 下用 "WenQuanYi Micro Hei"。如果你画的检测框上方用 OpenCV 的putText写中文类别名,OpenCV 默认字体不支持中文,会出现乱码,解决方法是只用英文类名(如White_Greasy)显示在框上,完整中文名称只写在结果文本框里。

5. 训练与部署避坑:标注、显存、依赖的九条实战记录

5.1 标注阶段翻车:LabelImg 闪退与坐标格式混乱

现象一:LabelImg 打开图片目录后闪退,或者框选时程序直接崩溃退出。

原因:多半是 PyQt5 版本与 Python 版本不匹配。LabelImg 的setup.py依赖PyQt5>=5.10,但 PyQt5 从 5.15 开始在 Python 3.10 以上版本存在已知兼容问题,部分构建在 QGraphicsView 渲染时崩溃。

解决:创建 Python 3.8 的虚拟环境后重新安装。如果不想建新环境,降级 PyQt5 到 5.15.4:

pip install PyQt5==5.15.4

现象二:训练 YOLO 时 loss 从一开始就是 nan,几轮后彻底不下降。

原因:标注坐标没有归一化。LabelImg 保存的 VOC XML 里坐标是原始像素值,直接拿来训练 YOLO 时数值范围在数百到数千之间,模型输出的边界框回归值根本对不上。

解决:写转换脚本时把坐标统一除以图片宽高,得到 [0,1] 区间。检查转换结果有一个快速方法——打开转换后的 txt 文件,看里面的坐标值是不是都在 0 到 1 之间,如果有大于 1 的,肯定漏了归一化。

5.2 训练阶段翻车:ResNet 冻结层导致梯度不更新与显存崩溃

现象三:训练 ResNet50 时 loss 一直不下降,准确率停留在随机水平。

原因:requires_grad=False设置得太狠,把 layer4 也冻住了。named_parameters()遍历时如果你的字符串匹配条件写成了"layer" in name,会把 layer1 到 layer4 全部冻结,整个模型只有最后一个全连接层在训练,而它从随机初始化开始学,根本学不动。

解决:检查你 freeze 的条件,确认只有前三个残差块(layer1、layer2、layer3)被冻结,layer4 保持可训练。打印model.layer4[0].conv1.weight.requires_grad验证一下,输出 True 才对。

现象四:batch_size 设 32,一张 1080Ti(11GB 显存)直接 OOM。

原因:YOLOv5s 在 640×640 分辨率下每个 batch 的显存开销约 4GB,但 ResNet50 的分类任务如果同时在显存里加载了主干网络和候选框特征,会额外吃掉 3 到 4GB。你的显存总量不够,不是显卡差,是 batch_size 和分辨率组合不当。

解决:把 batch_size 降到 8,--img降到 480,两个改动叠加显存占用能降到原来的三分之一左右。数据增强里做了 RandomResizedCrop 到 224,所以 YOLO 的输入分辨率低一点不影响分类效果。

5.3 界面与部署阶段翻车:模型推理结果不对与 PyQt5 环境碎一地

现象五:相同的舌苔图片,训练时准确率高,部署到 PyQt5 应用里识别结果全错。

原因:数据预处理的差异。训练时数据管道里有RandomResizedCrop、RandomHorizontalFlip等随机增强,但推理时只做 Resize 和 Normalize,这是对的。问题出在 Resize 的尺寸:训练代码里 Resize 到 224 后进模型,界面代码里如果用了不同的尺寸(比如 256),模型输入分布就变了。

解决:把界面代码里的 transform 与训练时的val_transform保持一致,连Normalize的 mean 和 std 都不许改。最稳妥的做法是把 val_transform 序列化保存到模型权重同一个目录:

import pickle with open("val_transform.pkl", "wb") as f: pickle.dump(val_transform, f)

部署时直接加载,而不是手抄一遍。

现象六:用 PyInstaller 打包后,程序启动显示ModuleNotFoundError。

原因:PyTorch 和 PyQt5 都是动态导入依赖很多的包,PyInstaller 默认不会把所有动态加载的模块都打进去。torch的 backend 库、PyQt5的 Qt 插件(platforms 目录里的 qwindows.dll)都是典型的漏网之鱼。

解决:不要手动写--hidden-import一个个补。先创建一个干净的虚拟环境,只装上程序运行所需的库,从这个环境用 PyInstaller 打包,错误会少很多。打包命令加上:

pyinstaller --onefile --windowed \ --hidden-import PyQt5.QtCore \ --hidden-import PyQt5.QtGui \ --hidden-import PyQt5.QtWidgets \ main.py

现象七:PyQt5 安装时与 labelme 或其他 GUI 库冲突,pip 报 “Cannot uninstall X”。

原因:系统里残留了其他版本的 PyQt5,pip 尝试先卸载再安装,但卸载失败导致进程中断。

解决:别用 pip 硬磕,直接指定独立虚拟环境。python -m venv venv_dp后激活环境再装 PyQt5,全程不影响系统环境。

现象八:基于深度学习的舌苔识别系统在深色背景下拍摄的图,界面上检测框抖动明显。

原因:不是模型问题,是 Cosine 相似度/置信度阈值设得太低。模型对模糊图像输出接近 0.5 的置信度,界面里如果设置了动态阈值,波动就会造成框的抖动。

解决:把置信度阈值设为固定值 0.6,低于此值不画框,只显示“无法识别”。这能过滤掉大部分拍摄质量差的样本,模型精度指标也会更好看。

现象九:训练数据里舌苔类别不平衡,多数类准确率 95%,少数类只有 40%。

原因:数据分布天然不均衡——正常舌象和薄白苔占大多数,剥苔、黑苔样本极少。模型倾向于把所有样本预测为多数类。

解决:最直接的办法是给CrossEntropyLoss传类别权重,权重与类别样本数成反比:

class_counts = torch.tensor([300, 120, 50, 30]) class_weights = 1.0 / class_counts class_weights = class_weights / class_weights.sum() * num_classes criterion = nn.CrossEntropyLoss(weight=class_weights.to(device))

这个公式做了归一化,权重总和等于类别数,不会放大学习率的影响。如果用了权重后少数类准确率上升但多数类下降,可以在论文里把“单类别准确率”和“宏平均准确率”都展示出来,说明这是类别均衡策略的必然结果,答辩时更能自圆其说。

6. 模型导出与边缘部署:把 PyTorch 模型转成 ONNX 并提速推理

训练完成、界面能跑通之后,如果你想让系统在答辩现场不那么“卡顿”,或者想给论文加一个“轻量化部署”的亮点章节,有两种实用的进阶手段:ONNX 导出和 TorchScript 量化。前者可以让你脱离 Python 环境用 ONNX Runtime 推理,后者能在不改变代码结构的情况下压缩模型体积和推理时间。

ONNX 导出的核心价值在推理速度。PyTorch 的 eager 模式在 CPU 上有较高的解释开销,ONNX Runtime 通过静态图优化和算子融合,通常能获得 1.5 到 2 倍的速度提升。对一个 224×224 的输入,ResNet50 纯 PyTorch CPU 推理约 120ms,ONNX Runtime 可以压到 70ms 左右。听起来不多,但界面里做实时窗口检测时,每一帧都节省 50ms,体验差异是明显的。

import torch import onnxruntime as ort # 导出模型到 ONNX 格式 def export_onnx(model, export_path, input_size=(1, 3, 224, 224)): model.eval() dummy_input = torch.randn(*input_size, device="cpu") torch.onnx.export( model, dummy_input, export_path, input_names=["input"], output_names=["output"], opset_version=13, dynamic_axes={"input": {0: "batch_size"}}, ) # 用 ONNX Runtime 加载并推理 ort_session = ort.InferenceSession("resnet50_tongue.onnx") input_name = ort_session.get_inputs()[0].name output_name = ort_session.get_outputs()[0].name input_tensor = torch.randn(1, 3, 224, 224).numpy() result = ort_session.run([output_name], {input_name: input_tensor}) pred = torch.tensor(result[0]).argmax(dim=1).item()

导出时的dynamic_axes参数声明第 0 维(batch 维度)是可变的。如果你只做单张图片推理,可以不设置动态轴,这样 ONNX Runtime 可以做更多静态优化,进一步提升推理速度。我一般建议单张推理时就固定 batch size 为 1,OCR 和医学图像这类场景几乎不会批量推理,没必要保留动态维度的灵活性。

TorchScript 量化则是另一个方向。torch.quantization.quantize_dynamic可以把模型里的线性层和卷积层从 float32 转为 int8,模型体积缩小约四倍,推理速度在 CPU 上再提升 30% 到 50%。动态量化不需要校准数据集,一行代码就能完成:

quantized_model = torch.quantization.quantize_dynamic( model, {torch.nn.Linear, torch.nn.Conv2d}, dtype=torch.qint8 )

注意量化的代价是精度损失,舌苔分类这种类别间差异较大的任务通常损失 1% 到 2% 的准确率,可以接受。但如果你的类别里有“薄白苔”和“白腻苔”这种视觉特征高度相似的组合,损失可能到 5%,就不建议量化了。测试方法很简单:用同一批验证图跑原模型和量化模型,对比准确率差值,超过 3% 就回退到原始模型。

我对这类系统的一个习惯是:答辩前一周不做任何训练参数调整,只做导出和界面稳定性测试。原因是训练调参是无限游戏,而部署是有限任务——在这个阶段把精力花在验证“演示时无论如何点都不会崩”上,远比再刷两三个点的准确率更有价值。最终交付的成果里,模型权重、推理代码、界面程序、论文四件事各归其位,一套完整的基于深度学习的舌苔识别检测鉴定系统,就是这样收敛到“能演示、能复现、能讲清楚”的状态。希望这篇拆解能把你的落地过程缩短一半,少走那些我已经替你踩过的弯路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 14:03:14

神经视频编码技术解析:从传统Codec到端到端率失真优化

打开视频编码的技术资料&#xff0c;到处都在讲 H.264、H.265、AV1&#xff0c;偶尔冒出个 VVC/H.266。过去二十年&#xff0c;我们一直在跟 DCT、量化、熵编码这些“手写规则”打交道。最近几年方向有点变了&#xff1a;一批研究者开始让 Codec 自己“学习”——不是工程调参意…

作者头像 李华
网站建设 2026/9/28 14:02:42

OpenClaw全解析:从零部署到生态工具实战

1. OpenClaw刷屏背后&#xff1a;一款开源Agent平台凭什么出圈最近这几天&#xff0c;OpenClaw在开发者圈子里热度高得离谱。GitHub趋势榜上挂着它&#xff0c;技术群里讨论它&#xff0c;连身边做运营的朋友都在问我怎么部署。作为一个长期折腾AI Agent框架的人&#xff0c;我…

作者头像 李华
网站建设 2026/9/28 14:01:16

ESP32-S3-N16R8在PlatformIO中的自定义板级配置全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 14:00:42

Taste-Bench:智能体决策品味评测框架解析

1. Taste-Bench 不是又一个“准确率测试”&#xff0c;而是给智能体装上“味觉神经”最近刷到一条技术动态&#xff1a;“微软发布Taste-Bench&#xff0c;测智能体决策品味”——第一反应是&#xff1a;啥&#xff1f;AI还有“品味”&#xff1f;不是该比谁答得快、谁算得准、…

作者头像 李华
网站建设 2026/9/28 14:00:29

CSDN文章转PDF教程:Playwright无头浏览器从原理到批量实践

1. 先说清楚&#xff1a;为什么非要把 CSDN 文章弄成 PDF前几天想收藏一篇讲内核调度器的深度好文&#xff0c;原打算直接在浏览器里按CtrlP打印成 PDF 存到本地&#xff0c;结果导出后一看&#xff0c;页面上全是侧边栏、相关推荐、底部广告和作者卡片&#xff0c;正文只占中间…

作者头像 李华
网站建设 2026/9/28 13:58:00

CST共面波导色散曲线仿真全流程与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华