news 2026/9/28 17:15:51

中医舌苔检测Web应用:Python+YOLOv5+SAM+ResNet50多模型协作

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
中医舌苔检测Web应用:Python+YOLOv5+SAM+ResNet50多模型协作

简介:这是一份面向计算机、数学、电子信息类专业学生的中医舌苔分析Web应用完整开发源码,适合作为课程设计、期末大作业或毕业设计参考。项目核心是基于深度学习的舌象四维分类——舌色、舌苔色、薄厚、腻否,处理流程先由YOLOv5目标检测与Segment Anything模型完成舌象分割,再由ResNet50残差网络完成分类,多模型拼接的工程化设计对视觉应用入门与进阶都很有借鉴意义。压缩包为zip格式,共84个文件,大小约2.5MB,其中22个Python文件承载后端算法、接口与模型推理,24个Vue文件构成浏览器端界面,另有SQL数据库、JS脚本、JSON配置、CSS样式和项目说明文档,目录结构清晰,可对照理解前后端协作与部署逻辑;同时包含模型调用、路由配置和数据库表结构,呈现完整项目范式。目前已有369人学习下载;资源内包含可直接运行的源码和使用前必读说明,需要读者具备一定Python和Web基础,能自行调试并扩展功能。

1. 中医舌苔项目 Web 应用:一条检测、分割、分类全在线的 Python 实现

拖一张舌头照片到网页里,几秒钟后页面弹回舌色、舌苔色、薄厚度、腻否四个维度的分析结果——这个中医舌苔项目就这么直接。它不是一个只跑算法脚本的玩具,而是一套完整的 Web 应用:后端用 Flask 组织 API,前端是 Vite 构建的 Vue 工程,深度学习链路由 YOLOv5 检测、Segment Anything 分割、ResNet50 分类三块拼接而成,全部跑在浏览器交互层背后。压缩包里是项目全部 python 源码、说明文档和数据库文件,按文档步骤能直接本地复现。想用 Python 做课程设计、期末大作业或者毕设参考的,这份源码比从头搭框架省太多时间;想研究舌象分类的,多模型拼接的思路也很值得拆开看。

2. 技术拆解:YOLOv5 + SAM + ResNet50 三条模型链怎么协作

2.1 先看目录:这个项目把后端模块放得清清楚楚

解压后第一件事不是急着跑命令,而是把文件层级认清楚。项目的后端主体非常接近 Flask 标准工程结构:run.py是启动入口,requirements.txt是依赖清单,config目录下面放着配置,routes目录定义 API 路由,models目录是模型封装,orm目录负责和数据库打交道,根目录还有一个AppDatabase.db,这是 SQLite 的数据库文件。前端则整体收在frontend目录里,里面有package.json、vite.config.js、src、public,一眼就能认出是 Vite + Vue 的工程。

这种组织方式对复现很友好。很多课程设计项目喜欢把所有代码堆在两个 py 文件里,看起来简单,真要改功能时改到怀疑人生。这个项目把路由、模型、ORM 分开,意味着你要替换某个算法时,只需要动models目录里的对应类,路由层和前端基本不用碰。对我这种习惯先读目录再动手的人来说,这种结构省掉了一大半"这文件是干嘛的"的排查时间。

前端部分还带了vitest.config.js和cypress.config.js,说明项目里配了单元测试和 E2E 测试。虽然跑主流程用不到,但如果你要做二次开发,拿它当测试脚手架也挺合适。需要注意一点:README.md和Readme.md同时在工程目录里出现,以Readme.md为准,里面写的是启动顺序,别把两个文件混着看。

2.2 为什么是三个模型拼接,而不是一个模型打天下

项目描述里反复提到"多模型拼接",这是理解整个项目的关键。舌象分析这个任务其实分两段:先要从一张杂乱背景的照片里定位出舌体,再对舌体本身做分类。如果只用一个分类模型,它会不停地把背景噪声也学进去,比如把嘴唇、牙齿、舌头阴影当成特征。项目里选的方案是先把脏活拆开。

第一段是 YOLOv5 目标检测。它负责输出舌体位置的边界框,把舌头从照片中切出来。这一步的好处是后面的模型不用再关心"舌头到底在哪"这件事,输入被固定成一块相对干净的舌象区域。

第二段是 Segment Anything 模型,也就是 Meta 开源的 SAM。YOLOv5 给的是矩形框,框里仍然带着嘴唇、牙齿甚至皮肤,SAM 做的是像素级分割,生成精确的舌体掩膜,用掩膜把背景像素直接抹掉。这一步非常关键,因为 ResNet50 分类时如果看到嘴唇或者牙齿底色,很容易把舌色判断偏。

