搞视觉质检的朋友对“目标检测”和“工业落地”这两个词应该不陌生。布料缺陷检测这类项目传统做法是让工人肉眼盯着产线看漏检率、效率都是问题。我之前做了一个基于YOLO系列模型和SpringBoot的布料缺陷检测系统还把通义千问和DeepSeek大模型接了进来搞了一个有Web交互界面的前后端分离应用。这里把整个项目的设计思路、模型训练、后端集成、大模型分析、前端展示和踩坑记录完整梳理一遍。如果你是做视觉落地或者后端集成的这篇文章可以直接当参考模板。整个项目的目标很明确用户上传一张布料图片系统先由YOLO模型自动识别出破洞、污渍、褶皱、线头这类缺陷再把检测结果缺陷类型、数量、位置、置信度交给大模型生成一份通俗易懂的缺陷分析报告最后在网页上把标注框和报告一起展示出来。相当于从“画框”到“解释”全自动。1. 项目整体设计与技术选型1.1 缺陷检测系统的整体流程布料缺陷检测不是单纯跑一个模型就结束了真正要落地必须把它组合成一个闭环。我先用流程图的方式理清逻辑图像采集 → 前端上传图片 → SpringBoot接收 → YOLO推理服务检测缺陷 → 输出JSON结果 → 调用大模型生成分析报告 → 前端展示标注框和报告这里面的关键点是检测结果不能只给坐标和类别。工业场景下负责人更想知道“这批布是什么问题、可能怎么产生的、后续怎么避免”。所以我在检测链路后面接了一个大模型分析层让系统不仅会“看”还会“说”。图像采集端支持单张图片上传也预留了摄像头实时抓图的接口。如果产线有固定相机可以让SpringBoot定时拉取或接收图片流直接进入检测流程。我在项目里做了两种模式单张检测和批量检测。批量检测适合抽检场景一次上传多张图片后端循环处理结果汇总成一张统计表。1.2 为什么选YOLO系列和SpringBoot目标检测模型可选的很多但工业落地最看重的是速度和易部署性。YOLO系列从v8开始就支持端到端的anchor-free检测训练工具链基于ultralytics非常成熟。v10、v11、v12分别在不同方向上做了优化后面我会单独对比。SpringBoot作为后端框架生态完善开发效率高团队成员都有Java背景维护压力也小。其实也可以全部用Python写但考虑到Web交互、权限管理、数据库集成这些基础功能Java后端更稳。视觉推理部分我封装成独立服务不直接跟SpringBoot混在一起这样模型升级不会影响主业务。1.3 功能模块划分整个系统拆成五个模块模块功能数据管理模块上传图片、存储检测结果、管理历史记录YOLO推理服务基于FastAPI或ONNX Runtime执行缺陷检测返回坐标、类别、置信度大模型分析服务对接通义千问、DeepSeek根据检测结果生成缺陷分析文本Web交互端Vue3 Element Plus支持图片上传、标注展示、报告展示后端接口层SpringBoot提供REST API串联所有模块每个模块之间通过HTTP或JSON通信职责分离不会出现改一个功能影响全局的情况。2. 数据准备与YOLO模型训练实战2.1 布料缺陷数据集的构建与标注数据是模型的粮食。布料缺陷数据不像通用物体检测数据那么好找很多真实产线的数据涉及商业机密所以大部分项目要自己攒数据。我做的这个模拟项目用了一类公开的织物缺陷数据集同时自己拍了一些不同光照、不同纹理的布料图片做补充。缺陷类别我最终定为五类破洞、污渍、褶皱、线头、跳纱。标注工具用了X-AnyLabeling它支持自动标注辅助可以先用一个预训练模型跑一遍再人工修正效率比纯手工快不少。标注格式统一转成YOLO格式也就是每张图片对应一个txt文件每行是“类别序号 x_center y_center width height”坐标都是归一化到0~1之间的。数据清洗时有几个重点删除标签和框明显不匹配的样本同一类缺陷如果外观差异大尽量多收集不同风格小目标缺陷比如线头、极小破洞要单独多留一些不然模型容易漏检训练集、验证集、测试集按8:1:1划分且保证同一批布料的图片不会都落在一个集里防止数据泄漏。我做了一个统计脚本检查每个类别的样本数量。如果某类太少就对该类图片做裁切、复制粘贴增强。实际做下来线头类别最开始只有两百多张远不够后来通过裁剪放大和旋转扩充到了八百张漏检率才明显下降。2.2 模型选型与训练参数调整训练前先定用哪个YOLO版本。我这里跑过YOLOv8s、YOLOv10s、YOLOv11s和YOLOv12n在同一批数据上做了对比模型版本参数量推理耗时GPU约mAP50特点YOLOv8s中等约15ms0.86稳定兼容性好YOLOv10s中等约13ms0.87无需NMS端到端更快YOLOv11s中等偏大约14ms0.88骨干网络更高效YOLOv12n较小约11ms0.85注意力机制增强轻量试验下来我最终选了YOLOv11s作为主力模型。原因是它的mAP最高推理速度和v10差不多部署时转ONNX也没有遇到特殊问题。v12n虽然轻但在布料纹理复杂的图像上容易漏检小型褶皱所以没有选用。如果你的服务器资源紧张v8s仍然是万金油。训练参数我用了这样一套基准配置yolo train datacloth.yaml modelyolov11s.pt epochs150 imgsz640 batch16 optimizerAdamW lr00.001 augmentTrue patience30data/cloth.yaml数据配置指定训练验证路径、类别数和类别名称imgsz设成640兼顾精度和速度。如果布料纹理极细可以试896但显存占用会增大batch16单卡8G显存勉强够显存不够就调低到8或4用梯度累积补偿patience30早停策略防止过拟合。训练过程中重点观察两个指标验证集mAP和PR曲线。每训练完一个epochultralytics会自动保存best.pt和last.pt我最终只保留best.pt。这里有个很实用的技巧如果某些类别明显偏少可以在YOLO配置里设置class weights或者对rare class做mosaic增强时提高采样权重。当时污渍类样本很多而破洞类样本很少模型总是把破洞误检成污渍。后来我对破洞类图片做了三倍过采样再配合复制粘贴增强误检率才降下来。2.3 模型导出与部署准备训练得到的best.pt是基于PyTorch的部署时我选择导出成ONNX用ONNX Runtime推理。导出很简单yolo export modelruns/detect/train/best.pt formatonnx opset12 simplifyTrue imgsz640simplifyTrue会做图优化减少一些冗余算子。导出后建议用onnxruntime-test验证一下输出维度。YOLO的输出形状通常是(1, 5类别数, 8400)这里8400是各个特征层的预测框总和。如果类别是5那么输出就是(1, 10, 8400)。前4行是box坐标第5行是置信度后面是类别得分。导出后我直接用ONNX Runtime做了一次benchmark比PyTorch动辄几百MB的模型包小一些推理速度也快了一点点。如果要做极致优化就用TensorRT但TensorRT对显卡型号有要求部署环境不统一的话不建议一开始就上。3. SpringBoot后端与YOLO推理集成3.1 后端服务架构设计SpringBoot项目采用标准的controller-service-repository三层结构。为了分离视觉推理逻辑我把推理部分单独抽出来做了一个服务。整体接口层设计如下POST /api/detect接收上传图片返回缺陷列表和分析报告POST /api/batch-detect接收多张图片循环调用检测GET /api/history获取历史检测记录GET /api/report/{id}获取已生成的分析报告。image上传用MultipartFile接收先存到本地临时目录或者OSS再把图片路径或base64编码传给推理服务。为了控制请求体大小我在SpringBoot里设置了最大上传限制spring: servlet: multipart: max-file-size: 20MB max-request-size: 50MB如果超过限制直接返回友好错误前端提示“图片过大”。3.2 集成YOLO推理Java直调与Python服务两种方案集成YOLO推理有两种主流方案。方案一Java直调ONNX Runtime。项目依赖dependency groupIdcom.microsoft.onnxruntime/groupId artifactIdonnxruntime/artifactId version1.16.3/version /dependency推理步骤是先读图并做letterbox缩放转为float数组输入session拿到输出张量再做坐标还原。Java写的后处理代码比较繁琐尤其是NMS部分。如果只做单张图片的简单场景还能忍受但要做复杂的预处理和后处理代码量会翻倍。方案二Python服务独立部署。我用FastAPI写了一个极简的YOLO推理服务SpringBoot通过HTTP调用它。这个方案我强烈推荐理由如下ultralytics库在Python侧维护模型最省事版本升级也方便预处理、NMS、可视化这些opencv操作在Python里一行代码搞定Java端只需要一个RestTemplate或WebClient调用接口不需要管模型推理细节。FastAPI推理服务核心代码from fastapi import FastAPI, UploadFile, File from ultralytics import YOLO import numpy as np import cv2 import io app FastAPI() model YOLO(best.pt) app.post(/detect) async def detect(file: UploadFile File(...)): contents await file.read() img cv2.imdecode(np.frombuffer(contents, np.uint8), cv2.IMREAD_COLOR) results model(img, conf0.25, iou0.45)[0] boxes results.boxes detections [] for i in range(len(boxes)): cls_id int(boxes.cls[i]) conf float(boxes.conf[i]) xyxy boxes.xyxy[i].tolist() detections.append({ class_id: cls_id, class_name: model.names[cls_id], confidence: conf, bbox: xyxy }) return {detections: detections}SpringBoot代码String url http://localhost:8000/detect; HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.MULTIPART_FORM_DATA); MultiValueMapString, Object body new LinkedMultiValueMap(); body.add(file, new FileSystemResource(tempFile)); HttpEntityMultiValueMapString, Object requestEntity new HttpEntity(body, headers); RestTemplate restTemplate new RestTemplate(); ResponseEntityMap response restTemplate.postForEntity(url, requestEntity, Map.class);这样就把推理压力全部隔离在Python服务里Java后端只负责业务编排。Python服务如果挂了SpringBoot还能降级返回错误不影响其他接口。3.3 千问与DeepSeek大模型智能分析接入大模型在这个系统里的角色是“质检分析员”。检测模型只输出坐标和类别比如“缺陷1破洞置信度0.92位置(120, 300, 180, 340)”但业务人看不懂这种数据。我要让大模型把这些信息翻译成一段有总结、有分析、有建议的文字。具体做法是拼接一个结构化Prompt把检测结果传进去。通义千问和DeepSeek各有特点千问中文理解细腻适合生成报告DeepSeek推理能力强更适合分析缺陷成因。我这个系统两个都接了让用户自己选择用哪个模型分析。实际上可以双模型同时跑再交叉校验但常规场景用一个就够。我定义的Prompt模板你是布料缺陷检测分析专家。下面是一个布料检测结果 - 图像中检测到的缺陷 1. 类别破洞置信度0.92位置x1120, y1300, x2180, y2340 2. 类别污渍置信度0.85位置x1400, y1210, x2460, y2280 请根据这些信息分析 1. 整体缺陷情况综述 2. 每种缺陷可能的成因 3. 生产工艺改进建议 4. 是否建议停机检查 要求语言简洁专业并按markdown格式输出。SpringBoot调用大模型的代码我用的是HTTP客户端加上一个通用的ChatCompletion请求体。以DeepSeek为例public String analyzeWithDeepSeek(String prompt) { HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.setBearerAuth(your-api-key); MapString, Object body new HashMap(); body.put(model, deepseek-chat); body.put(messages, List.of(Map.of(role, user, content, prompt))); body.put(temperature, 0.7f); HttpEntityMapString, Object request new HttpEntity(body, headers); ResponseEntityMap response restTemplate.postForEntity(https://api.deepseek.com/v1/chat/completions, request, Map.class); // 解析 response 里的 content 字段 }通义千问的接口结构类似但endpoint不同。接入大模型有几个细节要注意为了节省token如果同一张图片检测结果很多我只会把置信度大于0.5的缺陷传给大模型增加超时控制避免大模型接口慢导致前端长时间等待大模型回复可能包含多余内容建议后端做一个输出清洗只保留分析和建议部分。除了同步返回我还实现了SSE流式推送。大模型一次生成几百字报告如果等全部生成完再返回用户体验很差。用SSE的话前端可以一边接收一边渲染。4. 前端交互界面与前后端分离实现4.1 Web界面功能模块设计前端技术栈我选Vue3和Element Plus原因很直接组件生态全图表库、上传组件、布局组件随手拿来就能用前后端分离开发时前端只要维护一套HTTP接口即可。页面主要由四个区域组成左侧图片上传区支持拖拽或点击上传中间原始图片和叠加检测标注框的图片切换展示右上缺陷统计面板显示各缺陷类别数量、置信度分布下方大模型分析报告展示区支持markdown渲染。界面整体不需要太复杂工业软件最重要的就是直观。实际做的时候我发现很多人会忽略图片的清晰度问题。前端上传图片最好先做一次压缩处理比如通过canvas把图片最大边限制在1280像素减少后端传输压力同时不影响检测效果因为YOLO输入也就是640。4.2 核心交互实现上传图片后前端用Axios发送到SpringBoot接口const formData new FormData() formData.append(image, file) axios.post(/api/detect, formData, { headers: { Content-Type: multipart/form-data } }).then(res { drawBoxes(res.data.detections, res.data.imageWidth, res.data.imageHeight) renderReport(res.data.analysis) })drawBoxes函数在image Canvas上绘制矩形框和标签const canvas document.getElementById(canvas) const ctx canvas.getContext(2d) ctx.strokeStyle #ff4d4f ctx.lineWidth 3 ctx.font 14px sans-serif res.data.detections.forEach(det { const [x1, y1, x2, y2] det.bbox ctx.strokeRect(x1, y1, x2 - x1, y2 - y1) ctx.fillText(det.class_name det.confidence.toFixed(2), x1, y1 - 6) })canvas坐标要和图片实际像素保持一致所以我建议后端返回检测框时不要用归一化比例而是直接返回原始图片像素坐标。大模型报告如果采用SSE推送前端用EventSource或fetch的ReadableStream接收流式文本。这里不细讲SSE核心是后端将大模型的token逐段推给前端前端追加到markdown解析器里出现打字机效果。前后端分离最大的好处是前端改版不用重新部署Java服务。实际项目中前端部署在nginxSpringBoot作为API服务运行在另一台内网机器通过nginx反代解决跨域。开发环境则专门配置了CORS让前端开发服务器可以直连后端。5. 常见问题与排查技巧实录5.1 模型训练中的数据与训练问题问题一破洞误检成污渍。布料纹理复杂时破洞边缘的反光很容易被模型误判。解决方法是增加一些难负样本也就是拍摄没有缺陷但带有类似纹理的布料专门标注为background用负样本低学习率继续训练一轮效果提升明显。问题二小目标漏检。线头、极小的跳纱在640分辨率下只有十几个像素很容易被下采样特征层丢掉。解决方法是把输入分辨率提高到896同时改用YOLOv11m这种更大的模型虽然速度慢了一点但检全率上去了。如果还是漏就按tile切图把小图放大再检测然后把框映射回原图。这个方案我实测对小目标提升极大。问题三训练loss不下降。一开始用的imgsz1280batch又大显存爆了。后来切小imgsz才正常。还有一个问题是学习率设置过高初始学习率超过0.01loss直接发散。建议统一用ultralytics默认的0.01以内AdamW配合warmup不要自己乱调。5.2 部署与服务串联问题问题一CORS跨域报错。前端8080端口后端8081端口直接请求被拦截。我在SpringBoot加了一个CORS配置类Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(*) .allowedHeaders(*); } }生产环境里Allowed origins要限定为nginx域名否则有安全风险。问题二Python推理服务并发能力弱。FastAPI默认是异步单进程的如果同时上传很多图片会导致排队。解决方法是用gunicorn启动多workergunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app4个worker可以并行处理四张图。如果模型显存不够在worker间共享可以用一个常驻模型实例加线程锁或者使用Ray Serve之类的方案但一般项目不需要那么复杂。问题三大模型接口偶发超时。调用千问或DeepSeek在高峰期经常要等十几秒甚至报错。我做了两层处理第一层在SpringBoot里给HTTP客户端设置连接超时和读取超时连接3秒读取30秒第二层用本地缓存同一个图片的检测结果在30分钟内重复查询直接返回缓存的分析报告避免重复消耗token。问题四base64和multipart传输大图卡顿。解决方案是前端压缩图片后端接受压缩后的图片直接推理。如果还需要保存原图压缩图只用于检测原图单独存到OSS。5.3 前后端联调的一些细节要和前端配合好接口返回的数据结构一定要统一。我定义了一个统一返回格式{ code: 0, message: success, data: { image_url: http://xxx/processed.jpg, detections: [ { class_name: 破洞, confidence: 0.92, bbox: [120, 300, 180, 340] } ], analysis: # 布料缺陷分析报告... } }前端只认这个结构如果后面要增加字段必须跟前端确认好否则很容易出现前端取了undefined。另外canvas绘制框之前一定要确认图片加载完成否则拿到的图片宽度是0画出来的框全部偏移。在页面里用onload事件处理。设计这样一套系统最大的感受是视觉模型只是其中的一部分真正让系统跑起来的是工程化能力。从数据标注、模型训练、部署、接口封装到前端展示每一步都可能出现意外。特别是多模型、多服务的组合一定要做好接口幂等和超时机制不然生产环境分分钟出问题。最后分享一个小技巧如果你在SpringBoot里用RestTemplate调Python推理服务请务必为RestTemplate单独设置一个合理的连接池和超时时间同时给Python服务的模型加载加上开机预热。我见过很多次因为模型懒加载第一次调用等了20多秒前端直接抛超时。把模型在FastAPI启动时就加载好后续每个请求平均耗时只有几十毫秒。这样才能真正满足产线质检的实时性要求。