ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

YOLO+SpringBoot+大模型:密集行人检测系统实战解析

YOLO+SpringBoot+大模型:密集行人检测系统实战解析 做项目最怕的不是技术难而是技术选型先绕晕自己。这个标题把 YOLO 系列、SpringBoot、大模型分析、前后端分离、数据集转换全塞在一起表面看是个“全家桶”实际上是一条非常典型的计算机视觉落地方案前端做交互后端管调度YOLO 负责“看见”大模型负责“理解”。我做这套密集行人检测系统的过程就是一点一点把这些零件拧到一块儿去的踩了不少坑也整理出了一套可以直接复用的套路。这套系统解决的核心问题说白了就一句话传统监控和图片分析只告诉你“这里有人”但业务方想要的是“这里有多少人、什么状态、需不需要引起注意”。YOLO 系列模型负责把“人”从画面里框出来SpringBoot 后端统一接管检测请求与数据流转千问和 DeepSeek 这类大模型再基于检测结果做语义化解读最后通过前后端分离的 Web 界面把结果直观地呈现出来。整条链路打通之后你上传一张密集人群图系统不仅告诉你检测到了几十个人还能输出“人群密度偏高疑似存在局部拥挤建议关注”这类可读分析。这篇文章就把从数据处理到模型训练、从后端集成到前端展示、从大模型对接到上线排坑的完整过程掰开揉碎讲清楚。1. 系统整体设计与技术选型思路1.1 项目定位从“目标检测”到“业务理解”的完整链路很多人一开始觉得这个项目无非就是训练一个 YOLO 模型然后写个接口返回坐标。但等你真正接手一个“密集行人检测系统”的需求就会发现模型推理只是最基础的一环。我最初的方案设想是单机 Python 脚本加载 YOLO 权重、跑推理、用 OpenCV 画框、输出几张图片。但跟业务方沟通后认识到他们需要的是一个可持续使用的系统要有账号操作界面、检测记录的留存、分析报告的自动化生成甚至后续还要接入摄像头实时流。这就决定了后端必须有一个稳定、适合做业务系统的框架SpringBoot 在这个位置上是比较稳妥的选择。它生态成熟、招人好招、跟数据库和权限框架的整合都非常顺手。技术链路的完整形态是Vue 前端上传图片或配置视频流 → Nginx 反向代理 → SpringBoot 后端接收请求 → 将图片转交 Python 模型推理服务 → 拿到检测框 JSON 结果后同步调用大模型接口生成文字分析 → 整合数据落库并推送到前端展示。YOLO 负责检测这个“感知”动作大模型负责“认知”这个理解动作SpringBoot 是中间的“调度中枢”。这样设计的好处是每一层都能独立替换、独立扩展不会因为某个环节的升级导致整套系统推倒重来。1.2 YOLOv8/v10/v11/v12 版本差异与选型理由标题里把 YOLOv8、YOLOv10、YOLOv11、YOLOv12 都列了出来实际项目里不可能四个版本都上生产环境正确做法是先把它们放在同一条评估流水线上用同一批密集行人数据测试精度和速度再做出选择。这里我把我对比后的结论整理一下。模型版本核心改进密集行人场景表现推荐场景YOLOv8anchor-free 架构C2f 模块通用性强精度稳定小目标表现中规中矩大多数通用检测任务适合作为基线YOLOv10NMS-free 训练双标签分配推理速度快但密集遮挡场景偶发漏检边缘设备、对帧率要求高的场景YOLOv11使用 C3k2 模块梯度流优化在 v8 基础上小幅提升同精度下模型更小资源有限但仍需精度的平衡方案YOLOv12引入注意力机制改进骨干网络小目标和遮挡目标召回提升明显密集人群、小目标居多的核心场景我最终在训练阶段采用的是 YOLOv12 的模型原因是密集行人场景最大的痛点不是“人少测不准”而是“人多互相遮挡导致漏检”。v12 加入注意力机制之后对遮挡目标的特征提取能力确实有所加强实测在同一组 CrowdHuman 风格数据下漏检率比 v8 降低了三四个百分点。不过推理速度上 v12 比 v10 慢一些所以如果做的是边缘盒子项目需要更高帧率我建议回头考虑 v10 或者对 v12 做 TensorRT 加速。关键词不是追新而是按场景匹配结构。2. 数据集准备与模型训练实战2.1 公开数据集选择与 KITTI 标注转 YOLO 格式模型训练不能只用网上随便下的几个图片文件夹数据质量直接决定模型效果的底线。密集行人检测这边常用的公开数据集包括 CrowdHuman、VisDrone、MOT17 和 KITTI。CrowdHuman 的密集程度最高但它的标注是 JSON 格式还需要做不少清洗VisDrone 是俯视视角的无人机画面跟普通监控视角差异较大KITTI 数据虽然主要是自动驾驶街景但行人标注规范对起步阶段非常友好。很多热门搜索里都有“KITTI 标注转 YOLO”这个问题因为 KITTI 的标注格式是“类别 截断 遮挡 观察角 bbox 3维信息”而 YOLO 需要的是“类别ID 中心点x 中心点y 宽度w 高度h”并且全部归一化到 0 到 1 之间。核心转换逻辑就是从 bbox 的四个像素坐标x_left, y_top, x_right, y_bottom计算出中心点和宽高再分别除以图片的宽度和高度。下面给出一段可以直接使用的转换脚本核心部分。import os from PIL import Image def kitti_to_yolo(kitti_line, img_width, img_height): parts kitti_line.strip().split() cls parts[0] x_left, y_top, x_right, y_bottom map(float, parts[4:8]) # 类别映射KITTI 中 Pedestrian 对应 person class_id 0 if cls Pedestrian else -1 if class_id -1: return None box_w x_right - x_left box_h y_bottom - y_top x_center x_left box_w / 2 y_center y_top box_h / 2 x_center_norm x_center / img_width y_center_norm y_center / img_height box_w_norm box_w / img_width box_h_norm box_h / img_height return f{class_id} {x_center_norm:.6f} {y_center_norm:.6f} {box_w_norm:.6f} {box_h_norm:.6f}转换时需要注意KITTI 原数据集里有一些“DontCare”类别这些区域转出来之后要直接过滤掉否则会把背景当成目标喂给模型。另外KITTI 图片尺寸统一为 1242×375实际训练时 YOLO 会做 letterbox 缩放所以转换时的归一化不会因为尺寸问题产生偏差。转换完成后记得随机划分训练集、验证集和测试集我采用的比例是 8:1:1并且保证同一段视频序列的图片只出现在同一个集合里避免数据泄露。2.2 YOLO 环境配置与 AMD 显卡训练的现实问题环境配置这块Intel/NVIDIA 用户相对顺利但 AMD 显卡用户很容易卡住。我看到很多人问“AMD RX 580 显卡能跑 YOLOv8 吗”这个问题需要分开看如果是拿别人训练好的权重做推理在 Windows 下可以通过 ONNX Runtime 的 DirectML 执行后端跑速度虽然不如 CUDA但能出结果如果是准备从零训练RX 580 这种 GCN 架构老卡基本指望不上ROCm 对它的支持有限而且 Windows 下的 ROCm 支持远不如 Linux。我在初期的试跑阶段也尝试过直接用本地 AMD 显卡训练结果装驱动、配 ROCm 折腾了半天最后还是放弃了转到云 GPU 平台租了一张 RTX 4090 做训练。如果你是学生或者个人开发者预算有限我的建议是本地只做代码调试和数据集检查真正训练用 Kaggle 或者 Colab 的免费 GPU又或者找按量计费的云服务器。不要为了“本地能跑训练”这个目标花太多时间。如果仍然要在本地做小规模验证CPU 跑 YOLO 也不是不能接受把 batch size 设成 2imgsz 设成 416epochs 设成 30训练一个小模型验证 pipeline 通了再上云。环境依赖就是下面这几行注意 PyTorch 版本和 CUDA 版本的匹配直接用 ultralytics 官方安装方式最省心。pip install ultralytics pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118实际上 ultralytics 包会把大部分依赖带上不需要手动装一堆 opencv、numpy 之类的东西。唯一容易出问题的是本机已经装了 CPU 版 PyTorch然后又想切 CUDA 版这时需要先把旧版本卸干净再装新版否则会一直提示 CUDA unavailable。2.3 YOLO 损失函数理解与训练参数调整聊训练参数之前有必要把 YOLO 的损失函数大概讲清楚这样你在调参的时候才有方向感。YOLOv8 及后续版本的总损失由三部分组成分类损失、回归损失和分布式聚焦损失。分类损失用的是 BCE负责判断每个预测框里的目标是什么类别回归损失用的是 CIoU负责让预测框跟真实框的位置尽量重合分布式聚焦损失是 YOLOv8 引入的 DFLDistribution Focal Loss它会把边界框的回归任务当作一个分布预测问题让模型对边界的学习更细腻。在密集行人场景里除了训练损失数值要下降更值得关注的是“遮挡”带来的训练难题。当两个人紧紧挨在一起时模型容易把两个目标合成一个框。常规的解决思路是第一把输入分辨率从默认的 640 提升到 768 或 896让模型看到更多细节代价是训练和推理变慢第二增大 Mosaic 和 MixUp 增强概率强迫模型在多个物体拼贴的画面中学习区分个体第三在最后训练阶段开启 close_mosaic让模型在普通分布的数据上稳定收敛。我使用的训练配置大致如下。# config.yaml model: yolov12x.pt data: crowd.yaml epochs: 120 imgsz: 768 batch: 8 optimizer: SGD lr0: 0.01 weight_decay: 0.0005 mosaic: 1.0 mixup: 0.2 close_mosaic: 10 hsv_h: 0.015 hsv_s: 0.7 hsv_v: 0.4 fliplr: 0.5 scale: 0.5训练完之后一定要看 val 指标里 PR 曲线的变化而不是只盯着 mAP 一个数。密集人群场景Precision 高但 Recall 低说明模型“框出来的人基本正确但漏掉很多”Recall 高但 Precision 低说明模型“找到很多人但也框错了很多”。业务上如果需要追求不遗漏就要在 Recall 和 Precision 之间找一个平衡点YOLO 训练完导出的模型默认会选择一个平衡阈值实际部署时你还可以在推理代码里调整 conf 和 iou 两个参数这个后面会详细说。3. SpringBoot 后端与模型推理服务集成方案3.1 模型部署方案对比Python 推理独立服务还是 Java 直接推理这是做前后端分离项目时一定会纠结的问题。YOLO 官方训练好的权重是 .pt 格式PyTorch 环境下使用非常顺畅但 Java 世界没法直接加载 PyTorch 模型。两条主流路线如下。第一条路是把 Python 推理做成一个独立的 HTTP 服务用 FastAPI 或 Flask 封装 /detect 接口接收图片字节流返回 JSON 检测结果。SpringBoot 后端通过 RestTemplate 或 WebClient 去调用这个 Python 服务。这样做的核心优势是生态互补检测相关的预处理、推理、后处理都在 Python 端YOLO 的权重迭代、数据增强逻辑完全不用碰 Java 代码SpringBoot 只管业务编排、数据库存储、权限控制。缺点是架构上多了一个服务部署时多一份运维负担。第二条路是将 YOLO 权重导出为 ONNX 格式然后在 Java 中使用 ONNX Runtime 的 Java API 加载模型执行推理。这样做的好处是整个系统只有一个 Java 进程没有跨语言调用开销。但坏处也很明显图像预处理、NMS 后处理都要自己在 Java 侧写ONNX 导出时如果选了带 NMS 的节点还需要仔细核对输出结构后续要改模型结构、做动态尺寸输入都需要同步改 Java 后处理代码。综合下来我的选择是第一条路Python 推理服务独立部署在另一台或同一个宿主机的不同端口上SpringBoot 通过内网地址调用这样最符合两者各自的社区最佳实践也是社区里大量生产项目采用的形态。3.2 SpringBoot 接口设计与前后端分离的数据流SpringBoot 这边的接口设计我按功能拆成了三个核心模块图片检测接口、视频流检测接口和分析结果查询接口。图片检测接口接收 MultipartFile 上传的图片将图片转成 Base64 或直接转字节流POST 到 Python 推理服务的 /detect 接口拿到检测结果后立即调用大模型分析接口最后把图片 URL、检测框 JSON、分析报告一起存进数据库。核心代码结构大致如下。Service public class DetectService { private final RestTemplate restTemplate; public DetectService(RestTemplate restTemplate) { this.restTemplate restTemplate; } public DetectResult detect(MultipartFile file) { byte[] imageBytes; try { imageBytes file.getBytes(); } catch (IOException e) { throw new BizException(图片读取失败); } HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.IMAGE_JPEG); HttpEntitybyte[] requestEntity new HttpEntity(imageBytes, headers); // Python 推理服务负责返回框坐标与置信度 ResponseEntityDetectResponse response restTemplate.postForEntity( http://localhost:8001/detect, requestEntity, DetectResponse.class); return buildResult(response.getBody()); } }前后端分离的数据交互中前端为 Vue3 Element Plus核心交互是上传图片后等待几秒钟画面上出现带边界框的预览图右侧面板同时展示检测数量、人群密度等级和大模型生成的文字分析。后端为前端提供 RESTful API前端通过 Axios 访问。图片视频的实时预览我采用了 WebSocket 推送检测结果的方案SpringBoot 收到检测结果后定时推送到前端指定频道前端用 Canvas 把边界框画在视频帧上而不是等待整个视频流传输完毕再处理。这样交互体验顺畅得多。为了便于团队协作我还把接口文档用 springdoc-openapi 生成成了可在线调试的 Swagger UI前端同学直接对着文档联调不用反复问我接口里的人名字段含义。3.3 千问与 DeepSeek 大模型智能分析模块的实现思路把大模型接进项目其实没有想象中复杂但需要想清楚输入什么、输出什么、以及调用成本。在密集行人检测场景中如果直接让大模型“看”图片很多接口并不支持视觉输入即便支持成本也高。我的实现思路是让大模型“读” YOLO 的检测结果把目标数量、框的分布区域、人群重叠情况、平均置信度这些结构化信息组装成一段文字让大模型基于这些信息输出业务层面的判断。例如组装给 DeepSeek 的 Prompt 是这样你是一个智能安防分析助手。下面是一次密集行人检测的结构化输出 - 检测总人数: 47 - 图像区域被划分为九宫格各区域人数分布: [12, 3, 5, 8, 2, 4, 7, 1, 5] - 最大重叠度作为遮挡指标: 0.78 - 平均置信度: 0.86 请基于以上信息分析该场景是否存在人群拥挤风险并给出简短建议不超过80字。千问和 DeepSeek 的接口在 OpenAI 兼容协议下接起来非常简单只要设置好 API Key 和 Base URL然后构造 chat completion 请求即可。SpringBoot 中我用的是 HttpClient 发送 JSON 请求没有引入额外的 SDK核心请求代码只有几十行。需要特别注意两件事第一API 的 Key 绝对不能写在前端代码里要放在 SpringBoot 的配置文件中并且通过环境变量注入第二大模型有响应延迟高并发场景下建议把分析过程做成异步任务先返回检测结果给前端分析报告通过 WebSocket 推送这样体验比同步等待好很多。curl -X POST https://api.deepseek.com/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer YOUR_API_KEY \ -d { model: deepseek-chat, messages: [ {role: system, content: 你是一个安防分析助手}, {role: user, content: 检测到密集人群人数47存在遮挡请分析} ] }千问则可以在阿里云百炼平台上开通平台的 Base URL 跟 DeepSeek 略有不同但请求体结构是一致的。为了优雅切换我在配置里用了一个枚举变量llm.provider可选值为 deepseek 或 qwen切换时只需要改配置不需要改代码。4. 前端交互界面与系统联调落地4.1 页面结构设计与 Canvas 画框实现前端部分如果只是简单展示“上传一张图、显示一段 JSON”那就不叫“web 交互界面”了至少要让用户感觉到这是一个真正在工作的系统。我的页面分为三个区域左侧是上传区域和检测设置区域用户可以上传图片、填置信度阈值、选择是否启用大模型分析中间是结果展示区默认显示原图检测完成后用 Canvas 叠加边界框框的颜色根据置信度高低从绿色渐变到红色右侧是信息面板展示总人数、密集区域数、平均置信度和大模型分析文字。Canvas 画框不能只看坐标因为 YOLO 检测输出的坐标是相对原图的如果前端展示时把图片缩放了框的坐标必须跟着缩放。稳妥的办法是采用同一个缩放比例后端返回原始图片的宽高前端拿到图片后根据最大显示区域计算缩放比然后用canvas.getContext(2d).strokeRect()画框。文字标签放在框的上方格式为person 0.86。核心绘制函数如下。function drawDetections(image, detections, canvas) { const ctx canvas.getContext(2d); const scale Math.min(canvas.width / image.width, canvas.height / image.height); const offsetX (canvas.width - image.width * scale) / 2; const offsetY (canvas.height - image.height * scale) / 2; ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(image, offsetX, offsetY, image.width * scale, image.height * scale); detections.forEach(det { const x det.x * image.width * scale offsetX; const y det.y * image.height * scale offsetY; const w det.w * image.width * scale; const h det.h * image.height * scale; ctx.strokeStyle det.conf 0.7 ? #00ff00 : #ffaa00; ctx.lineWidth 2; ctx.strokeRect(x, y, w, h); ctx.fillStyle rgba(0,0,0,0.6); ctx.fillRect(x, y - 20, 80, 20); ctx.fillStyle #ffffff; ctx.font 14px Arial; ctx.fillText(person ${det.conf.toFixed(2)}, x 5, y - 4); }); }4.2 系统部署与前后端联调的关键配置前后端分离项目上线时最容易出问题的是跨域和路径转发。开发阶段前端跑在 5173后端跑在 8080跨域问题通常靠后端 CORS 配置解决。生产环境则不同我用 Nginx 同时托管前端静态文件并反向代理后端接口浏览器只与 Nginx 一个域名通信从根上避免跨域。Nginx 核心配置如下。server { listen 80; server_name your-domain.com; # 前端静态资源 root /var/www/dist; index index.html; # 前端 history 路由 fallback location / { try_files $uri $uri/ /index.html; } # 后端接口代理 location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # Python 推理服务不直接暴露给外部 # SpringBoot 内部通过内网地址访问 8001 端口 }部署时Python 推理服务用 uvicorn 启动监听 8001 端口SpringBoot 项目打成 jar 包用nohup java -jar启动。建议不要用 root 用户直接跑新建一个业务账号专门用于运行服务。数据库选用 MySQL 8ORM 用 MyBatis-Plus检测记录表的核心字段包括 id、image_url、detect_result_json、analysis_text、create_time。如果表不存在MyBatis-Plus 可以配置ddl-auto: update让 JPA 管理自动建表用 MyBatis-Plus 的话可以在启动时执行一段建表脚本避免每台新服务器手动建表。5. 常见问题与排查技巧实录5.1 训练与推理阶段的高频问题对照现象可能原因解决方案训练时提示 CUDA unavailablePyTorch 版本和 CUDA 不匹配或安装的是 CPU 版卸载后按 CUDA 版本重新安装 PyTorchtorch.cuda.is_available()验证检测时一个人被框成两个框NMS 阈值设置过低或过高调高 iou 参数到 0.5 左右若是两个重叠框保留最高置信度密集人群漏检严重输入分辨率过低、数据增强策略不适配imgsz 提到 768增强 crowdhuman 数据集调整 mosaic/mixupAMD 显卡推理报错缺少 DirectML/ROCm 执行提供程序ONNX Runtime 安装onnxruntime-directml或者直接改用云 GPU视频流检测卡顿明显每帧都做同步推理降低检测帧率抽帧检测间隔帧插值保留最近分析结果SpringBoot 调用 Python 服务超时图片过大或推理排队设置合理超时时间Python 服务增加队列机制或改用异步调用大模型分析结果与检测结果不符Prompt 信息粒度不够把检测框按九宫格聚合后再交给大模型降低噪声5.2 自己踩过的几个值得单独说的坑第一坑是 SpringBoot 版本太高导致依赖冲突。我项目里最开始用了 SpringBoot 3.4 的新版本结果配合一些老版本的 MyBatis 和数据库驱动启动时就报各种 bean 初始化异常后来干脆统一用 SpringBoot 3.2 系列与 springdoc-openapi、mybatis-plus 的兼容性都很稳。给想少踩坑的人一个建议新项目不一定非要追最新版本选一个“被社区验证过组合”的版本集合更重要。第二坑是 Python 推理服务的并发问题。Flask 默认是单进程多线程高并发下需要 uvicorn 配合多个 worker但 YOLO 模型加载到内存里进程间不共享会导致内存占用翻倍。我的方案是用 FastAPI 一个进程内队列请求先进队列推理线程池固定 2 个 worker这样模型只加载一份又能控制并发不至于把显卡显存或内存打爆。其实就是一个“生产者消费者”的思路非常简单但非常有效。第三坑是“springboot 配置”里的大模型 API Key 误提交到 Git。我只提交过一次就立刻改掉了 Key但如果你也遇到过这种问题光改 Key 不够还要检查 Git 历史里是否已经残留最好用git filter-repo清洗历史或者直接把仓库设为私有并从远端强制刷新。最后再分享一点个人体会做完这个项目我最大的感受是技术整合的难度不在于某一个环节有多深而在于你是否有能力把“检测框数组”从一个模型输出变成一个业务系统里可以被理解、被存储、被分析的数据资产。YOLO 模型是引擎SpringBoot 是骨架千问和 DeepSeek 是大脑但真正决定系统成色的是你怎么设计它们之间的数据接口、怎么处理异常、怎么控制成本和延迟。如果你也想做一套类似系统我的建议是从最小闭环开始第一步先本地跑通 YOLO 推理脚本第二步写一个最简单的 SpringBoot 接口调用这个脚本第三步把检测结果用画框的方式显示在网页上。三步走通再考虑大模型分析和摄像头接入。别一上来就想着做一个多完美的系统先让数据完整地流转起来然后再逐步丰富每个环节。密集行人检测这套东西踩过坑之后回看其实每一步都有迹可循。
返回列表