第三段是 ResNet50 残差网络,做四分类任务:舌色、舌苔色、薄厚、腻否。值得注意的是,这四个维度不是简单的四分类输出,项目中把它拆成多任务。通常实现方式是在 ResNet50 的最后一层换上多个分类头,每个头负责一个维度,我在实际项目里也见过拆成四个独立模型的做法,但那样部署太重。这个项目选择共享主干、多个输出头的做法,训练和推理效率都更高。

说到底,三模型拼接不是炫技,而是把"定位—分割—分类"三个难度不同的问题交给最擅长它们的模型。这种思路在你以后做其他医学影像分类也通用:先检测再分割再分类的三段式,往往比端到端强行上一个大模型效果更可控,也更省钱。

2.3 推理链路核心代码解读

把链路串起来的推理代码是这个项目最值得读的部分。常见的写法是在routes里注册一个上传接口,接口内部依次调用三个模型的封装类,最后拼成 JSON 返回。下面这段代码是这类推理链路的典型骨架,我在代码里加了推理顺序和参数作用的标注。

# inference.py —— 全局推理骨架(逻辑示意) import cv2 import numpy as np from models.yolo import YOLODetector from models.sam import SAMSegmentor from models.resnet import ResNetClassifier # 初始化三个模型,detector 是 YOLOv5,sam 是分割模型,classifier 是 ResNet50 def run_tongue_pipeline(image_path: str) -> dict: # 1) YOLOv5 目标检测,返回 [x1, y1, x2, y2] 的边界框 bbox = YOLODetector.detect(image_path) if bbox is None: # 如果检测不到舌体,直接给兜底结果,避免后续模型报错 return {"code": 400, "message": "未检测到舌体,请重新拍摄"} # 2) 按边界框裁剪原图,缩小 SAM 的输入范围,降低显存占用 img = cv2.imread(image_path) x1, y1, x2, y2 = [int(v) for v in bbox] crop = img[y1:y2, x1:x2] # 3) SAM 分割,返回与原图同尺寸的 0/1 掩膜 mask = SAMSegmentor.segment(crop) # 用掩膜把非舌体区域置为黑色背景 masked = cv2.bitwise_and(crop, crop, mask=mask.astype(np.uint8)) # 4) ResNet50 对掩膜后的舌象做多维度分类 result = ResNetClassifier.classify(masked) # 返回示例:{"tongue_color": "淡红", "coating_color": "白", "thickness": "薄", "greasiness": "不腻"} return {"code": 200, "data": result}

四段逻辑里有两个参数值得盯一下:第一个是bbox为空时的兜底分支,很多复现者图省事会直接让 YOLOv5 返回 None,然后下一行就崩了,这里必须有条件判断。第二个是cv2.bitwise_and之前的掩膜类型转换,SAM 原始输出往往是 float 类型,不转成uint8,OpenCV 的位运算会直接报类型错误。这两个点就是"看起来照着写、一跑就翻车"的高发区。

分类器部分,项目用的是 ResNet50 预训练权重加微调。分类头输出的原始 logits 需要经过 softmax 转成概率,每个维度保留概率最高的那一个作为预测标签。实际部署时为了省一次推理,常见做法是把四个分类头拼成一个全连接矩阵做一次前向推理,只带四个 argmax 出来。

3. 本地复现:从 Python 环境到前端构建,照着跑就能过

3.1 虚拟环境与依赖安装

我接到这种带深度学习模型的源码包,第一原则永远是:不要用全局 Python 跑,先把环境隔离出来。项目依赖里既要有torch、torchvision、opencv-python这种重量级库,又要有 Flask、SQLAlchemy 之类的 Web 框架,混在全局环境里轻则版本打架,重则把系统里别的项目搞到跑不起来。

打开终端,在项目根目录执行下面的命令。

# 进入项目根目录,创建 Python 3.8+ 的虚拟环境 python -m venv venv # Linux / macOS 激活环境 source venv/bin/activate # Windows 激活环境 venv\Scripts\activate # 安装项目依赖,requirements.txt 里已经锁好了版本 pip install -r requirements.txt

这里说明一下为什么建议用venv而不是直接conda create。如果你是 Anaconda 用户,用 conda 创建环境当然没问题,但很多课程设计机器上既有 conda 又有系统 Python,两者混着用容易把当前环境变量搞乱。venv 是干净且零额外依赖的方案,只要 Python 版本对得上,就不会有意外。

