ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

YOLOv5安全帽检测系统:从模型训练到RTSP实时视频流部署

YOLOv5安全帽检测系统:从模型训练到RTSP实时视频流部署 简介基于Python与YOLOv5算法打造的安全帽佩戴及危险区域实时检测毕业设计项目面向计算机、通信、人工智能、自动化等专业学生与从业者可服务于毕业设计、课程设计或工程入门。项目已接通海康摄像头实现实时视频流检测覆盖模型训练、数据配置、界面可视化与区域危险判断等完整流程代码经过调试测试具备较高复用与二次开发价值。压缩包共63个文件总大小28.61MB包含21个Python源码文件、9个YAML配置与模型结构文件、若干JPG/PNG/GIF演示素材以及UI界面文件、Markdown文档说明和requirements依赖清单方便快速搭建环境并对照学习。配套有训练好的模型权重、可视化工具教程、测试输出图和视频示例可直观查看检测效果。当前已有193人学习下载适合零基础入门或需要在现有框架上做功能扩展的开发者。1. 一套能毕业设计答辩的实时检测系统难点不在模型用 YOLOv5 做安全帽检测早已不是新鲜事真正让毕设卡壳的是把训练好的模型和硬件环境串成一套“实时系统”。多数人拿到开源权重跑几张测试图片没问题一旦接上海康摄像头的 RTSP 视频流要么画面卡死要么检测框延迟两三秒要么 GPU 显存直接被撑爆。标题里“源码 文档 训练好的模型”这套组合本质上是要求你交付一个开箱即用的工程而不是一个 notebook 演示。这意味着三件事必须同时成立模型能在你的机器上快速跑通推理、海康摄像头的取流地址和参数设置正确、危险区域判定逻辑能稳定地叠加在检测结果上。适合拿这套方案做毕设的人是已经装好 Python 但没碰过 YOLOv5 的、手里有海康摄像头但不知道 RTSP 怎么取的、以及训练完模型却不知道如何部署到实时视频流上的。2. YOLOv5 的检测流程与安全帽识别的最小闭环2.1 YOLOv5 网络结构里的三个关键部件决定了你的训练效果YOLOv5 的检测流程分三段Backbone 负责提取特征Neck 负责融合不同尺度的特征图Head 负责输出预测框。对于安全帽检测这种目标尺寸不大、场景相对固定的任务真正值得关注的是 Neck 部分。C3 模块是 YOLOv5 的基本构件它借鉴了 CSPNet 的思路将输入特征图分成两个分支一条分支经过一系列 Bottleneck 操作另一条分支直接连接最后再拼接起来。这个设计减少了计算量同时保持了梯度信息的丰富程度。训练自己数据集的时候C3 模块里的 Bottleneck 数量由depth_multiple这个超参数控制。SPPF 是空间金字塔池化的快速版本它通过串联多个最大池化层来扩大感受野让模型能够同时看到局部细节和全局上下文。检测安全帽这种小尺寸目标时SPPF 的输出会直接送入 Head 的浅层特征图所以它的作用不可替代。Head 部分使用锚框机制在 COCO 数据集上默认的锚框尺寸对于安全帽这种目标可能偏大。你在训练自定义数据集时需要让 YOLOv5 自动计算适合你数据集的锚框尺寸后面会具体讲。2.2 从零准备安全帽数据集到模型训练的最小命令集先明确一个概念YOLOv5 的数据集格式与 COCO 不同它使用的是 YOLO 格式的标注文件。每张图片对应一个同名的.txt文件文本行的格式为class x_center y_center width height其中坐标值都是归一化后的浮点数取值范围 0 到 1。假设你的数据集目录结构如下helmet_dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yamldata.yaml的定义方式如下以我常用的/home/user/helmet_dataset路径为例train: /home/user/helmet_dataset/images/train val: /home/user/helmet_dataset/images/val nc: 2 names: [helmet, head]这里的nc是类别数量我用的训练配置是两类戴了安全帽的helmet和未佩戴的head。如果你还想检测危险区域闯入可以在names里追加类别比如[helmet, head, person, area]。标注工具方面常见的做法是用 LabelImg 或 LabelStudio导出格式直接选 YOLO 即可。如果你拿到的数据集是 VOC 格式的 XML 标注可以用 YOLOv5 仓库里的转换脚本处理。训练命令的最小可运行版本如下cd yolov5 python train.py --data /home/user/helmet_dataset/data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 100 \ --device 0参数说明--img 640表示训练时输入图片的尺寸推理时也建议保持相同的尺寸否则检测框位置会偏移。--batch-size取决于 GPU 显存大小我常在 8GB 显存的卡上使用 16如果在 CPU 上训练建议改成 4 或直接不写让脚本自动尝试。--weights yolov5s.pt是 COCO 预训练权重在自定义数据集上做迁移学习能大幅缩短训练时间。如果数据集很小少于 1000 张建议把--freeze-layers 10加到命令里冻结 Backbone 层只训练 Head 部分。训练完成后会在runs/train/exp/weights/目录下生成best.pt和last.pt。验证模型效果时使用python val.py --data /home/user/helmet_dataset/data.yaml \ --weights runs/train/exp/weights/best.pt \ --task val检测结果的 mAP 通常在验证集上能到 0.95 以上这个数值在安全帽这种目标类别少、外观差异不大的任务里算是正常水平。3. 海康摄像头实时取流的 RTSP 配置与 YOLOv5 推理对接3.1 每一步都在解决“实时”的瓶颈而你距离实线只差一段代码海康摄像头的视频流通过 RTSP 协议传输默认推流地址遵循固定格式但你第一次拿到的地址往往打不开原因是摄像头的用户名、密码、IP 或子码流参数不一致。一个最基础的海康 RTSP 地址格式如下rtsp://username:password192.168.1.64:554/Streaming/Channels/101URL 结尾的101代表主码流通道 1102代表子码流通道 1。对于实时检测场景我用的是子码流因为主码流分辨率通常是 2560x1440直接喂给 YOLOv5 做逐帧推理会占用大量 CPU 解码资源和显存。子码流分辨率一般为 640x480 或 704x576对安全帽检测来说完全够用。如果地址报错优先检查以下几点密码中是否含有特殊字符。RTSP URL 里的密码如果有或:等字符需要做 URL 编码比如要写成%40否则地址解析会截断出错。端口是否为 554。有些摄像头默认 HTTP 端口是 80但 RTSP 端口固定是 554不要混淆。在 VLC 播放器里先试一下这个地址能打开排查摄像头本身的异常。参数主码流子码流我的选择分辨率2560x1440640x480子码流帧率25fps12fps保持默认带宽占用~4Mbps~0.5Mbps子码流适用任务录像存储实时检测推荐检测3.2 用 OpenCV 的 VideoCapture 解码 RTSP 流并逐帧推理OpenCV 处理 RTSP 流的方式很简单但有两个坑一是默认的缓冲区会导致画面延迟越来越高二是read()方法阻塞时间不稳定。我习惯在捕获到帧后先清空缓冲区再通过判断关键帧条件来决定是否执行推理import cv2 import torch # 加载训练完成的模型 model torch.hub.load(./yolov5, custom, pathruns/train/exp/weights/best.pt, sourcelocal) model.conf 0.5 # 置信度阈值 model.iou 0.45 # NMS 的 IoU 阈值 # RTSP 地址使用子码流 rtsp_url rtsp://admin:password192.168.1.64:554/Streaming/Channels/102 cap cv2.VideoCapture(rtsp_url) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) while True: ret, frame cap.read() if not ret: continue # 关键帧推理每 3 帧处理一次减轻 GPU 负载 if cv2.waitKey(1) 0xFF ord( ): # 推理并画框 results model(frame, size640) rendered results.render()[0] cv2.imshow(helmet_detection, rendered) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()代码里的torch.hub.load会在第一次执行时加载模型结构sourcelocal表示从本地目录读取 YOLOv5 代码不要每次都从远程拉取。model.conf和model.iou是推理时的两个核心阈值参数直接影响误报和漏报比例conf降至 0.3 左右可以召回更多小目标但代价是出现更多误检框。iou越大重叠的检测框越容易被合并一般保持默认即可。键盘等待函数waitKey(1)同时起到了控制帧率的作用它可以让你看到推理画面的实时效果但无法解决网络延迟本身的问题。注意如果画面出现明显卡顿先观察摄像头子码流的实际帧率不必强求在屏幕上达到 25fps实时检测的“实时”通常指单帧推理耗时小于 200 毫秒。4. 危险区域检测的坐标判定与规则叠加4.1 从像素坐标到实际空间位置的映射危险区域的两种定义方式危险区域可以有两种理解一是画面中像素级别的固定矩形或多边形区域二是基于测距或坐标转换后的物理区域。对毕设系统来说像素级别的区域判定实现简单、效果直观是常见的做法。定义危险区域最直接的方式是让用户在画面中用鼠标框选一个多边形将顶点坐标保存下来。OpenCV 的cv2.selectROI只能选矩形若需要任意多边形通常用cv2.setMouseCallback配合鼠标事件实现。另一种方式是读取配置文件里的区域顶点比如使用 JSON 文件保存坐标。判定逻辑用 OpenCV 内置的cv2.pointPolygonTest函数它能判断某个点是否在一个多边形内也支持返回点到边界的距离。4.2 把检测框与危险区域结合用一段代码让安全帽和区域同时生效安全帽检测输出的是每个目标的边界框(x1, y1, x2, y2, conf, class)。判断是否闯入危险区域的位置参考点我一般取边界框底部中心点原因是人在站立时脚部所在的平面更接近真实的地面投影位置。头部被检测到时底部中心点其实对应的是肩膀位置对安全帽检测场景来说仍然可接受。import cv2 import numpy as np # 危险区域顶点坐标像素按顺序连接成多边形 danger_zone np.array([[200, 400], [600, 500], [450, 120], [100, 300]], dtypenp.int32) danger_zone_pts danger_zone.reshape(-1, 2) def is_point_in_polygon(point, polygon): # point: (x, y)polygon: Nx2 的数组 result cv2.pointPolygonTest(polygon, point, False) return result 0 # 对每一帧的检测结果做区域判断 for *xyxy, conf, cls in results.xyxy[0].cpu().numpy(): x1, y1, x2, y2 map(int, xyxy) bottom_center ((x1 x2) // 2, y2) # 未戴安全帽且进入危险区域触发报警 if int(cls) 1: # head 类 if is_point_in_polygon(bottom_center, danger_zone_pts): cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(frame, DANGER!, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)pointPolygonTest的第三个参数传False时返回三种值正数表示点在多边形内部负数表示在外部0 表示在边界上。所以判断条件result 0能保证边界上的情况也被算作闯入。危险区域报警往往要做连续帧确认。我通常的做法是维护一个字典键是目标 ID值是连续判定为闯入的帧数计数当计数超过 5 帧时才确认报警。这样可以避免人员站在区域边缘时因为检测框轻微抖动带来的误报。代码实现只是简单的计数逻辑关键在于这个思路在实际场景中对误报率的压制效果非常明显。4.3 三条能直接抄走的区域判定优化经验避开坐标错位问题第一RTSP 流经过解码后frame的分辨率可能和你标注区域时不一致。如果之前用截图工具标注的坐标是在 1920x1080 分辨率的图上做的而实际推理帧是 640x480坐标必须做等比缩放否则区域位置会偏差很大。第二把区域顶点和画面缩放做绑定不要直接写绝对像素值。建议先用cap.get(cv2.CAP_PROP_FRAME_WIDTH)和CAP_PROP_FRAME_HEIGHT获取实际画面尺寸再根据该尺寸按比例设置区域。这样摄像头分辨率调整后区域会自动适配。第三YOLOv5 推理后的坐标始终对应输入帧的分辨率。如果你把size参数设为 640而输入帧是 1280x720模型会在内部做缩放并回映射坐标返回的坐标已经对齐原图不需要自己换算。注意不要对results.xyxy[0]做额外的尺寸变换这是新手最容易犯的错误。5. 发现问题从哪查起部署后的验证与坑位排查5.1 一个命令同时验证模型、摄像头和整体流程的可用性完成整个系统后先用一段命令同时验证摄像头和模型是否各自正常。我在调试时习惯先跑摄像头裸流ffprobe -rtsp_transport tcp -i rtsp://admin:password192.168.1.64:554/Streaming/Channels/102ffprobe会输出视频流的编码格式、分辨率、帧率等信息。如果能正确打印出这些参数说明网络和摄像头取流正常如果卡在Opening rtsp://...阶段超过 5 秒基本可以判定为网络不通或者 RTSP 地址错误。接着再用单张图片验证模型推理本身没有问题python detect.py --weights runs/train/exp/weights/best.pt \ --source /path/to/test_image.jpg \ --img 640 \ --conf 0.5看到图片上正确画出检测框后再回到摄像头实时检测流程。这两步拆分能迅速定位问题所在不会浪费时间去排查一个本来就没问题的环节。实际运行中你还会遇到一个常见情况摄像头画面出来了但检测框没有实时更新或者画面一直卡在第一帧。这时核心排查点是 OpenCV 的后端解码可能使用了FFMPEG而在某些系统上CAP_PROP_BUFFERSIZE需要重新设置才会生效。5.2 三个最容易踩的暗坑与已有解决方案第一个暗坑是 CPU 推理速度严重不足。如果你的机器没有独立显卡YOLOv5s 在纯 CPU 模式下推理一张 640x640 的图片约需要 0.5 到 1 秒无法达到实时效果。解决方案有很多最有效的是把输入尺寸降到 416同时改用子码流并将摄像头帧率限制在 12fps。这能以略微降低小目标召回率为代价换来推理速度提升。如果仍然不够可以考虑在yolov5/models/yolo.py里把检测层部分通道裁剪但毕设阶段不建议动模型结构维护成本太高。第二个暗坑是torch.hub.load每次都去下载依赖。断网环境下会直接失败解决方案是提前克隆 YOLOv5 仓库git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt之后加载时用sourcelocal跳过拉取步骤。第三个暗坑是多个摄像头同时取流时线程模式下的延迟累计。OpenCVVideoCapture默认是单线程阻塞读取两个摄像头就需要两个线程各自维护一个cap实例不能共用一个对象。跨线程读取时注意把cap的读取循环和推理推理逻辑分离避免 GIL 锁导致性能进一步下降。5.3 答辩演示时让系统处于稳定状态的几项准备答辩现场最怕演示翻车。我建议你把推理脚本封装成一个带--source参数的命令行入口这样既可以用摄像头实时画面演示也可以随时切换成视频文件回放。一旦 RTSP 现场网络不稳定立即切换成本地视频保证演示流程不断。为了让画面效果更有说服力可以提前准备好一段包含安全帽佩戴人员和未佩戴人员行走到危险区域的测试视频。演示时使用本地视频文件一方面规避网络抖动另一方面你可以控制视频内容确保出现预期的检测结果。注意RTSP 连接断掉不会自动重连。如果你的系统需要长时间运行务必在read()返回False后增加重连逻辑比如尝试重新初始化VideoCapture并加上指数退避重试避免死循环。5.4 保存检测结果与输出日志答辩之后通常需要实验数据支撑可以在推理循环里加入结果保存逻辑。把每帧的检测结果转成 CSV 记录每行包含时间戳、类别、置信度、区域判定结果。OpenCV 同时可以保存带标注的视频流方便截取演示片段。import csv import time csv_file open(detection_log.csv, w, newline) writer csv.writer(csv_file) writer.writerow([timestamp, class, confidence, in_danger_zone]) # 在检测循环内部写入 for *xyxy, conf, cls in results.xyxy[0].cpu().numpy(): in_zone is_point_in_polygon(((x1 x2) // 2, y2), danger_zone_pts) writer.writerow([time.strftime(%Y-%m-%d %H:%M:%S), int(cls), f{conf:.2f}, in_zone])日志文件直接落到本地磁盘。保存时注意文件句柄不要每帧开关否则写入性能会成为瓶颈正确做法是程序结束时统一 flush 并关闭。6. 参数调优的后手用一组detect指令对阈值和区域做边界测试参数最终要落到model.conf和model.iou上但这两个值的最优组合需要通过批量测试来确定。我的做法是写一个循环针对同一段视频测试不同参数组合下的帧率和误检数形成对比表python detect.py --weights runs/train/exp/weights/best.pt \ --source test.mp4 \ --conf 0.5 --iou 0.45 \ --save-txt --save-conf--save-txt会把每帧检测结果写入labels/目录下--save-conf会把置信度一并写入。然后用脚本统计同一目标在不同参数组合下的检出次数通过对比找到最合适的阈值区间。对于危险区域的边界测试我习惯准备一组测试用例图片目标恰好位于区域边界外、边界上、边界内各 5 组。逐一运行区域判定函数并比对输出结果。pointPolygonTest在边界附近误差相对明显因为检测框本身有抖动这时可以把判定条件改为result 0且同时考虑前 3 帧的平均位置以帧序列的稳定结果作为最终闯入依据。帧率调优上cap.set(cv2.CAP_PROP_FPS, 10)有时候无效因为它取决于摄像头是否支持修改帧率。更可靠的做法是在推理侧做帧丢弃策略比如每读 3 帧只推理 1 帧并保证推理结果渲染回原图时不会出现坐标偏移。YOLOv5 的results.render()返回的是已经绘制好边框的帧直接输出即可。最终交付时确保你的项目目录里包含以下内容yolov5/模型代码、runs/train/exp/weights/best.pt训练好的模型、detect_rtsp.py实时检测脚本、danger_zone.json危险区域配置文件和README.md。将 RTSP 地址和参数配置统一放在config.yaml里以便答辩老师查看时能快速理解整个系统的输入输出结构。本文还有配套的精品资源点击获取
返回列表