
简介本资源为中医舌苔项目Web应用开发的完整Python源码包面向计算机、电子信息等专业学生及深度学习爱好者可作为课程设计、期末大作业或毕设的参考方案。项目核心是利用深度学习对舌象图片进行舌色、舌苔色、薄厚、腻否四维分类采用YOLOv5目标检测、Segment Anything分割与ResNet50分类的多模型拼接方案后端部署于Web应用用户可通过浏览器上传舌象并获取健康报告。压缩包共85个文件约2.49MB包含24个Vue前端组件、22个Python后端脚本、10个JavaScript文件及JSON、SQL、图片等配置与静态资源前后端目录结构清晰涵盖核心算法、数据库模型、路由与视图等模块。目前已有282人学习下载适合需要理解多模型协同与Web部署流程的读者参考借鉴。1. 舌象四维分类 Web 应用从上传一张舌头照片到拿到健康报告用户打开浏览器选一张舌象照片点上传几秒后页面返回舌色、舌苔色、薄厚、腻否四个维度的分类结果和一份健康报告——这套流程背后是一个 Python 后端加 Vue 前端的完整 Web 应用。这个资源包把整套源码和项目说明打包在一起后端用 Flask 组织路由和数据库核心算法侧串了 YOLOv5 做目标检测、Segment Anything 做舌象分割、ResNet50 做四维分类前端用 Vite 构建 Vue 单页应用。它适合做计算机、电子信息类专业的课程设计或毕设参考也适合想拆一个「深度学习模型怎么落到 Web 后端」完整链路的开发者。下面按「这套东西怎么跑起来、模型怎么串、坑在哪」的顺序拆一遍。2. 环境搭建与项目结构把后端 Flask 和前端 Vite 两条线跑通2.1 先看清目录里谁管什么拿到压缩包解压后根目录下是run.py、requirements.txt、AppDatabase.db和两个大目录application后端与frontend前端。这个划分很清晰后端负责算法推理和数据持久化前端只负责交互和展示。后端application里又分了config配置、core核心算法、models数据库模型、net神经网络模型、orm数据库映射、routes路由六个子模块__init__.py做应用初始化。前端frontend是标准的 Vite Vue 结构src下有assets、components、router、views入口是main.js和App.vuevite.config.js里配了打包和跨域代理。理解这个结构的关键是分清「算法」和「服务」的边界。net目录放的是网络结构定义core放的是推理调度逻辑routes才是对外的 HTTP 接口。很多人第一次读这类项目会迷路是因为把net里的模型定义当成了业务代码。实际调用链是前端请求打到routes的某个接口 → 接口调core里的推理函数 → 推理函数加载net里的模型并执行 → 结果写回models/orm对应的数据库表。2.2 后端依赖安装与启动先确认 Python 版本。这类项目通常跑在 Python 3.8 到 3.10 之间太新的版本3.12容易在 PyTorch 和部分科学计算库上翻车。建虚拟环境是基本操作# 创建并激活虚拟环境避免污染全局包 python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate # 安装后端依赖 pip install -r requirements.txtrequirements.txt里一般会包含 Flask、Flask-SQLAlchemy、PyTorch、torchvision、opencv-python、numpy 等。这里有个血泪经验PyTorch 的安装命令和 CUDA 版本强相关如果requirements.txt里写的是torchx.x.x而没有指定 index-urlpip 默认装的是 CPU 版。想用 GPU 推理得先去 PyTorch 官网查对应 CUDA 版本的安装命令单独装再装其余依赖。CPU 版也能跑只是单张图推理会慢几秒。装完依赖后看run.py它通常是整个应用的入口负责创建 Flask app 并启动服务# run.py 典型结构 from application import create_app app create_app() if __name__ __main__: # host 设为 0.0.0.0 才能被局域网其他设备访问 # debugTrue 仅开发阶段用生产环境必须关掉 app.run(host0.0.0.0, port5000, debugTrue)create_app()是工厂函数在application/__init__.py里定义负责注册蓝图、初始化数据库、加载配置。启动前要确认AppDatabase.db存在——这是 SQLite 数据库文件如果缺失要么项目初始化逻辑会自动建表要么需要手动执行建表脚本。启动命令就是python run.py看到 Flask 默认的启动日志说明后端通了。2.3 前端依赖安装与跨域配置前端进frontend目录先装依赖再起开发服务器cd frontend npm install npm run devvite.config.js里最关键的是server.proxy配置。前端开发服务器默认跑在 5173 端口后端在 5000浏览器直接请求后端会撞上跨域限制。Vite 的代理把/api开头的请求转发到后端// vite.config.js 代理配置片段 export default defineConfig({ server: { proxy: { /api: { target: http://127.0.0.1:5000, // 后端地址 changeOrigin: true, // 修改请求头中的 origin // rewrite: (path) path.replace(/^\/api/, ) // 后端无 /api 前缀时启用 } } } })changeOrigin: true是必须的它把请求头的 Host 改成目标地址否则后端可能因为 Host 不匹配拒绝请求。rewrite那行要不要开取决于后端路由有没有/api前缀——这个得去routes目录里翻一眼接口定义别凭感觉配。前端跑起来后浏览器打开http://localhost:5173能看到上传界面就说明前后端两条线都通了。3. 多模型推理链路YOLOv5 检测、SAM 分割、ResNet50 分类怎么串3.1 为什么用三个模型而不是一个这是整个项目最值得琢磨的选型决策。舌象分析要输出四个维度的分类结果如果用一个端到端模型直接出四维标签理论上最省事但实际效果往往不好。原因是舌象图片里舌头只占一部分背景嘴唇、牙齿、皮肤、拍摄环境会严重干扰分类。项目采用「先定位、再分割、后分类」的三段式YOLOv5 负责在整图里框出舌头区域Segment Anything 在框内做精细分割把舌头像素抠出来ResNet50 只对抠干净的舌头做四维分类。这个拆法的好处是每个模型只干一件事训练目标单一数据标注成本也可控。YOLOv5 只需要舌头的边界框标注SAM 用少量点或框提示就能分割ResNet50 拿分割后的干净图训练分类。坏处是推理链路长任何一环出错都会传导到最终结果——这也是后面避坑章节要重点讲的。3.2 推理链路的代码组织core目录是推理调度的核心。典型实现是一个推理类按顺序调用三个模型# core 目录下推理逻辑的典型结构 import cv2 import torch import numpy as np class TongueAnalyzer: def __init__(self, config): # 三个模型在初始化时加载避免每次请求重复加载 self.detector self._load_yolo(config[yolo_weight]) self.segmenter self._load_sam(config[sam_checkpoint]) self.classifier self._load_resnet(config[resnet_weight]) self.classes config[class_names] # 四维分类的标签 def analyze(self, image_path): img cv2.imread(image_path) # 第一步YOLOv5 检测舌头边界框 boxes self.detector(img) if len(boxes) 0: return {error: 未检测到舌象区域} # 第二步SAM 在框内分割得到舌头掩码 mask self.segmenter(img, boxes[0]) # 第三步用掩码抠出舌头送 ResNet50 分类 tongue self._crop_with_mask(img, mask) result self.classifier(tongue) return self._format_result(result)这段代码有三个关键点。第一模型在__init__里加载一次不是每次请求都加载——模型加载动辄几百 MB每次请求重载会让响应时间从秒级涨到十几秒。第二analyze里对检测结果做了空判断没检测到舌头直接返回错误不往下走避免后续步骤拿到空数据崩溃。第三_crop_with_mask用掩码抠图保证送进分类器的只有舌头像素背景被彻底剔除。3.3 四维分类的输出与数据库落库ResNet50 的输出是四个维度的分类结果每个维度对应一组标签。比如舌色可能是「淡红/红/绛/紫」舌苔色是「白/黄/灰/黑」薄厚是「薄/厚」腻否是「腻/不腻」。模型最后一层通常是四个并行的全连接头或者一个输出维度等于四类标签总数的大头再拆分。推理结果要落库models和orm就是干这个的。models定义数据库表结构orm提供对象关系映射的操作方法。典型的一张诊断记录表会存用户 ID、上传图片路径、四个维度的分类结果、生成时间。落库逻辑放在routes的接口函数里推理完拿到结果就写一条记录同时把健康报告返回给前端。这里要注意图片存储路径的配置——config里一般有个UPLOAD_FOLDER得确保这个目录存在且有写权限否则上传会静默失败。4. 避坑与排查模型加载、跨域、数据库三类高频翻车4.1 模型权重文件缺失或路径不对现象后端启动时报FileNotFoundError或RuntimeError: Error(s) in loading state_dict服务起不来。原因YOLOv5、SAM、ResNet50 的权重文件.pt、.pth通常体积很大压缩包里可能没带或者带了但config里配的路径和实际存放位置对不上。SAM 的 checkpoint 尤其大几百 MB 很常见。解决先去config里找到权重路径配置项确认路径指向的文件真实存在。如果压缩包里没带权重需要按项目说明里的链接自行下载放到配置指定的目录。state_dict加载报错多半是权重和网络结构定义不匹配检查net目录里的模型定义和权重是不是同一版本。4.2 前端请求 404 或跨域被拦现象前端页面能打开但上传图片后请求失败浏览器控制台报 CORS 错误或 404。原因两种可能。一是vite.config.js的 proxy target 端口和后端实际端口不一致比如后端跑在 5000 但代理写的是 8000。二是后端路由前缀和前端请求路径对不上前端请求/api/upload后端路由定义的是/upload中间少了 rewrite。解决先确认后端实际监听端口看run.py里的port参数再核对vite.config.js的target。然后去routes目录看接口的完整路径决定rewrite要不要开。改完vite.config.js必须重启前端开发服务器热更新不会重载代理配置。4.3 SQLite 数据库锁或表不存在现象上传诊断后报sqlite3.OperationalError: no such table或database is locked。原因AppDatabase.db文件缺失或表结构没初始化会报 no such table。database is locked 则是 SQLite 的并发限制——它同一时刻只允许一个写操作Flask 开发服务器多线程处理请求时容易撞上。解决表不存在就找项目里的建表逻辑通常在application/__init__.py或单独的初始化脚本里执行一次即可。锁的问题开发阶段可以把 Flask 的threaded参数关掉或者换用支持并发的数据库。生产环境不建议用 SQLite 扛并发写。4.4 推理结果四维标签错位现象分类结果能出来但舌色和舌苔色的结果明显对调了或者薄厚判断反了。原因ResNet50 输出层的维度顺序和config里class_names的顺序不一致。模型训练时标签的编码顺序和推理时解码用的标签列表必须严格对应。解决去net目录看分类模型输出层的维度定义再去config看class_names的排列两边对齐。如果模型是别人训练好的最好找训练时的标签映射文件别自己猜顺序。4.5 上传大图导致推理超时现象小图正常上传手机拍的高清大图时请求超时或内存溢出。原因YOLOv5 和 SAM 对输入尺寸敏感原图直接送进去显存或内存扛不住推理时间也会暴涨。解决在core的预处理环节加一步 resize把长边限制在模型能接受的范围内常见是 640 或 1024推理完再把结果坐标映射回原图尺寸。这一步很多项目会漏导致「测试用图好好的用户一传就崩」。5. 进阶技巧把推理服务从「能跑」调到「好用」5.1 模型预热与请求队列开发阶段单用户测试感觉不到一旦多人同时上传每次请求都触发模型前向计算GPU 或 CPU 会排队。一个实用技巧是在应用启动时做一次「预热推理」——用一张占位图跑一遍完整链路让模型完成首次加载和显存分配。这样第一个真实请求不会因为冷启动特别慢。# 在 create_app 或应用启动钩子里加预热 import numpy as np def warm_up(analyzer): # 构造一张纯色占位图跑一遍完整推理链路 dummy np.zeros((640, 640, 3), dtypenp.uint8) try: analyzer.analyze_image_array(dummy) except Exception: pass # 预热失败不影响启动真实请求会暴露问题预热的价值在于把「首次推理」这个最慢的环节提前到启动阶段。注意预热用的图尺寸要和真实请求接近太小起不到分配显存的作用。5.2 分割结果的边界处理SAM 分割出来的掩码边缘可能不平滑直接抠图会在舌头边缘留下锯齿或残留背景像素影响分类。常见做法是对掩码做一次形态学处理import cv2 def refine_mask(mask): # 开运算去噪点闭运算填小孔 kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) mask cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) # 高斯模糊让边缘过渡自然 mask cv2.GaussianBlur(mask, (5, 5), 0) return mask开运算先腐蚀后膨胀去掉掩码外的小噪点闭运算先膨胀后腐蚀填补掩码内的小孔洞。核大小(5,5)是个经验值图大可以调大图小调小。这一步做完抠出来的舌头边缘会干净很多分类准确率通常有可见提升。5.3 验证推理链路是否真的通了别只看前端页面有没有出结果要分层验证。第一层单独跑 YOLOv5 看能不能框出舌头把框画在图上存下来肉眼确认。第二层单独跑 SAM 看掩码覆盖是否完整。第三层拿一张已知标签的图跑完整链路看四维结果是否和预期一致。三层都过了再走前端上传流程。验证层检查内容失败表现检测层边界框是否框住舌头框到背景或框为空分割层掩码是否覆盖舌头全部像素掩码残缺或溢出到背景分类层四维标签是否合理标签错位或置信度极低服务层前端上传到结果返回全链路超时、404、数据库报错这套分层验证是我踩过坑之后养成的习惯。之前有一次前端一直返回错误查了半天以为是跨域最后发现是 YOLOv5 权重路径配错导致检测层直接抛异常错误被后端吞掉只返回了个笼统的失败提示。从那以后我每次调这类多模型链路都强制从检测层开始逐层单独验证不跳过任何一层。希望帮到你。本文还有配套的精品资源点击获取