requirements.txt里的依赖里大概率会同时出现torch和torchvision。这两个包的版本必须严格配套,比如torch 1.13.1就要配torchvision 0.14.1。在安装完成后建议加跑一行验证。

# 验证 torch 和 torchvision 版本是否配套 python -c "import torch, torchvision; print(torch.__version__, torchvision.__version__)"

如果版本号对不上,比如 torch 是 2.0 而 torchvision 还是 0.14,后面加载 ResNet50 时大概率会收到RuntimeError: Couldn't load custom C++ ops之类的报错。这是我在帮别人排查时最常碰到的问题,没有之一。

3.2 后端启动与数据库初始化

依赖装好后,启动后端理论上只需要一行命令。项目根目录的run.py就是入口,它会读取config里的环境配置,创建 Flask app,然后监听默认端口。

# 在项目根目录,venv 环境下执行 python run.py

启动过程里有几个细节值得检查。第一,端口是不是被占用,Flask 默认监听 5000 端口,如果你本机已经有服务占了这个端口,启动会直接失败,报Address already in use,改config里的端口号就好。第二,启动日志里如果出现AppDatabase.db does not exist,说明数据库还没初始化。项目里AppDatabase.db是直接放在根目录的,如果压缩包被解压时漏了这个文件,就需要用 orm 目录下的建表逻辑来重建。

手动初始化数据库通常是这样的流程,项目里应该有对应的 CLI 命令,没有的话可以直接在 Python shell 里执行建表操作。

# 从项目根目录打开 Python 交互环境,执行建表 python -c "from orm import init_db; init_db()"

init_db函数一般会在 orm 包的__init__.py里定义,它会把 models 目录下定义的数据模型通过 SQLAlchemy 映射成 SQLite 表。执行完后再看根目录,AppDatabase.db文件会重新生成。这里有个细节:SQLite 文件一旦生成在根目录,后续启动run.py时千万不要把启动目录切到子文件夹,否则 Flask 会去子文件夹里找AppDatabase.db,找不到就自动新建一个空库,你会遇到"明明建了表为什么提示没有这个表"的诡异问题。这个坑我后面在避坑章节还会展开。

3.3 前端 Vite + Vue 构建与联调

后端起来了,接下来是前端。这部分对纯 Python 用户来说可能是最陌生的,但其实流程非常机械。先确认你机器上有 Node.js,建议 16 以上的版本,然后进入frontend目录。

# 进入前端目录 cd frontend # 安装依赖,package.json 里已经声明了全部前端包 npm install # 开发模式启动,默认走 Vite 的热更新 npm run dev

npm install需要一点耐心,因为 Vue 项目会把 axios、element-plus 这一层的依赖全拉下来,网速慢的话会有几分钟空白。装完以后npm run dev会本地起一个 Vite 服务,默认端口通常是 5173,终端会显示可访问的地址。

联调前的最后一步是确认 API 地址。前端.env文件或者vite.config.js的 proxy 配置里,会把/api请求代理到后端地址。如果代理配置指错了端口,前端会一直报跨域或 404。常见的正确配置是后端跑在http://localhost:5000,前端开发服务器通过 proxy 把请求转发到5000。如果你用的是生产构建,还需要先执行npm run build,再把dist目录交给 Nginx 托管,同时 Nginx 里配一份反向代理到 Flask 的/api。

// vite.config.js —— 开发代理配置示意 export default { server: { proxy: { // 所有 /api 开头的请求,代理到 Flask 后端 '/api': { target: 'http://localhost:5000', changeOrigin: true } } } }

changeOrigin: true这个参数要注意,不写的话请求头里的 Host 名会保留成前端域名,后端如果做了域名校验就会返回异常。这是前后端联调里出现"要么跨域要么 404"时第一个要检查的地方。

4. 避坑手册:五个最容易翻车的点与解决办法

4.1 模型权重路径不对:加载不报错,推理结果全空

现象:程序能启动,前端页面也能打开,但上传一张舌头照片后,后端返回"未检测到舌体",或者分类结果始终是同一个值,换多少张图都一样。

原因:YOLOv5 和 SAM 的权重文件在项目里是以相对路径引用的,比如weights/best.pt。当你把项目从压缩包解压后,如果直接用了 IDE 里某个偏好的工作目录启动run.py,相对路径会解析到错误位置。模型加载到一半会走 catch 分支,表面上不报错,实际上加载的是随机初始化权重,推理结果自然全废。

