新闻详情

新闻详情

首页 / 资讯中心 / 详情

基于Spring Boot的智能垃圾分类系统:从MySQL建表到ONNX模型集成

发布时间:2026/10/1 23:00:57来源:尧图网络
基于Spring Boot的智能垃圾分类系统:从MySQL建表到ONNX模型集成
简介基于Java的智能垃圾分类系统毕业设计资料包面向计算机专业毕业设计、课程设计及Java项目实战学习者适合已有一定Java基础、需要完整项目参考与二次拓展的学生。包内共548个文件涵盖74个Java核心源码、XML配置、Vue与JavaScript前端页面、JSON数据、CSS/SCSS样式其中134个XML用于界面与框架配置28个Vue与40个JS构建管理端交互16个WXML与17个WXSS对应小程序端页面另含数据库SQL脚本、说明文档与演示视频整体约59MB目录结构清晰便于按模块调用。已有93人学习下载可作为毕业设计选题演示与答辩的有力支撑工程结构标准、代码注释完整适合课程设计与期末展示。除源码外附带的说明文档可梳理智能分类流程与接口设计SQL脚本可快速初始化数据库演示视频帮助了解系统运行效果方便快速跑通项目并继续扩展分类、统计等功能。1. 为什么这套智能垃圾分类系统总能难住一半开发者很多人拿到一套“基于 Java 的智能垃圾分类系统”毕业设计交付包后第一反应是打开源码找 main 方法然后被十几个包绕晕。先给结论这类项目能在答辩现场站得住靠的不是代码量而是两条主线——数据库表设计能不能说清业务闭环图片识别从训练到 Java 推理这条路能不能完整讲出原理。这篇笔记按常见落地路径拆开讲系统模块怎么切MySQL 表怎么建MobileNetV2 模型怎么导出成 ONNX 再塞进 Spring Boot以及演示最容易翻车的几个点。适合三类人准备交毕设的在校生、想要一份 java 课程设计案例源码作为参考的初学者、以及想快速搭内部演示验证的员工。2. 系统先拆分再看代码模块边界、技术选型与识别数据流2.1 识别一次垃圾数据到底怎么走拿到源码后不建议先读 Controller先建立一条完整的数据流认知。这套系统的核心链路是用户上传一张图片后端存文件推理引擎返回类别和置信度记录落库前端展示结果。围绕这条链路模块一般切成用户模块、垃圾类别模块、识别模块、识别记录模块、后台管理模块。用户模块负责注册登录毕设里用 Session 或简单 Token 都行重点是别在答辩时被问“密码为什么明文存”。垃圾类别模块维护的是分类树常见做法是大类加二级子类比如“可回收物”大类下挂“塑料瓶、纸箱、金属罐”。识别模块是核心它把图片传给模型推理引擎拿回一个类别 ID 和置信度。识别记录模块把每次上传的图片路径、预测结果、置信度写进数据库这是演示时最有说服力的功能因为老师一眼能看到系统“记住了”之前识别过什么。后台管理模块通常只开放给管理员做类别的增删改查。对应到工程上的包结构一般是 controller、service、mapper、entity、config、common 这几个目录。读源码的顺序应该反过来先找 entity 看表结构再找 mapper 看增删改查怎么写的最后回到 controller 确认接口路径。很多新手卡在第一天上是因为一上来就钻进了某个工具类的实现细节里。2.2 技术栈选型Spring Boot 单体为什么是默认答案这类题目的技术栈几乎固定是 Spring Boot MySQL MyBatis-Plus再加一个前端模板或简单 Vue 页面。不是没有别的选择而是在“能演示、能答辩、能短时间跑通”三个约束下单体应用是性价比最高的。Swing 或 JSP 的老式桌面端容易做界面也好控制但图片识别接入很别扭而且视觉上很难让老师眼前一亮。反过来上 Spring Cloud 微服务则没必要一次识别请求要跨两个服务答辩会引来一连串关于注册中心、负载均衡、分布式事务的追问而毕设的核心价值不在这里。Spring Boot 单体的优势在于依赖少、启动快、接口调试方便而且自动装配、过滤器链、事务传播这些知识点恰好是 java 面试常规盘问区做完这个项目相当于把后端主线的常见问题都过了一遍。选 MyBatis-Plus 而不是纯 MyBatis理由也简单单表增删改查几乎不用写 XML分页查记录一行搞定。识别记录的增删改查正好交给 IService 的通用方法省下的时间全投到模型推理上这才是本项目的真正难点。2.3 识别引擎两种集成路线内置推理还是另起 Python 服务这是整个项目最重要的分叉点也是答辩必问题。最常见的做法有两个一是用 ONNX Runtime 或 DJL 在 Java 进程里直接推理二是 Java 后端调用一个独立的 Python Flask/FastAPI 识别服务。方案二开发时很爽Python 生态调模型、改预处理都方便但演示时要同时开两个进程任何一个挂了都会翻车。老师还会追问“两个服务之间怎么通信、超时怎么办、丢结果怎么处理”这些问题对毕设来说都是额外的坑。方案一是一键启动 Java 进程就全包含了模型加载一次常驻内存推理走本地方法调用没有网络开销。代价是前处理要自己写OpenCV 的 resize、通道转换、归一化都得手动对齐训练脚本稍有偏差精度就垮。我一般建议选方案一把难度集中在可控的前处理对齐上而不是摊到两个进程的联调里。最后录演示视频的时候只开一个终端窗口就能录完整个链路动线短翻车点少。2.4 拿到一份陌生源码后的三步读法既然标题里带“源码”这里给一个通用的快速上手路径适合任何 Spring Boot 毕设包。第一步找到启动类看 SpringBootApplication 和 MapperScan 注解确认数据库配置在 application.yml 还是 application.properties。第二步找到 doc 或 sql 目录下的建表脚本把表名、主外键关系画出来对照 entity 类核对字段。第三步找 controller 里路径最长的那个文件通常就是识别主流程从它的方法签名出发一路追踪 service 和 mapper。三步走完这份源码在你脑子里就不再是黑匣子。后面的所有改动都能基于同一张认知地图进行而不是靠搜索“垃圾分类 java”再抄一段代码拼进去。3. 从数据库到能启动MySQL 三张核心表、Druid 连接池与最小配置3.1 建表 SQL用户表、类别表、识别记录表一次到位数据库设计决定系统能讲多圆。很多翻车现场是“识别完记录没地方存”或者“类别写死在代码里没法改”。下面这份 SQL 是我一般会推荐的落法三张表把业务闭环撑起来sys_user 管人waste_category 管分类字典classify_record 管每次识别行为。CREATE DATABASE IF NOT EXISTS trash_classifier DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE trash_classifier; CREATE TABLE sys_user ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 主键, username VARCHAR(64) NOT NULL COMMENT 登录名唯一, password VARCHAR(255) NOT NULL COMMENT 加盐后的密文禁止明文, nickname VARCHAR(64) COMMENT 昵称, role TINYINT NOT NULL DEFAULT 1 COMMENT 1普通用户 2管理员, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间, UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT系统用户表; CREATE TABLE waste_category ( id BIGINT AUTO_INCREMENT PRIMARY KEY, parent_id BIGINT NOT NULL DEFAULT 0 COMMENT 0表示大类否则指向父类ID, name VARCHAR(64) NOT NULL COMMENT 类别名如可回收物, keyword VARCHAR(255) COMMENT 搜索关键词逗号分隔, description VARCHAR(500) COMMENT 投放说明展示在页面上, icon_url VARCHAR(255) COMMENT 图标路径为空用默认图, sort INT NOT NULL DEFAULT 0 COMMENT 排序权重, KEY idx_parent (parent_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT垃圾类别表; CREATE TABLE classify_record ( id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT DEFAULT NULL COMMENT 可空未登录也能识别, image_path VARCHAR(255) NOT NULL COMMENT 上传图片保存路径, category_id BIGINT NOT NULL COMMENT 模型预测出的类别ID, confidence FLOAT NOT NULL DEFAULT 0 COMMENT 置信度范围0~1, model_version VARCHAR(32) DEFAULT mbv2_onnx_v1 COMMENT 模型版本, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 识别时间, KEY idx_user_time (user_id, create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT识别记录表; INSERT INTO sys_user (username, password, nickname, role) VALUES (admin, 这里填MD5加盐后的密文, 管理员, 2);参数说明放在这里讲清楚。数据库和表全部指定 utf8mb4是为了让“可回收物”“厨余垃圾”这类中文类别名在任何平台都不变形。sys_user 的 password 字段特意留成 VARCHAR(255)因为 MD5 加盐或 BCrypt 的密文长度都超过传统 32 位别因为字段短导致初始化数据插不进去。waste_category 用 parent_id 而不是单独建一张树表是因为垃圾分类最多两级大类套小类一张表加一个索引完全够用答辩时也更好解释。confidence 用 FLOAT 而不是 DECIMAL因为展示需要的精度只有两位小数统计时用 AVG 也方便没必要为“精确十进制”增加心智负担。classify_record 里的 image_path 建议存相对路径比如 uploads/20240512_xxx.jpg不要把盘符写死进数据库。这样整个项目拷到答辩机器上路径依然有效这是血泪经验换来的。user_id 允许为空这条设计很关键它保证了“游客也能识别”的演示场景现场不用先登录再拍照。管理员初始账号写在 INSERT 语句里说明文档里记得附上密码生成方式否则老师问“你怎么登录后台”时只能现场改库。3.2 Spring Boot 工程骨架pom、数据源参数与启动类工程侧最省事的组合是 Spring Boot 2.7.x MyBatis-Plus Druid mysql-connector-j其中 MyBatis-Plus 和 Druid 的版本按你本地的 Spring Boot 版本来配不要只挑最新的。pom 里的核心依赖如下重点是别漏了 ONNX Runtime它是后面推理章的地基。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.x/version /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.x/version /dependency dependency groupIdcom.alibaba/groupId artifactIddruid-spring-boot-starter/artifactId version1.2.x/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdcom.microsoft.onnxruntime/groupId artifactIdonnxruntime/artifactId version1.16.x/version /dependency /dependencies这些依赖选型的理由顺着讲一遍MyBatis-Plus 负责把单表 CRUD 从几十行 XML 压缩成几行继承classification 和 record 两张表的操作全是套路代码Druid 是国产连接池里文档最多、面试官最熟悉的配置项在网上能搜到大量现成案例onnxruntime 是微软的跨平台推理引擎Java API 稳定能把 PyTorch 导出的模型直接跑起来绕开 TensorFlow Java 那套繁琐的 API。接着给出 application.yml 的最小可运行配置。这组参数可以直接抄但要理解每一行的用途答辩老师大概率会指着连接串问时区参数。server: port: 8080 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/trash_classifier?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: root druid: initial-size: 5 min-idle: 5 max-active: 20 max-wait: 6000 validation-query: SELECT 1 test-while-idle: true servlet: multipart: max-file-size: 10MB max-request-size: 50MB mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl关键参数逐个说。连接串里 useUnicodetrue 和 characterEncodingutf8 管中文传输serverTimezoneAsia/Shanghai 解决 MySQL 8 驱动时区报错allowPublicKeyRetrievaltrue 解决第一次连接时 MySQL 8 的 caching_sha2_password 加密插件导致连接失败。这几个参数缺一个启动时就会报时区异常或 Public Key Retrieval is not allowed。Druid 的 initial-size 是启动时预建立的连接数min-idle 是池中最小空闲连接max-active 是最大活跃连接。毕设场景最大 20 就够了调太大反而占内存。validation-query 配 SELECT 1配合 test-while-idletrue能在空闲连接被 MySQL 断开后自动剔除避免早上第一次请求报“连接失效”。max-file-size 限制单张图片 10MB实际手机拍的照片通常 3~5MB够用。最后的启动类写成这样注意 MapperScan 的路径必须和你的 mapper 包一致。package com.example.trash; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; SpringBootApplication MapperScan(com.example.trash.mapper) public class TrashApplication { public static void main(String[] args) { SpringApplication.run(TrashApplication.class, args); } }这里 MapperScan 的作用是让 MyBatis-Plus 扫描到所有 Mapper 接口不用每个接口都加 Mapper。如果你在启动后看到 NoSuchBeanDefinitionException 报 mapper 找不到第一反应应该是检查这个注解的包路径是否写对了。3.3 首次跑通的验证顺序别急着点上传数据库建好、工程能编译后先别急着做功能验证按三步走。第一步用命令行把 SQL 脚本导进去MySQL 8 的导入命令是mysql -uroot -proot --default-character-setutf8mb4 trash_classifier.sql带上字符集参数能避免 Windows 下脚本编码问题。第二步启动 Spring Boot观察日志里 Druid 是否打印了初始化连接池成功再打开浏览器访问http://localhost:8080/api/category/list看是否能返回类别 JSON。第三步登录管理后台手动新增一个类别然后刷新页面确认中文没乱码。三步全过说明数据库连接、字符集、MyBatis 映射三条线都通了。此时再把模型推理代码接进来后面排查问题时范围会小很多。4. 把识别能力接进 Spring BootMobileNetV2 训练、ONNX 导出与 Java 推理4.1 类别体系与数据集先定类别再选模型顺序不能反识别部分的第一个决策不是选模型而是定类别。垃圾分类公开数据集常见的有两类组织方式一类按 4 大类可回收、有害、厨余、其他整理一类按具体物品分 40 多小类。毕设不建议一上来做 40 类样本不均衡和相似物混淆会把准确率拖到没法看的程度。更稳的落法是合并成 6~8 类比如厨余垃圾、可回收塑料、玻璃、纸张、金属、有害垃圾、其他垃圾。这个粒度既能体现“智能”又不至于让模型在答辩现场翻车。类别 ID 要和前面 waste_category 表里预置的数据一一对应这样最终预测输出的是一个数字查表就能拿到完整分类树信息。模型选 MobileNetV2 而不是 ResNet50 或更深的网络理由很实际MobileNetV2 在 CPU 上跑一张 224x224 的图只要几十毫秒模型文件约 14MB而 ResNet50 体积大且推理慢。答辩用的机器大概率没有独显模型再强跑不动等于零。迁移学习用 ImageNet 预训练权重把最后一层分类头换成自己的类别数只微调后面的几层训练时间能压缩到一两个小时效果已经足够演示。4.2 训练与导出PyTorch 微调后固定 batch 导出 ONNX训练脚本按常见做法写数据按 8:1:1 切成训练、验证、测试三份。预处理统一用 Resize 直接拉伸到 224x224不加随机裁剪原因在后面讲 Java 对齐时会更清楚。import torch from torchvision import models, transforms from torch import nn num_classes 7 # 训练和推理的预处理必须保持同一套规则 train_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), transforms.ToTensor(), # 只归一化到 [0, 1]不额外做 mean/std ]) val_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), ]) # 迁移学习加载 ImageNet 预训练权重替换分类头 model models.mobilenet_v2(weightsmodels.MobileNet_V2_Weights.IMAGENET1K_V1) model.classifier[1] nn.Linear(model.last_channel, num_classes) opt torch.optim.Adam(model.parameters(), lr1e-4) loss_fn nn.CrossEntropyLoss() # 简化的训练主循环实际按 train_loader 迭代 for epoch in range(30): for images, labels in train_loader: opt.zero_grad() loss loss_fn(model(images), labels) loss.backward() opt.step() torch.save(model.state_dict(), best.pth) # 导出 ONNX固定 batch1不搞动态维度 model.load_state_dict(torch.load(best.pth, map_locationcpu)) model.eval() dummy torch.zeros(1, 3, 224, 224) torch.onnx.export( model, dummy, waste_mbv2.onnx, input_names[input], output_names[logits], opset_version11, dynamic_axesNone, do_constant_foldingTrue )这里的三个关键参数值得在答辩时展开。lr 用 1e-4 而不是默认的 1e-3因为迁移学习时预训练权重已经很好了学习率太大会把学到的特征冲掉。opset_version 固定 11是为了兼容 ONNX Runtime 的 Java 版本版本太新反而可能在旧环境中加载失败。dynamic_axesNone 意味着模型只接受 batch1演示系统一次只处理一张图省去动态维度的复杂映射换来的是推理代码更简洁。预处理特意不加 Normalize 的 mean/std只用 ToTensor 把像素缩到 [0,1]。这是为了减少 Java 端对齐的工作量训练和推理都统一成“除以 255”后端代码里的人为误差就少了一个来源。4.3 Java 推理ONNX Runtime 加载模型与前处理对齐Java 端推理一般封装成一个单例组件项目启动时加载一次模型之后所有请求复用同一个 OrtSession。下面是核心代码OpenCV 负责读图和缩放ONNX Runtime 负责推理。package com.example.trash.service; import ai.onnxruntime.*; import org.opencv.core.*; import org.opencv.imgproc.Imgproc; import java.util.Collections; public class WasteClassifier { private final OrtEnvironment env; private final OrtSession session; public WasteClassifier(String modelPath) throws OrtException { env OrtEnvironment.getEnvironment(); OrtSession.SessionOptions options new OrtSession.SessionOptions(); options.setIntraOpNumThreads(2); // 限制线程数避免争抢 CPU session env.createSession(modelPath, options); } public int predict(String imagePath) throws OrtException { float[] input preprocess(imagePath); // 形状 NCHW归一化到 [0,1] OnnxTensor tensor OnnxTensor.createTensor( env, FloatBuffer.wrap(input), new long[]{1, 3, 224, 224}); try (OrtSession.Result result session.run( Collections.singletonMap(input, tensor))) { float[][] logits (float[][]) result.get(0).getValue(); return argmax(softmax(logits[0])); } } private float[] preprocess(String imagePath) { Mat src Imgcodecs.imread(imagePath); Mat rgb new Mat(); Imgproc.cvtColor(src, rgb, Imgproc.COLOR_BGR2RGB); Mat resized new Mat(); Imgproc.resize(rgb, resized, new Size(224, 224)); resized.convertTo(resized, CvType.CV_32FC3, 1.0 / 255.0); float[] data new float[3 * 224 * 224]; for (int c 0; c 3; c) { for (int i 0; i 224; i) { for (int j 0; j 224; j) { double[] pixel resized.get(i, j); data[c * 224 * 224 i * 224 j] (float) pixel[c]; } } } return data; } }这段代码里最容易出错的是通道顺序。OpenCV 读图默认是 BGR而 PyTorch 训练时用的是 RGB所以 preprocess 里第一步必须 cvtColor 转成 RGB。然后是缩放逻辑训练时 Resize 直接拉伸到 224x224Java 端也要用 Imgproc.resize 把整张图拉伸不能做 center crop否则物体位置和训练时不一致识别结果会莫名其妙地偏到别的类别。归一化的 1.0/255.0 对应训练脚本的 ToTensor。如果你在训练时额外加了 mean 和 stdJava 端必须写成(pixel[c] / 255.0 - mean[c]) / std[c]两边不一致时精度崩塌是必然的这不是玄学是数据分布变了。模型输入名写成 “input”输出名 “logits”这两个字符串必须和 onnx.export 时的参数一致否则 session.run 会因为找不到输入节点直接抛异常。关于线程数setIntraOpNumThreads(2) 是给每个推理会话限制内部并行线程。演示机器通常只有四核八线程默认值会吃掉全部 CPU后端其他接口会一起变慢。这个参数是压测时最容易发现的优化点。4.4 REST 接口封装上传、识别、落库一条龙模型组件准备好后剩下的就是把它挂到 Controller 上。接口设计成 POST /api/classify用 MultipartFile 接收图片识别完同步写入 classify_record。package com.example.trash.controller; import com.example.trash.service.WasteClassifier; import com.example.trash.service.ClassifyRecordService; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.File; RestController RequestMapping(/api/classify) public class ClassifyController { private final WasteClassifier classifier; private final ClassifyRecordService recordService; private final String uploadDir uploads/; // 相对路径演示时不会死锁 PostMapping public Result classify(RequestParam(file) MultipartFile file, RequestParam(value userId, required false) Long userId) throws Exception { String fileName System.currentTimeMillis() _ file.getOriginalFilename(); File target new File(uploadDir fileName); if (!target.getParentFile().exists()) { target.getParentFile().mkdirs(); } file.transferTo(target); int categoryId classifier.predict(target.getAbsolutePath()); float confidence classifier.confidence(target.getAbsolutePath()); recordService.save(userId, uploadDir fileName, categoryId, confidence); return Result.ok(categoryId, confidence); } }参数说明里三个细节值得记住。文件名用时间戳加原始名防止两个人同时上传同名的 demo.jpg 互相覆盖这也是演示时常见的隐蔽 bug。uploadDir 用相对路径而不是 C 盘绝对路径项目拷到哪都能跑这点在答辩前换机器时特别重要。userId 允许为空意味着游客也能识别主键由数据库自动生成不会因为空 userId 报错。confidence 方法在 WasteClassifier 里和 predict 共用一次推理结果不要把同一张图推理两遍演示机器性能有限重复推理会让人明显感到卡顿。返回的 Result 对象统一包一层 code、message、data前端判断 code 为 0 就展示结果否则弹提示。这套约定能避免前后端在“到底什么状态是成功”上产生分歧。5. 高频踩坑记录从连不上数据库到现场识别翻车5.1 连不上数据库Public Key Retrieval is not allowed 与时区报错现象第一次启动后端日志里出现 Communications link failure或者明确报 Public Key Retrieval is not allowed还有一种是 The server time zone value 乱码 is unrecognized。原因MySQL 8 默认用 caching_sha2_password 认证插件客户端第一次连接时需要通过 RSA 公钥交换密钥而 JDBC 驱动出于安全默认不自动获取公钥。时区报错则是驱动 8.x 要求连接串里显式声明 serverTimezone。解决在 JDBC 连接串末尾追加三个参数useSSLfalse 关闭 SSL 握手allowPublicKeyRetrievaltrue 允许客户端向服务端请求公钥serverTimezoneAsia/Shanghai 指定东八区。同时确认 mysql-connector-j 用的是 8.x 驱动如果 pom 里还挂着 5.1.49 这种老版本连 MySQL 8 即使加了参数也连不上。5.2 中文乱码类别名在数据库里变成问号现象导入 SQL 后查 waste_categoryname 字段显示??? 或一堆乱码前端页面展示“鍙敹鍥炵墿”之类的内容。原因建库时没指定 utf8mb4或者 SQL 脚本文件本身是 GBK 编码导入时又被命令行按系统默认编码读了一遍双重污染。解决三处必须统一。第一处是 CREATE DATABASE 语句显式加上 DEFAULT CHARACTER SET utf8mb4第二处是 JDBC 连接串带 characterEncodingutf8第三处是在命令行导入时加 --default-character-setutf8mb4 参数用 Navicat 导入时也要在高级选项里选 UTF-8。改完这三处后重新建库再导入中文基本不会再出问题。5.3 现场识别率崩塌训练 95%演示一拍照就错现象训练集的准确率在 95% 以上验证集也不错但现场拿手机拍一张实物识别结果完全不对甚至输出了悬殊的置信度。原因九成是预处理不一致。训练脚本里用了 Resize 加 RandomCropJava 端只做了整图拉伸模型见到的图片分布和训练时不一样。另一成是演示样本和训练集分布差异太大比如训练集里全是干净背景的塑料瓶现场拍了张桌面杂物里的瓶子模型当然懵。解决训练和推理共用一个预处理规则Java 端严格复现训练验证集的处理流程。我一般建议训练时直接 Resize 到 224x224不做 RandomCrop这样 Java 端最不容易写错。演示前固定挑 5 到 10 张背景干净、光线均匀的图反复测别现场即兴发挥。同时给置信度加一个阈值低于 0.6 时返回“未能识别”让用户手动选择类别这比硬给一个错误答案体面得多。5.4 ONNX Runtime 首次请求卡死模型加载和线程配置问题现象后端启动正常上传第一张图片时请求卡了十几秒甚至直接接口超时。连续识别几次后内存明显涨。原因最常见的错误是每次请求都 new 了一个 OrtSession模型重新读盘、重新加载内存里堆积多个会话。另一个问题是没限制线程数ONNX Runtime 默认按 CPU 核数开线程和 Spring Boot 业务线程抢资源。解决把 OrtSession 做成 Spring 单例 Bean启动时加载一次之后所有请求复用。SessionOptions 里 setIntraOpNumThreads(2)并把 setExecutionMode 设为 ORT_SEQUENTIAL。最后在 ApplicationRunner 里做一次预热推理随便传一张测试图进去让模型真正的初始化在演示前完成用户看到的第一次请求就不至于卡顿。5.5 演示视频与现场环境不一致录好了却复现不出来现象演示视频里登录、识别、历史记录一切正常答辩现场一操作登录白屏图片传不上去或者端口被占用整个动线崩溃。原因视频是在开发机上录的路径写死、端口冲突、数据库没启动这些环境差异在录屏时不存在到现场全部爆发。解决上传目录、模型路径全部改成相对路径或配置项不能写死。答辩前给出一键启动脚本把 MySQL 服务、后端 jar 的启动顺序固定下来。演示视频要录完整链路登录、拍一张图、识别结果展示、历史记录查询、后台类别维护控制在 3 分钟以内。视频里界面操作要慢半拍给老师看清楚点什么现场演示则反过来只操作自己反复跑过的那几条动线一旦出错直接切视频不要在现场修 bug。6. 演示前 20 分钟一键启动脚本、接口自测与演示动线纪律最后一章给一个能直接提升演示完成度的方案把现场环境固定成一条命令再用 curl 把核心接口全部验一遍最后严格按预设动线操作。一键启动脚本按下面这个思路写作用是让 MySQL 和后端按顺序拉起避免“登录白屏”这种环境级翻车。#!/bin/bash echo step1 启动 MySQL 服务 service mysql start sleep 5 echo step2 检查 3306 端口 ss -lntp | grep 3306 echo step3 启动后端 jar日志输出到 app.log java -jar target/trash-classifier-1.0.0.jar \ --spring.config.locationconfig/application.yml app.log 21 echo step4 实时查看日志 tail -f app.log脚本里的 sleep 5 是为了等 MySQL 完成初始化端口检查则是确认数据库真的起来了。启动 jar 时用 --spring.config.location 指向外部配置这样换机器时只改 config 下的 yml不用重新打包。日志重定向到 app.log界面保持干净方便事前自测而不是当场看刷屏。接下来用 curl 把三条核心链路全部冒烟测试一遍这一步能在答辩前发现 80% 的运行时问题。# 1. 登录拿会话 curl -X POST http://localhost:8080/api/auth/login \ -d usernameadminpasswordadmin123 # 2. 上传一张准备好的测试图看返回的 categoryId 和 confidence curl -X POST http://localhost:8080/api/classify \ -F filedemo.png -F userId1 # 3. 查询识别历史确认记录落库 curl http://localhost:8080/api/record/list?userId1自测的验收标准很简单登录接口返回成功标识识别接口返回的 categoryId 是我预算好的类别confidence 不低于 0.8历史记录接口能查刚才那条数据。三条都通过再开始正式演示。如果 confidence 偏低提前换测试图别在讲台上和模型较劲。演示动线我习惯就三条登录后直接识别一张图看结果页的分类说明进入历史记录页证明数据落库最后去后台改一个类别说明文字证明系统可维护。每一条都必须在视频里完整出现过。这是血泪经验现场演示翻车大部分发生在“开发时没走过完整链路”的接口上。我的习惯是每次答辩前只练习这三条动线和一句话串场这张图是系统没见过的但模型依据材质和形状给出了判断。多一点都是风险少一点都是遗憾。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Python手写BP神经网络:从零实现MNIST手写数字识别与迁移 2026/10/2 1:23:34

Python手写BP神经网络:从零实现MNIST手写数字识别与迁移

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

阅读更多 →
Project与Office 365冲突排查指南:从文件锁到许可证的全面解析 2026/10/2 1:23:34

Project与Office 365冲突排查指南:从文件锁到许可证的全面解析

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

阅读更多 →
Cadence Broadband SPICE模型生成全指南:从S参数到可信SPICE网表 2026/10/2 1:23:33

Cadence Broadband SPICE模型生成全指南:从S参数到可信SPICE网表

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

阅读更多 →
LSF多队列与bsub实战:抢占+公平调度解决HPC资源争抢 2026/10/2 1:23:33

LSF多队列与bsub实战:抢占+公平调度解决HPC资源争抢

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

阅读更多 →
Java运算符全解:六大类型、易错点与面试通关指南 2026/10/2 1:23:33

Java运算符全解:六大类型、易错点与面试通关指南

1. 一起把 Java 运算符这块硬骨头啃下来聊到 Java 语法,很多自学的小伙伴进度很容易卡在运算符这一章。说实话,运算符在教科书里的篇幅不算长,但信息密度极高,而且它不像循环、数组那样能直观看出“学完能干嘛”。很多人要么囫囵吞…

阅读更多 →
Flask+微信小程序搭建同城二手交易系统实战 2026/10/2 1:23:26

Flask+微信小程序搭建同城二手交易系统实战

说实话,接到这个同城二手交易系统的需求时,我的第一反应是:这不就是个“看起来不难、做起来琐碎”的典型项目吗。等真正动手,用 Python Flask 写后端接口、微信小程序做用户端,前后花了一个多月,才发现真正…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