首页 / 资讯中心 / 文章详情

基于YOLO与SpringBoot的疲劳驾驶检测系统:从模型训练到前后端部署全解析

基于YOLO与SpringBoot的疲劳驾驶检测系统:从模型训练到前后端部署全解析 ★ FEATURED ARTICLE
做一个能真正落地用的疲劳驾驶识别系统比想象中要折腾得多。这个项目是我自己从模型选型到前后端部署完整跑了一遍的核心就是用 YOLOv8/v10/v11/v12 这几个版本的检测模型做眼睛、嘴巴、头部的状态识别后端用 SpringBoot 搭一套前后端分离的 Web 服务前端提供实时监控和交互界面最后再接一个大模型接口对连续帧检测结果做更聪明的综合判断。整个过程踩了不少坑也摸出了一些特别值得分享的经验。这篇文章就围绕这个系统的完整实现路径来写从模型怎么选、数据怎么标、服务怎么拆到部署调试、常见问题排查一次性讲清楚适合想自己动手做一遍目标检测Web应用的同学参考。1. 项目背景与整体设计思路1.1 为什么疲劳驾驶检测值得用 YOLO 系列来做疲劳驾驶识别的核心任务是判断驾驶员的眼睛是否闭合、嘴巴是否打哈欠、头部是否频繁点头或倾斜。这些行为在视频里其实是典型的“小目标”和“局部遮挡”问题但好在检测对象相对固定只需要在驾驶员面部区域做精细分析。传统的图像处理方法比如人脸关键点EAR计算也能做但鲁棒性不够好一旦驾驶员戴墨镜、歪头、光照变化剧烈关键点就容易飘。换成目标检测模型之后可以直接把“闭眼”“张嘴”等作为一个类别框出来模型自己学习特征视觉干扰的容忍度高很多。YOLO 系列在这里的优势很明确单阶段检测、速度快、工程化成熟。从 v8 到 v12模型结构一直在优化但共同点是部署方便从训练到导出 ONNX 再到用 TensorRT 加速链路很顺畅。对一个需要实时处理摄像头视频流的系统来说检测延迟每毫秒都很宝贵YOLO 在这方面的表现比两阶段模型更适合实际场景。1.2 前后端分离和 SpringBoot 的定位为什么后端要用 SpringBoot原因是我自己最熟的 Java 技术栈是一方面另一方面是 SpringBoot 在业务接口、权限控制、定时任务、数据库操作这些方面生态太完善了。疲劳驾驶系统除了要处理模型推理还需要管理用户、设备、报警记录、历史查询这些工程化能力用 SpringBoot 写起来效率很高。而模型推理部分则是典型的 Python 强项。用 Python 加载 PyTorch 模型、处理图像、调用 GPU 加速比在 Java 里折腾 JNI 和 PyTorch 的 Java API 要省事得多。所以最好的架构就是把系统拆成“SpringBoot 业务服务 Python 推理服务”两个独立进程中间用 HTTP 通信。前端完全独立部署通过 RESTful API 和 WebSocket 跟后端交互这就是典型的前后端分离。这套结构的好处是每一层都能单独扩容比如摄像头多了推理服务可以多起几个实例业务层完全不用动。1.3 系统整体架构与数据流向整体流程可以概括成一句话视频流进前端前端抽帧后发给后端后端再转发给推理服务做检测检测结果回到后端记录同时大模型综合分析结果给出疲劳等级最终推送到前端大屏展示和报警。在物理部署上Python 推理服务和 SpringBoot 服务可以放在同一台 GPU 服务器上也可以分开。前端页面用 Nginx 托管访问后端 API。视频流的话既可以前端直接访问摄像头 RTSP 流也可以后端的 WebSocket 主动推送处理后的结果帧。架构里比较关键的是数据协议。检测结果要包含目标类别闭眼、打哈欠、低头、正常、边界框坐标、置信度、时间戳、驾驶员编号。这些信息经过后端结构化以后一部分存入数据库一部分推送给前端绘制框另一部分喂给大模型做语义分析。设计好这个数据模型后面每一步都会顺很多。2. 模型选型与训练要点2.1 YOLOv8/v10/v11/v12 的核心差异对比很多第一次做选型的人会纠结到底用哪个版本我的建议是先别盯着“最新”两个字要看自己的硬件条件和场景要求。四代模型我都实际训练过总结起来各有脾气版本核心特点推理速度GPU小目标效果易用性/生态YOLOv8anchor-free结构成熟官方支持全流程快中上极高文档多社区问题基本都能搜到YOLOv10去掉 NMS端到端部署更简洁更快中等相对较高但需要留意输出格式变化YOLOv11改进 Backbone增强特征提取mAP 有提升和 v8 接近更好较高训练接口延续 v8 习惯YOLOv12加入注意力机制小目标更友好略慢于 v10最好相对较低部分训练细节需要自己调实际对比下来v8 最适合当基线模型因为训练参数大家都熟踩坑成本低v10 因为去掉了 NMS确有速度优势但部署时输出层变了解析要跟着改v11 在 mAP 上比 v8 有小幅提升算是不错的中期选择v12 在闭眼这类小目标上提升最明显但 GPU 稍微差一点的话速度下降也明显。所以我的选型逻辑是如果算力充足、要求精度最高用 v12如果要在老显卡或者 CPU 上跑用 v8 最稳。2.2 疲劳驾驶数据集构建与标注规范训练模型的根基是数据。闭环的数据集至少要有四类正常睁眼、闭眼、打哈欠张嘴、低头/左顾右盼。有人会想用公开数据集但疲劳驾驶场景非常特殊不同摄像头安装角度、车内光照、驾驶员脸型差异都很大公开数据很难完全覆盖。我自己是采用“公开数据集模拟驾驶环境自采”的方式在模型车和实验室里架摄像头找几个人分别模拟正常驾驶和疲劳状态录了上百段视频再抽帧筛选。标注的时候特别注意三点。第一闭眼和半闭眼要区分开或者在标注时统一成“闭眼”但边界要清晰眼睛只剩一条缝时算闭眼还是睁眼最好在标注规范里明确。第二打哈欠时嘴巴张开的程度也要定义好张嘴超过一定阈值才算正样本。第三样本均衡很关键正常睁眼的帧数会远多于闭眼和打哈欠需要做欠采样或数据增强。我用的是某标注工具导出 YOLO 格式的 txt 文件每行是类别 x_center y_center width height。2.3 训练配置与调参经验训练 YOLO 的配置有很多可调项但核心就几个输入分辨率、batch size、epochs、优化器、数据增强策略。输入分辨率我建议用 640 起步如果闭眼检测精度不够再尝试 960。分辨率提高能明显改善小目标检测但训练和推理速度都会下降。batch size 受显存限制我实测 8GB 显存下用 640 分辨率batch 8 差不多再大就 OOM。epochs 一般 100 到 200配合早停机制看验证集 mAP 变化。优化器直接用默认的 SGD 或 AdamW 都行关键是学习率策略。数据增强里最有用的几个是随机亮度对比度模拟光线变化、随机旋转和透视模拟头部姿态变化、随机裁剪模拟摄像头角度差异。但注意不要增强过头比如翻转就只做水平翻转垂直翻转会让脸变成倒着的真实场景不可能出现。训练完成后先用测试集看各类别的 mAP。如果闭眼类别 mAP 明显低优先补闭眼样本而不是盲目加训练轮数。我遇到过闭眼和正常睁眼的 mAP 相差十几个点补了一批闭眼数据后效果立竿见影。2.4 模型导出与转换细节训练出的 PyTorch 模型权重不能直接塞给 Java 或者 Python 推理服务使用通常要转换成 ONNX 再转成推理引擎格式。如果部署在 GPU 上转 TensorRT 是首选如果只有 CPU转 OpenVINO 更合适。导出 ONNX 时要注意几个坑。第一YOLO 模型的输出形状是[batch, 4 classes, grid_h * grid_w]或类似不同版本结构不同导出后需要写后处理逻辑。第二opset版本尽量选高一点否则某些算子会丢失。第三输入动态维度建议关闭固定为 640x640能提升 TensorRT 转换成功率代价是送入模型的图像必须做 letterbox 缩放并记录缩放比例。我在导出 v10 的时候踩过一次坑因为 v10 去掉了 NMS输出层不同直接用 v8 的后处理解析会导致结果全是零。后来查看模型结构文档才明白需要根据输出 shape 判断特征图类别数再写对应的解码函数。3. 后端 SpringBoot 服务与模型推理集成3.1 项目模块划分与工程结构整个后端工程我分成了三个 Maven 模块server-adminSpringBoot 主服务负责鉴权、设备管理、报警存储、接口封装。detect-client封装对 Python 推理服务的 HTTP 客户端提供统一的检测请求方法。common存放公共实体、DTO、常量。独立推理服务是一个 FastAPI 应用单独一个项目仓库。用 FastAPI 而不是 Flask是因为它有类型校验、自动文档、异步支持接口对接起来体验更好。为什么推理服务一定要独立除了技术栈差异还因为模型推理是 CPU/GPU 密集型操作如果和 SpringBoot 跑在同一个 JVM 里一旦检测请求并发上来JVM 的 GC 和 GPU 争用会让系统抖动。分开之后SpringBoot 只做业务编排Python 服务只做推理职责清晰也方便横向扩容。3.2 推理服务接口设计Python 推理服务暴露的接口要设计得简单而稳定。我常用的是POST /detect Content-Type: multipart/form-data file: 图像文件 params: - driver_id: 驾驶员编号 返回 { code: 0, data: { boxes: [ {class: 1, class_name: closed_eye, confidence: 0.92, x1: 120, y1: 80, x2: 180, y2: 140} ], image_width: 640, image_height: 640, inference_ms: 28 } }接口内部先做图像解码再做 letterbox 缩放转为张量推理后解析输出最后把检测框坐标还原到原始图像坐标系。这里要特别小心坐标还原很多人在前端 canvas 上画框偏移就是因为忘了按缩放比例换算。除了单帧检测推理服务还要支持连续帧序列检测或者配合视频流抽帧。因为 SpringBoot 每次转发一张图的话网络开销比较大。更高效的方式是推理服务内部维护一个消息队列SpringBoot 只把视频帧通过 WebSocket 推送到推理服务由推理服务批量处理再把结果批量返回。不过我用的实现比较简单HTTP 单次请求也能满足 10 路摄像头的需求关键看帧率限制。3.3 SpringBoot 核心业务实现SpringBoot 这边的接口设计主要围绕这个几个场景用户登录和权限管理JWT 令牌角色包括管理员、监控员。设备管理增删改查摄像头每个摄像头绑定一个驾驶员或车辆。检测请求接收前端上传的图像调用推理服务拿到结果后保存到数据库并根据规则判断是否触发报警。报警记录查询按时间、人员、设备筛选历史记录。WebSocket 推送检测结果实时推送到前端。调用 Python 推理服务的代码我用的是 Spring 的RestTemplate现在也可以用WebClient但RestTemplate足够稳定。注意超时时间一定要设检测接口如果 GPU 排队可能耗时超过 5 秒。我设置的是连接超时2秒、读取超时10秒同时用线程池隔离避免一个慢请求拖垮整个 Web 服务。保存到数据库时除了保存检测框还要保存原始图片的 Base64 编码或者文件路径。这个很重要否则事后复盘报警记录时没有图像证据。数据库表设计至少包括detect_recordid, driver_id, device_id, timestamp, image_path, result_json, fatigue_level, status。3.4 前后端分离鉴权与会话设计前后端分离后传统的 Cookie Session 不再适用我用 JWT 做无状态鉴权。用户在登录接口换取 token后续请求在 Header 里带Authorization: Bearer token。SpringBoot 里用一个拦截器或 Spring Security 过滤器校验 token 即可。WebSocket 的鉴权要单独处理。因为 WebSocket 握手走的是 HTTP 协议升级可以在握手请求的 header 里带上 token在后端实现一个HandshakeInterceptor校验。我踩过一个坑前端用原生 WebSocket 时header 里不能随意自定义字段所以很多前端会选择把 token 放到 URL 查询参数里虽然不优雅但简单可行。生产环境建议用 STOMP 框架支持 CONNECT 帧的传参能正规一点。跨域配置也要做好。前端页面如果部署在http://localhost:8081后端 API 在http://localhost:8080那 SpringBoot 里要配置 CORS 允许指定源、指定请求头、指定方法否则浏览器会拦截所有 AJAX 请求。我习惯把允许的域名放到配置文件里方便切换环境。4. Web 交互界面与大模型智能分析集成4.1 前端框架与页面布局前端我用的技术栈是 Vue 3 Element Plus ECharts这套组合开发监控类界面效率很高。页面核心功能包括登录/权限控制。实时监控大屏左侧显示摄像头视频流中间用 canvas 覆盖显示检测框右侧是实时统计图表疲劳指数曲线、各类别检测计数。历史记录页面支持按时间范围查询报警记录回看抓拍图片和当时的检测结果。设备管理页面增删改查摄像头信息。大屏的视觉设计要点是信息密度高但不能乱。我习惯把屏幕划分成三栏中间大画布放视频流和检测框左侧放设备列表右侧放图表和实时报警列表。视频流用 H5 的 video 标签播放 RTSP 流时浏览器原生不支持 RTSP需要先经过后端转码成 WebRTC 或 HLS 流。更轻量的方案是前端通过 WebSocket 接收后端推送的 JPEG 帧流然后利用 canvas 逐帧绘制同时绘制检测框。这种方式延迟低实现也简单很适合内网局域网使用。4.2 实时视频流传输与处理这里有个取舍直接让前端拉摄像头 RTSP 流并解码对浏览器来说基本不可能。所以常见做法是后端用 FFmpeg 拉流并转成 MJPEG 或 HLS。我采用了 WebSocket JPEG 帧的推送方式。具体流程是SpringBoot 每 100ms 从摄像头拉一帧图像转成长度较小的 JPEG质量调成 70 左右通过 WebSocketBinaryMessage推送给前端。前端收到二进制数据后用Blob转成图片再画到 canvas 上。接下来这一帧也可以发给推理服务做检测检测框的坐标通过另一条 WebSocket 消息推送。前端收到检测结果后在 canvas 上重新绘制图像和框。这个方式的优点是实时性高能保持端到端延迟在 300ms 以内。缺点是对前端的绘图性能有一定要求如果一帧图像是 1280x720每秒20帧客户端如果性能不好会卡顿。优化的办法是只绘制检测区域的缩放图或者降低到15帧。4.3 大模型智能分析模块的设计与接入检测模型只能告诉你“这一帧里有哪些目标”但疲劳驾驶是一个时间维度上的判断。单帧眼睛闭合可能是因为眨眼几秒内频繁闭合才更有可能是疲劳。大模型的价值就在这里把一段时间内的检测序列拼接成自然语言提示让大模型做推理和总结。我接的大模型接口是一个通用的中文对话 API。接入之前要先把检测结果转成文本比如每 10 秒为一个窗口统计这个窗口内闭眼帧数占比、打哈欠次数、低头次数然后构造提示词你是驾驶安全分析助手。以下是一段10秒内的检测统计数据 闭眼帧占比15% 打哈欠次数2 低头次数1 平均置信度0.85 请判断驾驶员疲劳程度等级正常、轻度疲劳、中度疲劳、高度疲劳并给出理由和安全建议。用JSON输出。大模型返回的内容可以是 JSON 格式方便直接解析。我在提示词里明确要求“只输出JSON”并且配置了输出格式参数。如果用的是支持结构化输出的API也可以直接用 schema 约束。接入的关键细节是控制成本和延迟。每 10 秒调用一次大模型对一个摄像头来说一天有 8640 次调用费用和响应压力不小。我的优化策略是先用规则判断是否到了需要大模型介入的程度。比如如果闭眼占比小于 5%直接用规则结果返回“正常”不调用大模型只有当闭眼占比超过阈值或者短时内出现多次哈欠才触发大模型做深度分析。这样可以省下大量调用。另外大模型调用是 IO 密集型操作不能放在处理视频流的线程里否则会阻塞实时性。我用了一个异步任务池处理大模型请求完成后把结果写入数据库并通过 WebSocket 推送给前端。4.4 报警与数据可视化报警规则最常用的是 PERCLOS单位时间内眼睛闭合时间占比。行业里一般以闭眼帧数占总帧数的比例来判断比如连续 20 秒内闭眼比例超过 20%就认为是严重疲劳。基于检测结果我做了两层报警即时报警某帧检测到闭眼且置信度高触发提示。周期报警按时间窗口聚合模型输出结合大模型疲劳等级触发不同程度的声光报警。前端收到报警消息后页面顶部弹出红色告警条同时播放声音。ECharts 用来画疲劳指数曲线横轴是时间纵轴是每个时间窗口的综合疲劳分曲线平滑处理后会更有趋势感。安全提醒要注意报警信息不能只展示给监控员还要支持短信或企业微信消息推送。我的系统预留了消息推送接口目前用的是一个模拟的 HTTP 回调实际使用时可以对接第三方短信服务。5. 部署实施与性能优化5.1 Docker 化部署方案整套系统的部署我完全用 Docker Compose 编排包含五个容器容器镜像基础说明web-servereclipse-temurin:17-jreSpringBoot 服务端口 8080detect-serverpython:3.10-slimFastAPI 推理服务挂载模型文件GPU 时启用 nvidia runtimeweb-frontendnginx:alpine前端静态资源端口 80反向代理后端 APIdatabasemysql:8.0业务数据存储redisredis:7-alpine缓存、WebSocket 会话管理、任务队列如果推理服务需要用 GPUdocker-compose.yml里要指定gpus: all同时需要机器上已安装 NVIDIA Container Toolkit。我第一次部署时忘了加这个容器跑起来之后 Python 代码报找不到 CUDA排查了半天才发现是 runtime 没配置。Dockerfile 写 Python 推理服务时要注意减小体积只拷贝requirements.txt、模型文件和源码不要拷贝训练用的数据集。模型文件我直接打进镜像因为模型体积不会太大一般在几十 MB避免启动时去外部下载。5.2 性能瓶颈与调优系统上线后最直接的瓶颈往往是推理服务。检测一张 640x640 图像在 GTX 1660 上大约需要 20ms但在 CPU 上可能要 500ms 以上。如果所有摄像头都要实时检测必须对帧率做限制。我的做法是每个摄像头抽帧率限制为 5 FPS也就是每 200ms 处理一帧。对于有 10 个摄像头的情况总推理负载是每秒 50 帧GPU 利用率大约在 60%~80%。这个抽帧率对疲劳检测完全够用闭眼动作持续至少 300ms5 FPS 能捕获到关键状态。推理服务内部也用上了队列。FastAPI 是异步框架但模型推理是 CPU/GPU 同步操作并发请求会排队。我用了asyncio的 Lock 限制同时只能有一个推理任务否则显存会爆。如果要提升并发可以起多个推理进程每个进程各自加载模型再用 Redis 做任务分发。网络传输也是隐性瓶颈。前端上传原始图像再返回检测结果图像体积会直接影响延迟。我在推理服务里加了图像压缩逻辑先按比例缩小到最长边 800px再转成 JPEG quality80这样单张图不超过 100KB走内网非常快。5.3 合规与隐私保护疲劳驾驶系统会持续采集驾驶员面部图像属于敏感个人信息落地时必须做隐私保护。我的方案是所有图像数据默认不保留原始视频流只保留触发报警前后的关键帧。数据库中的图像路径不能包含人员姓名等直接标识信息用随机 UUID 命名。摄像头采集区域要避开无关路人。系统权限要分级只有授权管理员才能查看带有面部的抓拍图。这些都是经过实际项目验证的底线尤其在企业内部项目里数据合规问题不是技术问题是能不能上线的问题。即使技术实现再完善隐私保护不到位也会被叫停。6. 踩坑记录与问题排查6.1 常见问题速查表我把这大半年开发维护中遇到的典型问题整理成了一张表遇到类似情况可以先对着查现象可能原因解决方案前端检测框位置错位letterbox 缩放后没有还原坐标原图在推理服务统一负责坐标还原前端直接用原始坐标WebSocket 频繁断开代理服务器对空闲连接超时前端每 30 秒发心跳包后端设置超过 60 秒无消息自动断开推理服务显存泄漏推理输入张量没有释放使用torch.no_grad()并在每次推理后主动清理变量大模型返回内容解析失败提示词没有严格要求 JSON 格式增加输出格式约束并做异常重试与默认值回退第一次请求特别慢模型和 CUDA 上下文初始化在服务启动时执行一次预热推理把 CUDA 模块加载好摄像头 RTSP 流断流网络抖动或摄像头连接数限制后端增加自动重连机制启动守护线程定期检查流状态数据库记录增长过快每帧都保存检测结果只保存报警帧和周期统计结果单帧正常状态不落库6.2 推理精度与速度权衡心得我在实际使用中最大的体会是不要盲目追求模型最强。v12 在闭眼检测精度上确实好一些但如果目标是做到 5 路以上摄像头并发实时检测v8 的推理速度优势更能保证体验。后来我把生产环境稳定在 YOLOv8s 上闭眼检测的 mAP 达到了 0.895 FPS 下延迟 30ms 左右完全够用。v10 和 v12 还是要留在实验环境里对比着玩。v10 的无 NMS 设计确实让推理延后处理简单了很多但官方权重在闭眼这个小类别上表现略逊于 v11。v12 的提升需要更大的训练数据量才能发挥出来如果只是几百张标注图反而容易过拟合。模型蒸馏也是个可以考虑的方向。用 v12 作为教师模型把知识蒸馏给 v8s 的学生模型可以在精度和速度之间找到一个更好的平衡点。我做过一次蒸馏实验v8s 的 mAP 提升了大约 3%基本接近 v11 的效果但速度没有牺牲太多。这种方案适合有一定调优时间的团队。6.3 实用的调试技巧调试这类多服务系统最怕的就是问题出现后难以定位。我养成了一套自己的调试习惯分享出来很有用。日志必须全链路带 traceId。在 SpringBoot 里用拦截器生成一个 traceId放入 MDC然后在调用 Python 推理服务时把这个 traceId 放在 HTTP Header 里传给 FastAPIFastAPI 的日志同样打印这个 traceId。这样不管是前端报错还是后端报错只要拿着 traceId 就能把整个请求链路串起来看。离线回放功能一定要做。我写了一个模拟推送脚本可以从本地视频文件读取帧按照时间间隔把帧发送给 SpringBoot 服务。这样不用天天坐在车里测试只要拿着录制好的疲劳驾驶视频就能反复验证检测逻辑和报警规则。压测别忽略大模型环节。很多人压测时只关注检测接口忽略了前端不断调用大模型分析接口的并发。我用 JMeter 模拟多个设备同时触发疲劳分析结果发现线程池满了导致接口超时。后来把线程池参数调大并且增加了一个排队缓冲问题就解决了。最后再分享一点个人体会这套系统做完之后我最意外的不是模型精度而是工程整合要花的时间远超训练模型。模型训练可能只占三成工作量剩下七成都在做前后端联调、接口协议对齐、异常处理、部署上线。如果你也想从零做一套类似的项目建议先定好接口协议和数据结构再动手实现各个模块。还有一个深坑是不同 YOLO 版本的输出格式看起来差不多实际上差别不小换模型版本时后处理代码一定要写单元测试否则上线那天绝对会踩雷。希望这篇文章能帮你绕开我走过的弯路。
阅读完成 · 觉得有帮助?
咨询建站