解决:建议强制用绝对路径定位权重文件。在模型的加载代码里改成os.path.dirname(__file__)拼出权重目录,而不是用.表示当前目录。改完以后要重新启动后端,并盯着启动日志,确认类似Loaded weights from /absolute/path/best.pt的日志真正打出,这行日志比什么断言都靠谱。

4.2 torch 与 CUDA 版本不匹配:显卡形同虚设

现象:模型推理时 GPU 占用率始终是 0%,推理速度慢到每张图要十几秒,终端里不断出现UserWarning: CUDA initialization: CUDA_DEVICE_NOT_FOUND。

原因:装了 CPU 版的torch,或者 torch 的 CUDA 版本和显卡驱动不匹配。检查后发现 requirements.txt 里的torch是 PyPI 默认的 CPU 版本,这个版本用起来没问题,但根本不会调显卡。

解决:先把 GPU 上的推理显存需求压到最低,然后重装对应 CUDA 版本的 torch。NVIDIA 显卡先去官网查驱动支持的 CUDA 版本,再按对应命令安装,比如安装 CUDA 11.8 配套的 torch。

# 以 torch 2.0 + CUDA 11.8 为例,先卸载 CPU 版 pip uninstall torch torchvision # 安装 CUDA 12.1 版本,注意是 cu121 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121

如果显卡实在不支持,那就老老实实 CPU 推理,不要试图在代码里强制.to('cuda'),那只会收到 RuntimeError。项目功能上 CPU 也能跑通,只是慢,备选方案是缩小输入尺寸。

4.3 图片上传格式与大小:前端不拦,后端必崩

现象:上传 jpg 没问题,一上传 png 或者超高清大图,后端就 500,日志里是cv2.error: OpenCV(4.x) ... assertion failed。

原因:OpenCV 的imread对部分格式支持不完整,尤其是带透明通道的 PNG 或者 16 位深度的 TIFF。另外前端没做图片压缩,几张 12MP 的手机原图直接塞进来,后端cv2.resize时内存飙升,触发 OOM。

解决:前端上传组件里强制转码和压缩,统一输出为 RGB 的 JPEG,尺寸限制在 1024 像素以内。这个限制放在前端比后端做省事,并且用户体验也好。后端接口里也得再校验一次 Content-Type 和文件后缀,双保险。

# 后端校验文件类型的示意代码 ALLOWED_TYPES = {"image/jpeg", "image/png"} def valid_upload(file): # 检查 MIME 类型,忽略大小写 if file.mimetype not in ALLOWED_TYPES: return False return True

图片尺寸这块,项目原始代码如果没做预缩放,我一般会建议在 YOLOv5 检测前加一步,用cv2.resize把最长边压到 1280。超过这个尺寸对检测精度提升有限,但推理耗时和内存占用是成倍上涨的,得不偿失。

4.4 数据库文件或表缺失:登录注册全 500

现象:从压缩包解压后没看到AppDatabase.db,或者启动时它在根目录生成,但等页面注册时一直 500,日志提示sqlite3.OperationalError: no such table: user。

原因:项目自带的AppDatabase.db没被正确解压,或者启动了两次run.py,其中一次改变了工作目录,导致 SQLAlchemy 在另一个路径下新建了空库。

解决:第一步,先确认根目录下AppDatabase.db存在,并且文件大小不是 0 字节。如果是不存在或者为空,执行建表初始化。第二步,检查orm目录里有没有init_db之类的函数,没有的话就在run.py里手动调用db.create_all()。之后启动服务时固定目录,不要用python app/run.py这种从子目录执行的姿势,老老实实退到根目录再启动。

4.5 SAM 分割掩膜为空或全黑:分类结果完全不可用

现象:检测正常、分割也走完了,但分类结果永远是"舌色偏黑"之类的不合理输出。把中间结果保存出来一看,掩膜全黑。

原因:SAM 生成的概率图里,低于阈值的像素全被置为 0,而这个阈值得看项目自带的权重和数据。如果参数是写死的,比如mask_threshold=0.5,在一些对比度不高的舌象上就会把所有像素判定为背景,导致分割出的舌体直接变黑块。

解决:把掩膜生成后的像素值分布打出来,看最大最小值和均值。如果均值低于 0.1,先把阈值降到 0.3 再试。更稳的做法是不硬编码阈值,而是直接用 SAM 输出的 logits 取 top-k 像素来做自适应,效果会好很多。

# 自适应掩膜阈值示意:用 logits 分位数代替固定阈值 import numpy as np def adaptive_mask(pred_logits, quantile=0.7): # 取一个分位数作为阈值,避开对比度差异 threshold = np.quantile(pred_logits, quantile) return (pred_logits > threshold).astype(np.uint8)

