
搞过密集行人检测的朋友应该都有同感一张图里几十上百号人框一多性能、漏检、误检、NMS压制、后端吞吐、前端卡顿全来了。只做离线推理还好一旦要落地成一套能看的系统事情就变成“目标检测 Web后端 大模型分析 前端可视化”的四面围攻。这次做的这套东西就是用YOLOv8/YOLOv10/YOLOv11/YOLOv12做检测底座SpringBoot做业务后端再把千问和DeepSeek接进来做智能分析最后用Vue这类前端搭出交互界面整体走前后端分离。这套系统能解决什么问题往大了说是把“检测能力”变成“业务产品”。摄像头或者历史视频抽帧上传之后后端调用YOLO模型识别行人拿到坐标和置信度识别结果再交给千问或DeepSeek做语义层分析比如判断这个区域是不是聚集过度、当前画面有没有安全风险最后前端实时展示检测框、告警信息、人流统计图表。如果不想只停留在跑通一个demo而是想在真实场景里让模型、接口、页面、大模型各司其职那这篇东西值得你参考。1. 密集行人检测系统的整体技术栈与设计思路1.1 为什么在YOLOv8/YOLOv10/YOLOv11/YOLOv12里选型YOLO系列现在几乎成了目标检测事实标准不是因为每个版本都完美而是因为生态太成熟了。训练、导出、部署、换数据集一套流程通吃遇到问题搜一下全是解决方案。这套系统同时兼容了四个版本很多人问意义在哪其实很简单不同部署环境和精度要求下换模型只改一行配置系统框架不用动。YOLOv8的C2f结构在特征复用上做得比较到位配合Anchor-Free的解耦头训练稳定是绝大多数项目的稳妥起点。YOLOv10在推理效率上砍掉了NMS用了一致性匹配和双标签分配正面密集场景下目标互相遮挡导致NMS误删真值的情况减少不少。YOLOv11继续优化了C3k2模块和注意力机制轻量化和精度平衡得更好。YOLOv12的最新架构引入了区域注意力对拥挤场景的语义理解有提升但对算力要求也高一点。我的建议是边缘设备优先YOLOv8n/s追求速度和精度均衡用YOLOv10n/s能上GPU服务器就试YOLOv12。1.2 SpringBoot在整个系统里的角色很多人习惯把推理服务直接写成Python接口前端调Python后端就一个转发层这样做小demo没问题但做成正经系统会出问题。SpringBoot在这里解决的是业务侧的事用户登录和权限控制、视频源和摄像头配置、告警记录存储、历史检测结果查询、与大模型交互的编排、前端页面的数据接口。前后端分离的价值在这里体现得很明显。前端只关心怎么把数据渲染出来后端只关心业务逻辑和数据处理两边可以并行开发。SpringBoot把模型推理、业务数据库、外部大模型API统一编排成一套RESTful接口前端对接时不需要关心底层调的是YOLO还是千问。1.3 千问和DeepSeek在系统里的分工纯目标检测只能输出坐标、类别、置信度它回答不了“这个画面里发生了什么”这种问题。千问和DeepSeek在这里负责语义分析层把检测结果转成结构化JSON交给大模型生成人流趋势描述、风险等级判断、异常聚集提示。我在这个项目里的做法是双模型配合快速场景判断用千问因为千问对中文场景描述和通用安全知识的理解稳定单次分析响应速度也不错复杂场景的深度分析交给DeepSeek让它在给定上下文的情况下做更细的因果关系推断。两边的API都是OpenAI兼容风格封装层可以共用一套代码换Base URL和模型名就行。2. 密集行人数据的准备与YOLO模型训练全流程2.1 训练数据怎么搞才不偏密集场景和普通场景的数据要求完全不同。拿CrowdHuman、VisDrone这类公开数据集打底能解决大部分基础问题。CrowdHuman的特点就是密集一张图里几十人互相遮挡非常适合训练密集行人检测VisDrone是无人机视角小目标多能提升模型对远距离小人的感受能力。自采数据时要特别注意抽帧策略。很多朋友直接按视频时间抽帧结果相邻帧几乎一样训练集有效信息极少。正确做法是按场景抽帧同一个机位每间隔5到10秒抽一帧覆盖不同时段和光照条件。另外只抽帧还不够要把不同机位、不同俯仰角、不同密度的画面分开归组训练集、验证集按场景分组划分而不是随机乱分防止模型对特定机位过拟合。2.2 标注密集行人的核心注意事项标注工具选AnyLabeling或者X-AnyLabeling都行前者自带模型辅助标注能省不少手工活。密集型场景的标注和普通场景区别很大严重遮挡的目标也要标只要人能看出有明显的头肩特征或者半边身体就框进去这样模型才能学到“遮挡状态下依然是人”。小目标不要因为看着费劲就不标用辅助标注工具放大图片再标跟VisDrone混训的模型普遍对小人更敏感。类别不要搞太复杂只标person就够了目标过多会分散模型学习能力。标注完导出YOLO格式时注意坐标格式是归一化的cx、cy、width、height标注工具一般直接导出不用手动改。验证时可以随机抽几张图可视化看一眼框和原图是否对齐这个步骤不要省。2.3 1660Ti这种显卡能不能训怎么训很多人担心显存不够我用GTX 1660Ti实测过6GB显存跑YOLOv8s完全没有问题。关键参数是这么调的batch size设为8图像尺寸640开混合精度训练ampworkers设置为2。如果你只有4GB显存把batch降到4模型换成YOLOv8n一样能训练只是收敛慢一些。训练命令用Ultralytics的标准方式yolo detect train \ datadataset.yaml \ modelyolov8s.pt \ epochs100 \ batch8 \ imgsz640 \ patience20 \ device0 \ ampTrue训练完看result.png里的损失函数曲线图判断模型是否正常收敛。正常情况是train/box_loss和val/box_loss都趋势向下如果train_loss一直在降、val_loss降到一定程度开始反升那是过拟合回退epochs或者加强数据增强。2.4 密集场景的训练细节与超参数调整密集行人的核心痛点是遮挡和尺度变化NMS阈值要调整。默认的NMS IoU阈值是0.7在密集场景里偏高容易吞掉被遮挡目标的框。建议在导出和推理时把NMS阈值降到0.5左右置信度阈值压到0.1到0.2宁可多出几个IoU高的框也别漏检。后期叠加WBF后处理能将多个模型的预测结果加权融合密集场景的召回率能再提升2到3个点。另外训练时不要忽略负样本。密集行人画面里经常有路牌、树干、阴影这种人形干扰如果模型在验证集上误检很多补充一些空场景负样本能显著降低FPR。3. SpringBoot后端架构与接口设计3.1 整体架构Python推理服务与SpringBoot的边界划分这套系统最关键的一个设计决定不要用SpringBoot直接加载PyTorch模型做推理。JVM调用Python推理性能损耗大模型升级又要重新部署整个后端排错也麻烦。所以我把模型推理拆成独立的Python微服务用FastAPI提供HTTP接口SpringBoot只负责业务编排和数据管理。整体请求流程是这样的前端把视频帧或图片上传到SpringBootSpringBoot先把图片保存下来或直接转Base64然后调用Python推理服务的detect接口拿到检测结果再根据业务规则决定要不要调用千问或DeepSeek做语义分析。分析结果和检测结果一起写入数据库最后通过WebSocket推给前端刷新展示。这样拆的好处是模型服务可以在GPU服务器上单独部署SpringBoot只跑CPU两边互不拖后腿。业务逻辑改动不用重新打包模型服务模型版本迭代也不用碰业务代码。3.2 核心接口设计后端接口按业务模块拆没有必要搞特别复杂的RESTful规范清晰够用就行POST /api/detect上传图片或视频帧返回检测结果列表字段包括目标ID、类别、置信度、x、y、width、height。POST /api/analyze提交结构化检测结果触发大模型分析返回分析报告包含风险等级、场景描述、处理建议。POST /api/video/process提交视频文件或RTSP流地址启动异步抽帧检测任务。GET /api/records?page1size20分页查询历史检测记录。GET /api/stats/summary获取今日/本周/本月的人流量统计和告警趋势供前端图表展示。检测和视频处理都是耗时操作所以不能同步阻塞。我的做法是图片检测同步返回因为单帧推理大约200到500ms前端能接受视频处理启动异步任务后端立即返回taskId前端轮询任务状态或者通过WebSocket接收进度通知。这种设计在生产环境实测下来很稳不会出现请求超时崩溃的问题。3.3 与Python推理服务的通信策略SpringBoot与推理服务之间用HTTP通信虽然性能不是最优但胜在简单稳定、方便调试。推理服务的接口地址通过配置项维护模块间不硬编码。yolo: service-url: http://192.168.1.100:8000 timeout: 5000注意在SpringBoot里配置连接超时和读取超时用RestTemplate或者WebClient都行推荐WebClient做异步调用。这里有个实际心得别用默认的HTTP客户端超时时间推理服务和业务服务都容易在大并发下出现连接池耗尽。配置一个连接池最大连接数100每个路由最大连接50等待队列别设太长1000毫秒足够。3.4 数据库表设计与自动建表项目推荐用Spring Boot 3.x加MyBatis-Plus或者Spring Data JPA。表结构不用很复杂核心就是检测记录表和告警记录表。检测记录表存图片路径、检测时间、目标数量、检测结果的JSON快照告警记录表存告警类型、风险等级、分析报告文本、处理状态。如果你用的是JPA把ddl-auto设置为update表不存在的场景它会自动建表开发阶段很省事。生产环境建议换成Flyway做版本化迁移但别在主业务启动时自动执行DDL不然多实例部署会撞锁。API Key这类敏感配置不要直接明文写在application.yml里。可以用jasypt做配置加密或者用环境变量注入。我踩过直接把千问的Key提交到Git仓库的坑后来只能强制轮换从那以后所有Key都走环境变量。4. YOLO推理服务与模型部署实战4.1 模型导出从PyTorch到ONNX再到TensorRT直接用PyTorch做在线推理不靠谱内存占用高、推理慢、依赖重。正确姿势是先导出ONNX在GPU环境再转成TensorRT engine。TensorRT能针对你的显卡做算子优化实测推理速度比PyTorch快2到3倍。导出ONNX的命令yolo export modelyolov8s.pt formatonnx dynamicTrue simplifyTrue opset12如果部署目标是rk3588这类边缘盒子导出成rknn格式如果是Jetson设备直接整机刷JetPack装TensorRT如果只是纯CPU服务器ONNX加ONNXRuntime就够。TensorRT转换时注意把YOLO的NMS层也导进去这样输出直接是最终检测框。我在这里踩过坑第一次转TensorRT时没做NMS集成导致Python端还得自己写解码和NMS白白多了一层耗时。4.2 用FastAPI搭建推理服务Python推理服务不用Flask直接用FastAPI。原生异步支持、自动生成Swagger文档、性能更好前端联调时直接打开/docs看接口测试效率高很多。推理服务的接口逻辑很清晰app.post(/detect) async def detect(image: UploadFile, conf_thresh: float 0.25, iou_thresh: float 0.5): img read_image(await image.read()) results model(img, confconf_thresh, iouiou_thresh, verboseFalse) boxes results[0].boxes.data.cpu().numpy().tolist() return {boxes: boxes}注意并发控制。GPU显存是有限资源如果同时进来多个推理请求不能让它们抢显存。我用了一个信号量asyncio.Semaphore(1)把推理过程串行化配合队列等待。这样单卡不会因为并发导致OOM吞吐反而稳定。如果追求更高吞吐就上NVIDIA Triton做动态batch。4.3 视频流抽帧与队列缓冲视频处理是密集检测系统里最烦的部分。直接按30fps逐帧推理是不现实的一帧就要200到500ms处理速度远赶不上视频播放速度。我的做法是使用OpenCV或FFmpeg按需抽帧先解码然后按间隔抽取关键帧比如每秒2到4帧。抽出来的帧放入队列队列积压超过阈值时丢帧优先处理最新帧。这个策略保证前端看到的一定是接近实时的画面而不是越积越多的历史帧。检测结果按帧号和时间戳存储前端播放时跟视频时间轴对齐。实测下来100米的走廊摄像头密集时段每秒大约检出30到50人按每秒3帧抽帧单卡推理完全扛得住。5. 千问DeepSeek智能分析模块的实现5.1 为什么要让大模型介入分析流程YOLO检测出来的是一堆坐标和置信度它不知道有多少人正聚集在一起也判断不了可不可以通行。规则引擎当然能做写死一个阈值超过20人告警。这种方案太死板人一多但不是聚集比如大家只是排队也一样误报。接了千问和DeepSeek之后系统能把检测结果和场景语义一起交给大模型输出的是人的语言级别的分析。比如“该区域检测到45人密集程度较高有聚集风险建议限流并安排疏导”这就是产品级输出而不是一堆数字。5.2 检测结果如何转成模型输入大模型的输入必须是结构化文本不能让模型直接看图片除非用多模态版本。我的做法是把检测结果合并成一段紧凑的JSON结构把坐标转化为区域密度分布描述analysis_input { scene: 商场入口, timestamp: 2025-06-15 10:30:00, total_persons: 45, density: high, areas: { upper_left: 10, upper_right: 8, lower_left: 15, lower_right: 12 }, avg_confidence: 0.82 }Prompt设计决定输出质量这里分享一个我调试后的模板你是安全监控分析助手。给定行人检测统计数据请判断当前场景是否存在安全风险。 输出格式要求 1. 风险等级低/中/高 2. 风险描述不超过50字 3. 建议动作不超过30字 请基于以下数据回答{analysis_input}实际调用千问API时用Postman先调试接口确认请求和返回格式再往代码里搬。两家模型都兼容OpenAI接口格式封装基本一致client OpenAI(api_keyyour_api_key, base_urlhttps://dashscope.aliyuncs.com/compatible-mode/v1) resp client.chat.completions.create( modelqwen-plus, messages[{role: user, content: prompt}], temperature0.3, response_format{type: json_object} )DeepSeek的调用方式几乎一样换成对应的API地址和模型名就行。从这个角度看接大模型本身没什么难度真正的难点全在Prompt工程和结果容错处理上。5.3 调用策略、降级与缓存大模型调用不能逢帧必调否则延迟和成本都扛不住。我的经验是只有画面人流超过阈值或者区域密度分布出现明显的聚集趋势才触发分析调用。正常画面不调用模型产品体验反而更好。大模型的响应不稳定偶尔会超时、偶尔返回非JSON文本。所以在后端的逻辑里加降级策略千问调用失败自动尝试DeepSeekDeepSeek也失败就用规则引擎兜底至少能输出一个基于阈值的简单告警。分析结果缓存上同一机位在5分钟内不重复分析直接在Redis里取上次的分析结论。6. 前端交互界面与前后端分离集成6.1 前端技术栈与工程结构前端用了Vue 3加Vite加Element Plus加ECharts这套组合在管理后台类项目里是主流方案。Vite启动速度快Element Plus组件库很齐全ECharts画趋势图和人流变化曲线最省事。配合TypeScript接口数据结构清晰后续维护省心很多。工程结构大致是views目录里面放DetectionMonitor.vue、HistoryRecords.vue、DataStatistics.vue这类页面api目录封装所有后端接口调用store目录用Pinia管理登录状态和当前监控场景。6.2 视频检测画面与检测框叠加前端实时画面的实现方式不建议直接用video标签接RTSP流浏览器不支持。我的做法是后端按秒抽帧把帧编码成JPEG通过WebSocket推送前端在canvas上绘制图像再根据后端推送的检测坐标数据在canvas上叠加画框。大致的绘制逻辑是function drawBoxes(ctx: CanvasRenderingContext2D, boxes: Box[]) { boxes.forEach(box { ctx.strokeStyle rgba(255, 82, 82, 0.9); ctx.lineWidth 2; ctx.strokeRect(box.x, box.y, box.width, box.height); ctx.fillStyle rgba(255, 82, 82, 0.7); ctx.fillText(person ${(box.confidence * 100).toFixed(1)}%, box.x, box.y - 6); }); }注意这里存在一个坐标换算问题后端返回的坐标是相对于原图分辨率的前端canvas展示的尺寸往往跟原图不一致必须做等比缩放换算不然框就歪了。如果画面比例不同不要拉伸铺满canvas而要用contain方式居中留黑边。6.3 统计面板与实时告警卡片我有两个页面布局建议。第一是单场景监控页左侧视频区域右侧悬浮告警列表和实时人流数底部是近30分钟人流趋势折线图。第二是历史记录页表格展示检测记录、告警等级、分析文本支持按时间范围筛选点击记录能回看当时的检测截图和框选结果。这套页面的关键不在炫技而在信息呈现效率。一个操作员看监控页最需要的是第一时间知道“哪里出事了”所以告警卡片要醒目用颜色区分等级高优先级置顶配合音频提示。6.4 Nginx部署与前后端联调前端npm run build之后生成静态文件由Nginx托管后端接口通过Nginx反向代理转发。这里有一个需要注意的点前端请求后端时不能用绝对地址写死要用相对路径/api由Nginx统一转发到SpringBoot服务这样迁移服务器或换端口时只改Nginx配置。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_read_timeout 60s; }WebSocket的代理要单独配置Upgrade头不然实时推送连不上location /ws { proxy_pass http://127.0.0.1:8080/ws; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; }如果用云服务器部署记得安全组放行80/443端口数据库不要暴露公网只允许内网访问。这是我一直强调的底线后端和数据库部署在同一台内网环境公网只暴露Nginx一个入口。7. 常见问题与排查技巧实录7.1 YOLO训练阶段问题原因解决方案显存不足batch size或imgsz过大batch降到4imgsz降到640或512开amp密集场景大量漏检NMS阈值过高推理时降低iou_thresh到0.5提升recall小目标完全检测不到输入尺寸太小imgsz提高到960或加入VisDrone数据混合训练训练震荡不收敛学习率过高lr0降到0.001开启warmup7.2 SpringBoot调用推理服务超时这是我在最初联调时最崩溃的问题。排查思路是先确认Python推理服务本身响应时间用curl直接调detect接口看耗时如果接口本身很慢优化模型和GPU推理如果接口快但SpringBoot调用慢大概率是HTTP客户端配置问题检查WebClient的连接池和超时设置。另外一个坑是SpringBoot默认的Tomcat最大线程数是200如果前端频繁轮询任务状态会把线程池打满。我的做法是给轮询接口也走WebClient异步化并且把轮询间隔设置为2秒别用500毫秒这种短间隔。7.3 大模型输出不稳定大模型生成JSON偶尔会夹带多余的描述文字导致后端解析失败。我的兜底方案是先尝试JONS解析失败后用正则提取JSON片段再失败就直接返回规则引擎的兜底结果。Prompt里强制要求response_format为json_object千问和DeepSeek现在都支持这个参数能极大提高解析成功率。7.4 视频流卡顿与延迟后端抽帧处理压力大时前端画面会明显延迟。解决思路不是无限加CPU而是降采样和丢帧策略。每秒钟抽2帧每帧推理时间控制在300毫秒以内就算一帧卡住下一帧最多等半秒。这里我强烈建议开启推理服务的内置监控路由记录请求耗时和排队长度方便定位瓶颈。7.5 前后端联调时接口参数对不上接口联调最浪费时间的问题就是字段命名不一致。后端返回confidence前端写成score一个字段查半天。我现在所有项目都在前端定义TypeScript Interface规范接口数据后端用OpenAPI/Swagger导出接口文档两边以文档为准。最后分享两个小技巧第一模型服务和后端服务都做成Docker镜像部署时用Docker Compose一键拉起整套环境。包括MySQL、Redis、SpringBoot、推理服务、Nginx五个容器写在一个compose文件里到哪台服务器都能快速复现。这套操作做完之后项目交付成本降低非常多。第二大模型不是用得越勤越好它适合分析而不适合计数。计数这种活交给YOLO统计图表交给ECharts只有“用自然语言描述发生了什么”才交给千问和DeepSeek。在真实系统里越清晰地划分职责边界就越不容易出现不可控的级联故障。这套系统做完之后我最大的体会是目标检测只是整个链路里最成熟的一环真正决定一个项目能不能交付的是工程化的组织能力——数据、模型、后端、大模型、前端的协作关系。每个环节都选对了工具再把接口约定搞清楚一个看起来复杂的系统也就稳稳落地了。