施工区路肩上那一排红色安全锥,平时开车经过可能压根不会多看一眼。但一旦你做AI视觉项目,就会知道这种小目标到底有多难搞:目标小、背景杂、光照变化大,还要兼顾实时性。我这次就做了一个以安全锥为检测目标的完整系统,用YOLOv8/YOLOv10/YOLOv11/YOLOv12做检测模型,SpringBoot搭后端服务,前端用Vue写交互界面,再接入千问和DeepSeek对检测结果做智能风险分析。整个项目是标准的前后端分离架构,既覆盖了深度学习训练与部署,又包含Web全栈开发,很适合作为“AI视觉+工程落地”的综合项目来参考复现。
这套系统能做什么,一句话说清楚:上传一张施工区域的图片或一段监控视频,后端调用YOLO模型识别出图片里的每个安全锥,在Web页面上画出检测框并统计数量;接着千问和DeepSeek基于检测结果做语义分析,自动生成安全风险提示和处置建议。这篇文章我会从技术选型、数据集处理、模型训练、后端集成、前端联调一直写到踩坑记录,尽量把每一个关键决策背后的原因也讲明白。
1. 项目整体设计与思路拆解
1.1 需求背景:安全锥为什么值得专门做检测
安全锥也叫交通锥、路锥,是道路施工、事故处理、园区封闭时最常用的物理隔离设施。它在场景里通常表现为小目标,尤其在监控摄像头俯拍视角下,一个安全锥可能只占几十个像素。实际需求不只是简单“识别出来”,还包括统计数量、判断摆放密度、识别缺口,甚至在夜间反光条件下也能稳定检测。
这类需求在高速公路养护、市政道路施工、智慧园区巡检里都很常见。施工单位需要确认安全锥是否按规范摆放,安全员每天要巡查多次,靠人工看图效率低且容易漏。用深度学习模型做自动检测,配合后端业务系统和大模型分析,就能把“看图片数锥子”这件事变成一条自动化的分析链路。对个人开发者来说,这个项目的好处是麻雀虽小五脏俱全:目标检测、服务端接口、前端展示、大模型调用全串起来了,非常适合作为求职项目或毕业设计。
1.2 技术选型背后的取舍逻辑
模型选型方面,YOLO系列几乎是目前工程落地最顺手的目标检测方案。题目里的v8、v10、v11、v12四个版本其实代表了不同的迭代方向:v8是生态最成熟的版本,文档多、坑少,做基线最合适;v10在结构上做了改进,但部分实现和官方库的兼容性需要留意;v11优化了主干网络,推理速度更快;v12是最新版本,外围配套还在完善,适合尝鲜但生产环境要谨慎。我在项目里用v8做主力模型,其余版本做对比实验,这样既能保证系统稳定,也能在汇报时展示多个模型的性能差异。
后端选SpringBoot,是因为它非常适合快速搭建业务接口,和数据库、文件存储、第三方API的集成都很顺手。需要特别说明的是,深度学习模型推理没有直接跑在SpringBoot进程里,而是做成了独立的Python推理服务。原因很简单:训练和推理都用Python生态,模型迭代不需要重新打包Java应用;而且GPU推理、图像预处理、NMS这些操作在Python侧实现成本最低。SpringBoot专心做业务编排、数据持久化和前端对接,职责边界清晰。
前端采用Vue3加组件库的方案,负责上传图片或视频、展示检测结果、调用AI分析接口。页面不需要太复杂,但交互要顺畅。大模型分析环节接入千问和DeepSeek两个接口,千问负责生成流畅的自然语言风险描述,DeepSeek负责输出结构化的JSON分析结果,两者各有侧重,实际测试下来效果不错。
1.3 系统架构与核心流程
整个系统分成四个部分:前端Web界面、SpringBoot后端、Python推理服务、大模型API。检测主流程是:前端上传图片,后端接收后调用Python推理服务的检测接口,推理服务返回检测框坐标、类别和置信度;后端把结果保存到数据库,同时把检测结果摘要拼成Prompt,调用千问和DeepSeek接口生成风险分析和处置建议;最后前端拿到所有结果,在图片上绘制检测框,在侧边栏展示统计信息和分析结论。
这套架构最关键的一点是“模型与业务解耦”。YOLO模型更新或者换版本,只需要改Python服务,不用动SpringBoot代码;业务规则和前端页面变化,也不影响模型推理。数据流是图片字节流或URL进去,结构化JSON出来,接口契约清晰,任何一端出问题都能独立排查。
2. YOLO模型选型、环境搭建与数据集处理
2.1 YOLOv8/10/11/12到底怎么选
我在动手前专门花了半天时间横向对比这四个版本,结论如下表:
| 版本 | 生态成熟度 | 推理速度 | 训练配置复杂度 | 适合场景 |
|---|---|---|---|---|
| YOLOv8 | 最高,资料齐全 | 中等 | 低,直接装官方包 | 基线模型、生产落地首选 |
| YOLOv10 | 中,部分改动需源码安装 | 较快 | 中 | 追求结构创新的对比实验 |
| YOLOv11 | 较高,官方包已支持 | 快 | 低 | 实时性要求高的场景 |
| YOLOv12 | 新,配套待完善 | 中等 | 中高 | 技术演示、新特性体验 |
实际结论是:如果只允许一个模型上线,我建议选YOLOv8s或YOLOv8m。v8的API稳定,从加载权重到导出ONNX都有现成方案,遇到问题搜一下基本都有答案。v11虽然快一些,但某些数据增强参数和v8不完全一致,踩坑成本高。v10和v12更适合作为对比项写进实验总结里,不建议一上来就全押在它们身上。
2.2 开发环境与依赖安装
我用的环境是Ubuntu服务器加一块中端显卡,PyTorch版本和CUDA版本要匹配。Python环境建议直接用Miniconda管理,避免污染系统环境。创建环境后安装核心依赖:
conda create -n cone python=3.10 -y conda activate cone pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics pip install fastapi uvicorn python-multipart注意版本坑:ultralytics版本不能太旧,否则无法加载部分v11和v12权重;但也不能追最新版,偶尔会引入破坏性变更。我习惯锁定一个已知稳定的版本,比如固定到某个发布版本,后续训练和部署都在同一环境里复现。Java侧只需要一个SpringBoot项目,用Maven管理依赖,部署时和后端一起打成jar包即可。
2.3 数据采集与标注规范
安全锥检测的数据集比想象中难搞,因为公开数据集里专门的“安全锥”类别很少,多数是COCO里的交通标志或行人,没有单独的cone标签。我的做法是混合策略:从公开道路上采集施工场景图,再补充一部分自己拍摄的图片,保证视角多样性。
标注工具我用的是X-AnyLabeling,它可以直接导出YOLO格式的txt文件。标注规范很简单,类别只设一个traffic_cone,用矩形框贴合锥体主体。有几个细节:反光条和地面阴影不要框进去;距离远的小锥子宁可框大一点也不要漏标;图片中有多个锥体时全部标完,不能只标志明显的。最终数据集我整理到了6000多张,其中包含白天、黄昏、夜间、雨雾等不同光照条件,这对模型泛化能力影响很大。
数据增强方面,不需要在外部手工做太多,Ultralytics训练时会自动应用Mosaic、随机仿射、色彩抖动等增强策略。但如果你的数据集里夜间图片太少,建议单独加一些亮度增强副本,否则夜间检测效果会明显变差。
3. 模型训练、评估与导出
3.1 数据划分与YAML配置
数据准备好后,我按8:1:1划分训练集、验证集和测试集。划分时注意:同一个场景连续截帧的图片要放到同一个集合里,避免图片泄漏导致评估结果虚高。也就是不能让模型“见过”测试集里同一场景的相邻帧,否则验证集的mAP看起来很高,实际现场效果却打折扣。
数据集根目录下新建data.yaml:
path: /data/cone_dataset train: images/train val: images/val test: images/test nc: 1 names: 0: traffic_cone这个文件是训练入口,路径尽量用绝对路径。Ultralytics读取时会自动检测类别数量,如果发现nc和names不匹配会直接报错。还有一个容易忽略的点:图片文件夹和标签文件夹在数据集内部的结构要与yaml里写的保持一致,标签文件默认放在与images平级的labels目录下。
3.2 训练参数设置与启动训练
我用YOLOv8s做基线,训练命令如下:
yolo detect train \ data=/data/cone_dataset/data.yaml \ model=yolov8s.pt \ epochs=120 \ imgsz=640 \ batch=16 \ device=0 \ workers=8 \ project=/data/train_output \ name=cone_v8s参数选择逻辑很简单。imgsz=640是检测精度和速度的平衡点,安全锥目标偏小,如果想进一步提升小目标召回,可以尝试imgsz=1280,但显存占用会明显增加,推理速度也会下降。batch由显卡显存决定,我的显卡12G,batch=16加上imgsz=640刚好能跑满。epochs=120足够模型收敛,我在第80轮左右就发现验证集mAP基本不再上升,早停或减少epoch都能接受。
如果要用v11或v12训练,命令基本一样,只是把model参数换成对应权重文件名。v11在官方库中可以直接通过yolo命令调用;v12在某些第三方实现中需要额外的安装步骤,使用时先确认权重加载是否正常,否则很可能在训练第一步就报错。
3.3 评估指标与模型导出
训练结束后,重点看验证集上的mAP50和mAP50-95。mAP50是所有检测框与真实框IoU达到0.5时的平均精度,主要反映“能不能找到目标”;mAP50-95则是在0.5到0.95多个阈值下的平均,更严格,也更贴近实际定位精度。我这边v8s在夜间测试集上mAP50能到0.86左右,mAP50-95在0.55左右,对小目标来说这个水平可以接受。
导出模型用一行命令:
yolo export model=/data/train_output/cone_v8s/weights/best.pt format=onnx opset=12 simplify=True导出ONNX是为了兼容不同推理环境。实际部署时我用的是PyTorch权重加载Python推理服务,ONNX格式作为备选。如果在没有GPU的机器上跑,ONNX配合CPU推理也能达到可用的速度,只是帧率会低一些。导出后建议用onnxruntime跑一次推理对比结果,确认导出后的模型输出与原始模型一致,避免前后处理差异导致检测框偏移。
4. SpringBoot后端集成与AI分析模块
4.1 后端整体设计
SpringBoot项目的核心任务是业务编排和接口管理,我用Maven模块划分了三个包:controller负责接收前端请求;service处理检测调用、分析调用和数据持久化;repository和entity负责数据库操作。数据表主要两张,一张存检测记录,一张存AI分析日志。
detect_record表字段包括:主键ID、图片存储路径、安全锥数量、平均置信度、完整检测结果JSON、创建时间。analysis_log表字段包括:主键ID、关联的检测记录ID、调用的模型名称(千问或DeepSeek)、生成的文本建议、结构化JSON结果、创建时间。
后端启动时预先初始化一个连接池,用来管理指向Python推理服务的HTTP客户端,避免每次都新建连接。同时配置了全局异常处理器,前端上传非图片文件或推理服务超时,都能返回统一的错误结构。
4.2 推理服务集成方案:两个选择
模型推理集成有两种做法,我这里都试过,最终选了独立Python服务方案。第一种是SpringBoot直接嵌入ONNX Runtime,Java加载onnx模型做推理,优点是不需要额外部署Python进程,但缺点也很明显:图像预处理、Letterbox缩放、NMS后处理全部要用Java重写,调试一次框偏移问题非常痛苦。第二种是Python跑FastAPI推理服务,SpringBoot通过HTTP调用,优点是利用了Python完整生态,换模型只改Python端代码,缺点是多了一个进程要部署管理。
我采用第二种。推理服务暴露两个接口:/infer/image接收上传的图片字节流,返回检测框列表;/infer/status用于健康检查。核心代码大致如下:
from fastapi import FastAPI, UploadFile from ultralytics import YOLO import cv2 import numpy as np app = FastAPI() model = YOLO("/data/train_output/cone_v8s/weights/best.pt") @app.post("/infer/image") async def infer_image(file: UploadFile): data = await file.read() img = cv2.imdecode(np.frombuffer(data, np.uint8), cv2.IMREAD_COLOR) results = model.predict(img, conf=0.4, imgsz=640) boxes = [] for r in results: for box in r.boxes: boxes.append({ "x1": float(box.xyxy[0][0]), "y1": float(box.xyxy[0][1]), "x2": float(box.xyxy[0][2]), "y2": float(box.xyxy[0][3]), "conf": float(box.conf[0]), "cls": int(box.cls[0]) }) return {"count": len(boxes), "boxes": boxes}这里返回的坐标是原始像素坐标,前端可以直接用来画框。如果图片过大,建议先在Python侧做一次尺寸限制,比如最长边压到1920像素内,否则大图推理耗时明显增加。
4.3 检测结果持久化
SpringBoot收到推理结果后,先把图片保存到本地磁盘或对象存储,然后把元数据和检测框JSON存入数据库。保存逻辑有几个细节:图片文件名用UUID加时间戳,避免重名;检测框坐标和置信度原样存JSON,后续做数据分析时直接读取;平均置信度单独抽出来建索引,方便前端按质量排序检索。
数据库保存这一步看似简单,但容易忽略异常场景。比如一张图片里检测到0个安全锥,这本身是一个有效结果,不能当成失败丢弃。我保存记录时要求后端对0结果做标记,前端列表里能看到“未检测到安全锥”的提示,而不是直接消失。
4.4 千问和DeepSeek智能分析接入
大模型分析的接入是整个系统里最有“智能感”的部分。我在SpringBoot里写了一个AiAnalyzeService,同时管理千问和DeepSeek两个调用客户端。两个模型的分工是:千问负责生成自然语言的风险描述和建议,语气更像安全员写的巡查记录;DeepSeek负责输出结构化JSON,方便前端直接渲染成条目化的风险列表。
调用的核心是Prompt设计。我固定了一套提示模板:
你是道路施工安全分析助手。以下是一次安全锥检测的结果: 检测数量:5个 平均置信度:0.87 检测框位置摘要:[左侧1个,右侧4个,中间区域0个] 请完成两件事: 1. 用自然语言描述当前安全锥摆放情况和潜在风险。 2. 输出JSON格式:{"risk_level": "低/中/高", "issues": ["..."], "suggestion": "..."}注意几个细节:大模型返回文本并不保证是合法JSON,我要求DeepSeek在输出时只返回JSON体,如果解析失败,后端会做一次清洗,截取{}包围的内容再解析;千问的调用单独走文本通道,不要求结构化。接口超时时间设置在25秒左右,避免用户长时间等待。关键词“安全锥”“施工风险”等业务语义完全由Prompt控制,后续想扩展检测目标,不需要改模型,只需改Prompt和前端展示。
5. 前端交互界面与前后端联调
5.1 前端技术栈和页面布局
前端我用Vue3加Vite搭项目,UI库选择Element Plus,图表用ECharts。页面整体分三块:顶部是上传入口,支持拖拽图片、选择文件、上传视频;中间区域展示检测后的图片或视频画面,检测框用Canvas绘制;右侧边栏展示统计信息和AI分析结果。
布局上的考虑很简单:检测结果要让用户一眼看懂。Canvas画框必须和原图尺寸一致,所以我在上传成功后先把图片原始宽高记录下来,后端返回的检测框坐标是像素值,绘制时直接用,避免出现框错位。统计区域用数字卡片展示安全锥总数、平均置信度、最高风险等级。AI分析区放在最下方,先显示千问的文本描述,再展示DeepSeek的结构化风险条目。
5.2 图片和视频检测完整链路
以图片检测为例,完整链路是:用户拖入图片,前端预览缩略图,点击“开始检测”按钮后调用POST /api/detect/image接口。SpringBoot接收图片后调用Python推理服务,拿到检测框后保存数据库,再调用AI分析服务,最后把检测结果和分析结果一次性返回给前端。
这个流程我故意把AI分析做成了同步调用,虽然等待时间会增加,但用户体验简单:一次点击,全部结果出来。视频检测则改成异步任务,后端收到视频后返回任务ID,前端轮询任务状态,检测完成后逐帧展示结果。异步流程稍微复杂,但避免了大文件上传导致接口长时间占用。
前端调用接口统一用Axios封装,拦截器里处理后端返回的错误码。这里有个实用的做法:在请求头里带一个X-Request-Time时间戳,方便在日志中定位一次完整链路的耗时分布。
5.3 联调中的几个关键问题
前后端联调阶段最容易出问题的是跨域。SpringBoot需要显式配置CORS,允许前端的域名或端口访问,否则浏览器会直接拦截响应。我是在配置类里写了一个全局CORS映射,允许所有来源,方便开发阶段调试,生产环境再收紧到具体域名。
另一个重要问题是参数传递。后端接收图片用的是MultipartFile,前端上传时一定要用FormData,不要直接传JSON字符串。视频上传要调大SpringBoot的spring.servlet.multipart.max-file-size和max-request-size,默认1MB肯定不够,我设置到了500MB。
联调中还有个小坑:前端画框时,如果把Canvas的显示宽度设置为容器宽度,而后端返回的坐标是以原始图片为基准的,就会出现框偏移。需要在Canvas上根据图片实际显示尺寸做等比换算,或者直接把Canvas的宽度设置为图片原始宽度,再通过CSS控制显示缩放。
6. 常见问题与排查实录
6.1 训练阶段高频问题
训练时报CUDA out of memory是最常见的问题,解决思路是减少batch或调小imgsz,再或者用device=0确保用的是GPU。还有一个容易被忽略的问题:数据路径里的中文名或空格会导致读取失败,数据集目录和标签路径最好不要出现中文和空格。
检测效果不好的原因通常是数据问题,而不是模型问题。我发现夜间效果差时,第一反应不是改模型结构,而是补充夜间样本,并把夜间图片的亮度标签记得打开。另外,安全锥和某些红色锥形施工现场设施外观接近,容易误检,这类问题要通过增加负样本或者提高置信度阈值来缓解。
6.2 部署与联调阶段高频问题
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 前端请求报403 | 跨域未配置 | SpringBoot配置CORS映射 |
| 检测框偏移 | Canvas宽度和图片原始宽度不一致 | 按图片原始尺寸绘制并等比缩放 |
| 上传大视频超时 | Nginx或后端请求体限制 | 调整multipart限制和网关超时 |
| 大模型返回解析失败 | 模型输出包含多余文本 | 清洗文本,提取JSON片段后解析 |
| Python推理服务无法访问 | 服务未启动或端口不一致 | 检查健康检查接口是否返回200 |
| 视频检测内存暴涨 | 一次性读取全部帧 | 改为逐帧读取、逐帧推理 |
排查这类问题,我的习惯是先画一条“请求链路图”,确认问题发生在哪个环节。比如前端报错,先在浏览器开发者工具里看网络请求是否到达后端,再查看SpringBoot日志里是否调用过Python服务,用日志定位最有效。
6.3 性能优化要点
如果觉得推理速度不够,可以从几个方向优化。第一是模型侧的优化:用TensorRT或ONNX Runtime的FP16推理,速度比PyTorch原生推理快不少;第二是减少输入尺度,安全锥检测其实不需要每帧都跑1280分辨率,640足够;第三是加缓存,对已经检测过的图片,在后端用Redis缓存结果,重复请求直接返回历史结果,降低压力。
我在实际部署中还做了一个小优化:Python推理服务内部加了一个有界线程池,控制并发推理数量。因为GPU同时跑多个推理任务不仅不会提速,反而会因为显存争抢导致每个任务都变慢。现在系统对单图请求的响应时间稳定在300到500毫秒之间,视频抽帧检测能做到接近实时。
最后分享一点个人体会。这个项目做完,我最大的感受是模型训练只占整个工作量的一部分,真正花时间的是把模型、后端、前端、大模型这些零件拼成一条顺畅的流水线。每个环节单独拆开都不难,难的是接口设计是否稳定、异常处理是否完备、用户等待体验是否合理。如果你动手做类似的视觉检测系统,我建议先把整条链路跑通,再逐步优化每个环节的细节,顺序一旦反过来,效率会低很多。这套架构里用到的思路:模型推理独立成服务、大模型分析通过Prompt编排、前端结果可视化,后续扩展其他检测目标或者换新的模型版本,都不需要推翻重来。