要注意,这个兜底逻辑要和业务对齐:如果一张舌象本身就淡,阈值太低会把背景也划进来,此时应该结合 YOLOv5 的框做裁剪约束。我习惯把"分割结果可视化成中间图"的输出开关留在代码里,排查问题时比盯着一堆数字直观得多。

5. 进阶:替换成你自己的数据集和模型,并验证输出

当你想把项目改成自己的舌象数据集,核心改动集中在models目录和config。ResNet50 的分类头要按你的维度数量改,比如原项目是四个维度,每个维度 3 到 4 个类别,你要改成两个维度就删掉对应输出头,重新微调时冻结主干、只训练分类头,这样数据量小也不容易过拟合。YOLOv5 部分如果你要检测的是别的部位,需要重新标注数据,格式走 YOLOv5 的 label 格式,即每个目标一行class x_center y_center width height,注意是中心点加宽高的归一化坐标。

验证输出时我通常会做两个动作。第一,保存中间结果到debug目录,把 YOLOv5 画框图、SAM 掩膜图、ResNet50 掩膜分类图三张并排拼在一起,一眼就能看出是哪一环出问题。第二,做一个小批量一致性测试,统计同一个用户在相同光照条件下上传五张同一舌象照片的推理结果,看四个维度的输出是否稳定。稳定输出比单张错对重要,因为这类项目最大的风险就是随机性。

部署层面如果想让前后端不再分开启动,可以直接用 Nginx 托管frontend/dist静态文件,然后反向代理/api到 Flask。这样从用户视角只有一个端口,绕开了开发模式下的跨域问题。优化推理速度时,可以把 YOLOv5 的权重转成 ONNX 或者 TensorRT,但这种优化会让代码可读性变差,建议先把功能稳定跑通再去动。反向代理配置举例如下。

server { listen 80; # 静态文件路由 location / { root /path/to/frontend/dist; index index.html; } # API 反向代理 location /api/ { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; } }

从那以后我每次复现这种带深度学习的 Web 项目,都会强制自己先走一遍"核目录、查版本、启数据库、验中间结果"四步。前两步省去大部分环境问题,后两步把模型链路是否正常从黑匣子变成可见状态。你接手这份源码时,别直接双击运行,照着本文顺序走一遍,很多所谓的玄学报错会消失。希望帮到你。

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

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

多路Image Sensor同步的5个常见误区:从FSIN到MIPI时序

做过多路Image Sensor同步采集的工程师,基本都经历过这样的排查场景:4路相机拍同一个高速运动目标,上位机一看,每一路的画面都清晰流畅,但放在同一时间轴上一比对,帧不在一个点上;或者明明给所有…

作者头像 李华
网站建设 2026/9/28 17:15:18

RK3588S平台IMX415摄像头驱动从零调试实战:从设备树到4K出图

搞嵌入式视觉产品这几年,在RK3588S平台上调得最多的Sensor就是IMX415。这颗1/2.8英寸、830万像素的CMOS图像传感器,配上RK3588S的6 TOPS NPU和自带ISP,几乎成了中高端边缘AI盒子、视频会议终端、智能安防摄像头的标配方案。方案成熟不代表驱动…

作者头像 李华
网站建设 2026/9/28 17:15:17

RV1106平台H264视频流捕获与编码实战:从V4L2到MPP全链路解析

RV1106这颗芯片最近两年在低成本IPC、可视门铃、工业相机里的出镜率非常高,硬件集成度也确实是同价位里少有的。很多刚接触这块芯片的工程师拿到开发板后的第一件事,就是想把摄像头的画面采集下来,再编码成H264走网络推流或落盘,但…

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

水下管道YOLOv8检测全流程:数据、训练、部署与避坑指南

简介:面向水下管道目标检测的YOLOv8完整资源包,适用于海洋工程巡检与水下基础设施维护场景。包内数据集包含7971张已标注图像,标注类别统一为水下管道,同时给出YOLO格式的txt标签与VOC格式的xml标签,并已划分训练集、验…

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

从修仙挂机到赛博自动化:游戏外挂背后的技术架构

你有没有在一款修仙放置游戏里发现过这种诡异现象:凌晨三点,你刚上线准备做日常,好友列表里那位“道友”却已经显示在线,秘境扫荡、宗门任务、坊市抢购一个不落。你打招呼,对方不回;你盯着他,他…

作者头像 李华