ARTICLE DETAIL

资讯详情

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

深度学习与YOLO的电动自行车头盔佩戴检测实战指南

深度学习与YOLO的电动自行车头盔佩戴检测实战指南 简介基于深度学习的电动自行车头盔佩戴检测系统是一套完整可运行的项目包面向具备Python基础、希望实战目标检测与多目标跟踪的开发者或学生。系统以YOLOv5完成头盔目标检测结合DeepSORT对骑行者进行持续追踪借助PyTorch、OpenCV等库覆盖模型训练、推理以及视频流接入的完整链路适合智能交通与安全监控场景。压缩包共186个文件大小约133.57MB核心包含55个Python脚本、22个YAML配置、7个模型权重及45张图片样例另附前端展示页、Dockerfile与环境配置脚本便于快速搭建运行环境与二次开发。目前已有422人学习下载。借助这份资源读者不仅能获得完整的训练与检测代码还可学习YOLOv5与DeepSORT的整合方式、数据组织和参数配置技巧并通过可视化页面直观理解系统工作过程是一份适合项目实战和算法入门的综合参考资料。1. 一个 zip 就能跑通头盔佩戴检测先看它能解决什么把一段电动自行车路口的监控视频扔给模型它能在几十毫秒内把“戴着头盔的头部”和“没戴头盔的头部”分别框出来这就是这套基于深度学习的电动自行车头盔佩戴检测系统最核心的能力。资源用 Python 组织全过程从数据集整理、模型训练到摄像头实时推理都有可运行的脚本。它不是对整个画面做“戴了/没戴”的整图二分类而是把骑行者头部作为检测目标在空间位置上一并输出边界框和置信度这样后接告警、抓拍、统计都很方便。适合三类人做深度学习毕设的学生、给园区或社区做安防检测的开发者以及想快速验证目标检测落地方案的产品原型。下面我按拆包复现的顺序讲类别设计、训练参数、推理部署和踩坑记录。2. 数据与标注两类还是三类标签格式怎么对齐2.1 头盔检测不是二分类类别设计与样本结构很多新手会把“头盔佩戴检测”理解成二分类输入一张图输出戴了或没戴。但检测器输出的是边界框不是整图标签所以类别设计要围绕“目标是什么”而不是“状态是什么”。我一般建议用两个头部类别helmet戴头盔的头部和 no_helmet未戴头盔的头部。这两个类别都指向头部区域区别只在头顶区域有没有头盔覆盖。这样模型学的是“头部头盔覆盖”的视觉特征而不是试图把头盔从复杂背景里单独抠出来。也有团队把类别拆成三个person、helmet、head。这个方案的后处理更灵活先检测人再检测头部最后用头部中心在人体框内的相对位置判断是否佩戴代价是标注量大了不少而且行人的手臂、背包、雨伞遮挡会让 person 和 head 的框互相干扰。对电动自行车这种“骑行人头盔”结构相对固定的场景我更喜欢直接做两类。类别方案标注难度后处理逻辑典型误判适用场景两类helmet / no_helmet低只标头部区域模型直接输出佩戴状态后脑勺被盔檐遮挡时类别不稳定路口固定视角、抓拍机三类person / helmet / head高人体与头部都要标先检测部件再按相对位置判定行人背包被误判为 no_helmet复杂街道、多目标拥挤场景两类方案还有一个实际好处当两个骑行者挨得很近时只要头部区域清晰模型就不会把两个人的头盔混成一个框三类方案在这种叠加场景里反而容易把头部归属到错误的人体。实战经验是如果误报压不住再退回三类加规则判定不要一上来就选复杂度高的方案标注成本会直接拖垮项目进度。2.2 标注格式与目录组织解开后先对齐 YOLO 习惯解开 zip 之后第一步不是急着训练而是确认数据集目录是不是 YOLO 习惯的结构。主流训练脚本默认读一种格式每张 jpg 对应一个同名 txttxt 每一行是 class x_center y_center width height四个坐标全部按图片宽高归一化到 0-1。常规结构长这样dataset/ ├── images/ │ ├── train/ *.jpg │ └── val/ *.jpg ├── labels/ │ ├── train/ *.txt │ └── val/ *.txt └── helmet.yamltrain 和 val 里是图片文件同名标签文件放在 labels 对应的目录下yaml 负责告诉训练脚本数据在哪、有几类、类别名叫什么。如果手上是别人给的 VOC 格式 xml直接训练会报标签找不到或者标签张数不匹配所以要先写个转换脚本把 xml 转成上面的 txt 格式import xml.etree.ElementTree as ET import os classes [helmet, no_helmet] def convert(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.iter(object): cls obj.find(name).text if cls not in classes: continue b obj.find(bndbox) xmin float(b.find(xmin).text) ymin float(b.find(ymin).text) xmax float(b.find(xmax).text) ymax float(b.find(ymax).text) # 归一化坐标并防止标签越界到 1.0 之外 cx min((xmin xmax) / 2 / w, 0.999) cy min((ymin ymax) / 2 / h, 0.999) bw min((xmax - xmin) / w, 0.999) bh min((ymax - ymin) / h, 0.999) lines.append(f{classes.index(cls)} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) out_name os.path.splitext(os.path.basename(xml_path))[0] .txt with open(os.path.join(out_dir, out_name), w) as f: f.write(\n.join(lines))这段脚本里 xmin、xmax 先求平均再除以图宽得到的是框中心点的归一化横坐标ymin、ymax 同理。bw 和 bh 是框宽高相对图片宽高的比例。min(…, 0.999) 是防越界的小动作——有些标注工具会把框画到图片边缘之外坐标算出来刚好是 1.0训练时 IoU 计算会报异常。out_dir 按训练/验证分别填到 labels/train 或 labels/val 就行。2.3 数据自检脚本训练前必须跑一遍这步不是走流程。标注工具偶尔会导出宽度为 0 的框或者 xmin 和 xmax 写反又或者某张图有对应 jpg 但 txt 是空的。这类脏数据混进训练集不会直接报错而是让 loss 后期下不去、val mAP 忽高忽低排查起来比训练失败更头疼。我每次拿到数据集第一件事就是跑一遍自检from pathlib import Path img_dir Path(dataset/images/train) label_dir Path(dataset/labels/train) for img_path in img_dir.glob(*.jpg): label label_dir / (img_path.stem .txt) if not label.exists(): print(f[缺标签] {img_path.name}) continue lines label.read_text().strip().splitlines() if not lines: print(f[空标签] {img_path.name}) continue for i, line in enumerate(lines): parts line.split() if len(parts) ! 5: print(f[格式错] {img_path.name} 第{i1}行) continue _, cx, cy, bw, bh map(float, parts) if not (0 cx 1 and 0 cy 1 and 0 bw 1 and 0 bh 1): print(f[越界] {img_path.name} 第{i1}行: {line}) if bw 0.01 or bh 0.01: print(f[过小] {img_path.name} 第{i1}行)脚本逐个图片找同名标签依次检查文件存在、内容非空、每一行拆出来是不是 5 个字段、归一化坐标是否在合理区间、框宽高是否过小。常见做法是缺标签的图片直接删掉或补标空标签看图中是否确实无目标越界框裁剪或重标小于 1% 图宽的框放弃——这种目标在 640 分辨率下只有几个像素模型学不到有效特征只会给回归层制造噪音。检完一遍再训练能省下后面好几轮排错时间。3. 模型训练YOLO 选型与深度学习训练参数调优3.1 选型逻辑为什么是 YOLO 而不是 Faster R-CNN头盔佩戴检测对延迟敏感。路口摄像头是实时流一帧要在几十毫秒内出结果后面还要接告警和抓拍拖不得。Faster R-CNN 精度不差但两阶段结构先提候选区域再做分类回归在 CPU 或老 GPU 上很难跑到实时。YOLO 这一系是单阶段检测把定位和分类放在同一个卷积网络里直接输出速度和部署便利性都更适合这类落地场景。这个资源对应的实践里常见做法是从 yolov5s 这类轻量档位起步。YOLO 系列按 s、m、l、x 区分网络宽度和深度对应的权重体积和推理耗时差别不小。头盔目标不算小但骑行者经常出现在画面边缘和远处速度与精度的权衡点要选好。我一般先用 s 档跑通整条链路再对比 m 档的 mAP 提升值不值得上大模型让数据说话。档位权重体积640 分辨率 GPU 推理耗时适用场景yolov5s约 14MB5-10ms主流现场目标数量适中yolov5m约 40MB10-20ms光线复杂、目标拥挤yolov5l约 90MB20-40ms离线视频分析很多项目卡住不是因为模型精度不够而是整条链路没跑通就先调模型大小调到后面连 GPU 显存预算都变了。先小模型跑通再逐步放大是更稳的路径。训练调参本身有一点玄学成分但前提是数据没毛病所以上一章的自检脚一定不要跳过。3.2 训练启动helmet.yaml 与 train.py 参数训练前先写数据描述文件 helmet.yamlpath: dataset train: images/train val: images/val nc: 2 names: 0: helmet 1: no_helmetpath 是数据集根目录相对路径以训练脚本所在的目录为基准train 和 val 填图片目录路径训练脚本会自动把 images 替换成 labels 去找同名标签文件。nc 是类别数量names 的顺序必须和标注 txt 里的 class 索引一致标注时如果 0 是 helmet、1 是 no_helmetyaml 里就按这个顺序写否则训练不会报错但推理结果会张冠李戴。注意类别顺序在训练和推理阶段必须全局一致中途改顺序会让整个训练白费。启动训练命令python train.py --data helmet.yaml --weights yolov5s.pt \ --img 640 --batch 16 --epochs 150 \ --device 0 --multi-scale --label-smoothing 0.05--img 640 是输入分辨率头盔检测可以考虑改成 768 来提升对小目标的召回代价是显存占用和训练时间上涨--batch 16 在 8G 显存上比较稳显存大可以调到 32--multi-scale 让每个 batch 随机在 0.5 到 1.5 倍之间缩放输入尺度泛化更好训练时间变长但值得--label-smoothing 0.05 对类别标签做平滑防止模型对训练集过度自信--device 0 指定第一块 GPU机器只有 CPU 就删掉这个参数。第一次训练建议 epochs 设 150看 val mAP 是否还有上升趋势再决定加不加。3.3 训练监控与中断恢复关键指标和 resume 细节训练时主要盯两个指标box_loss 和 cls_loss。正常情况下前 10-20 轮快速下降后面缓慢收敛如果 loss 像锯齿一样反复先怀疑学习率默认学习率对大中数据集问题不大但样本量小时经常要手动降到 0.001 再试。val mAP 从第 50 轮还在涨就说明模型没学够可以把 epochs 加长连续 30 轮 mAP 不再上升基本可以提前停。训练中断是常态恢复命令python train.py --data helmet.yaml --weights runs/exp/weights/last.pt \ --img 640 --batch 16 --epochs 150 --resume--resume 会自动读取 last.pt 里记录的 epoch、优化器状态和学习率接着上次位置继续而不是从头再跑。有一点容易忽略恢复前如果手动改过数据集先把缓存文件删掉再 resume否则脚本还是会读旧的缓存索引新增的图片根本不会进入训练。训练结束在 runs/exp 目录下会同时生成 last.pt 和 best.pt。部署时一定用 best.pt它是按验证集 mAP 挑出来保存的last.pt 只是最后一个 epoch 的结果这时候模型往往已经过拟合盲目使用会让现场误报增加。这也是我早期翻车比较多的地方后面养成只看 best.pt 的习惯才消停。4. 推理部署权重加载、实时检测与 ONNX 转换4.1 加载权重跑单帧图片先确认模型没被训练流程带偏训练完拿到 best.pt先不要直接接摄像头用最少的代码做一次单帧验证import torch model torch.hub.load(ultralytics/yolov5, custom, pathbest.pt, force_reloadTrue) model.conf 0.45 # 置信度阈值低于此值的框被丢弃 model.iou 0.45 # NMS 的 IoU 阈值 results model(test.jpg) results.show()model.conf0.45 的含义是模型对检测框的类别置信度低于 45% 就丢弃。调低会让漏检减少但误报增多调高相反头盔检测通常从 0.45 起步现场再根据误报率微调。model.iou0.45 是 NMS 去重时用的 IoU 阈值两个框重叠超过 45% 就并成一个一般保持默认即可。torch.hub.load 首次运行会按需拉取模型结构如果现场是内网环境把整个仓库和权重放进资源包一起分发会更省事。验证通过后results.pandas().xyxy[0] 输出的每一行包含 xmin、ymin、xmax、ymax、confidence、class后续接业务逻辑时直接遍历这个结构取坐标。不要在这里做太多预处理YOLO 推理脚本内部已经处理了缩放手动叠加反而容易引入偏差。4.2 摄像头实时检测跳帧与 letterbox 的配合摄像头实时检测的骨架代码是这样import cv2 cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break results model(frame, size640) frame results.render()[0] # 返回画好框的帧 cv2.imshow(helmet, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()results.render() 返回的是画好检测框的 numpy 数组直接传给 imshow。waitKey(1) 表示主循环每秒最多刷新约 1000 次实际帧率由推理耗时决定。推理卡顿时先把 size 降到 416 试试或者做跳帧推理——每 3 帧跑一次模型中间两帧直接沿用上一次的检测结果画面观感几乎没有区别但 CPU 占用下降明显。这个技巧在 Jetson 这类嵌入式设备上尤其管用。letterbox 是 YOLO 推理里最容易忽略的细节。模型不会把输入简单 resize 成 640 见方而是长边缩放到 target size、短边用灰边补齐避免目标变形。如果摄像头画面本身已经做过裁剪形状不是常见比例务必在送入模型前手动补边否则输出的框会在坐标系上整体偏移。具体做法是复制一份画布等比缩放原图再放到画布中央推理后再把框坐标映射回原图坐标。4.3 导出 ONNX让部署脱离 PyTorch 环境现场机器不一定装得了 PyTorch常见做法是导出 ONNX用 ONNX Runtime 或 OpenCV DNN 加载。导出命令python export.py --weights best.pt --include onnx --opset 12导出后生成 best.onnx。opset 版本影响算子兼容性opset 12 在大多数设备上都能稳定支持。导出报算子不支持优先升级 onnx 和 onnx-simplifier 再重试。ONNX 输出的原始张量形状是 [1, num_anchors, 5nc]5 指的是 x、y、w、h 和 objectness 置信度后面按类别数接类别得分解析之后还要自己再做一次 NMS。如果不想自己写解析直接用 onnxruntime 加载并配合现成的后处理库工程上更省心。对大多数部署场景我更推荐 ONNX Runtime 而不是纯 OpenCV DNN后处理算子完善NMS 有现成实现模型更新时不需要反复维护解析代码。推理输入前同样要先做 letterbox再按照训练时的均值方差做归一化。到这里权重就不再依赖 PyTorch 环境一个 Python 脚本带上 onnx 和 onnxruntime 就能在普通机器上跑起来。5. 避坑指南头盔检测的五个踩坑记录与排查方法这一章的血泪经验我都按“现象、原因、解决”整理成五条覆盖标注、训练、部署三个环节。每条都是实际项目里真正发生过的不是理论推演。5.1 标注与类别戴盔却被判成未佩戴现象监控画面里骑行者明明戴着头盔模型却稳定输出 no_helmet 框而且位置就在头部附近看起来不像是完全没检测到。原因训练集里大量“从背后或侧面看到的头盔”被标成了 no_helmet。标注员见到帽檐遮挡了头顶就按没戴处理另一种情况是标签框画得太大把后面行人的头部一起框了进来。模型学到的“helmet”和“no_helmet”特征边界是混乱的推理时自然产生歧义。解决先统一标注规范再讨论模型。只要头部被头盔覆盖超过一半无论什么角度都标 helmet框边界收紧到头部范围不要带颈部以下。清洗历史标签比补充新数据更立竿见影特别是那种同一张图里两类标签交叠的样本直接删掉重标。5.2 漏检与误检小目标、环境光与摄像头视角现象晴天白天测试正常到傍晚或逆光时远处的骑行者全部漏检近处的误检也在增加。原因640 分辨率下远处骑行者头部高度只剩十几个像素特征图下采样几轮后就没了响应逆光环境下头盔轮廓淹没在背景亮度里模型对对比度变化很敏感。这属于小目标与光照双重问题不是简单调阈值能解决的。解决训练和推理分辨率一起提到 768显存受限就把 batch 降到 8训练时开启 --multi-scale让模型见过更多尺度推理端对暗光图像先做 CLAHE 自适应直方图均衡再送进模型。这个组合是头盔检测精度提升最明显的手段比换大模型成本低得多。5.3 训练与性能loss 不降和 CPU 卡顿现象训练到第 30 轮 loss 还在 1.5 以上val mAP 在 0.1 到 0.3 之间反复横跳另一边同一个模型在 CPU 上推理一帧要 0.5 秒以上。原因loss 不降大部分是脏标签和锚框失配。默认 anchor 按照 COCO 目标统计设计头盔这种头部目标相对尺寸很小锚框与目标的重合度低回归头很难收敛。CPU 卡顿基本都是直接拿 PyTorch 在跑没有做任何优化。解决loss 问题先跑第 2 章的数据自检脚本清掉空标签和越界框再用 autoanchor 重新计算数据集的锚框。性能问题把模型导出 ONNX 后用 onnxruntime 推理实测比 PyTorch 快两到三倍输入分辨率降到 416检测框数量被 NMS 压缩后后处理耗时也会下降。5.4 类别顺序错位yaml 里 names 对不上标注现象训练 loss 正常但推理时 helmet 和 no_helmet 输出颠倒了戴头盔的人框上标注着“no_helmet”。原因helmet.yaml 里 names 的顺序和标注 txt 的 class 索引不一致。标注工具导出时类别顺序是 A、Byaml 里写的却是 B、A训练脚本不会校验这个映射模型学到的是“类别 0 的特征”推理时按 yaml 翻译成对应名字自然就反了。解决训练前把标注统计打印一遍确认 class 0 是哪个类别、class 1 是哪个类别再回头核对 yaml。最稳的做法是标注阶段固定类别顺序后续所有脚本统一引用不要中途改顺序。5.5 部署环境不一致本地正常现场崩现象本地测试视频效果不错部署到现场后误报率从 3% 涨到 25%且目标框出现明显偏移。原因现场摄像头安装高度、俯仰角、分辨率与训练数据不一致模型学到的尺度分布失效还有一个常见原因是现场视频做了裁剪或缩放推理脚本没做对应的 letterbox框坐标映射错位。这类问题模型参数再怎么调都压不住根源在数据和预处理。解决从现场截取不同时段的视频帧用低置信度推理筛选出错分样本补充标注后增量训练下一章细说同时确认部署脚本与训练脚本的预处理完全一致特别是 letterbox 的补边逻辑。预处理不一致导致的框偏移是这类项目里最容易翻车的点排查优先级要排在模型前面。6. 进阶技巧用低置信度样本迭代让模型适应新现场6.1 低置信度结果才是金矿模型部署到新现场效果不好大多数人的第一反应是调置信度阈值。但阈值只决定接受多少框不改变模型对目标的判断能力。更有效的做法是收集“低置信度”样本做增量训练。具体思路把模型推理阈值放低到 0.25让它输出大量“不确信”的框里面既有漏检的真样本也有误检的假样本。人工扫一遍这些截图把真正漏掉的补充成标签把误检的归到负样本再追加到训练集训 10 到 20 轮。现场数据与训练分布的差距会被快速拉近。from pathlib import Path import cv2 out_dir Path(hard_samples) out_dir.mkdir(exist_okTrue) model.conf 0.25 # 故意放低阈值收集不确定样本 model.iou 0.5 for img_path in Path(field_frames).glob(*.jpg): img cv2.imread(str(img_path)) det model(img_path).pandas().xyxy[0] for _, row in det.iterrows(): if 0.25 row[confidence] 0.6: x1, y1, x2, y2 map(int, [row[xmin], row[ymin], row[xmax], row[ymax]]) crop img[y1:y2, x1:x2] cv2.imwrite(str(out_dir / f{img_path.stem}_{row[confidence]:.2f}.jpg), crop)这段脚本的置信度区间 0.25 到 0.6 是经验值低于 0.25 的框基本是背景噪音高于 0.6 的框模型已经很有把握最有价值的就是中间这一段。裁剪出来的小块按原图名和置信度命名人工分拣后把真正的头盔样本转成标签加入数据集。增量训练和从头训练不一样epochs 不要设太长20 轮以内就够了学习率比首次训练再调低一个量级避免模型把旧数据忘掉。我一般每轮迭代完都会拿最新权重重新跑一遍现场视频对比这轮多收回了多少漏检——指标没提升就说明新数据没有代表性需要调整采集策略而不是继续叠加训练。从那以后我每次接到目标检测类项目都会强制走一遍这个低置信度样本迭代流程哪怕只迭代一轮现场表现也会有肉眼可见的提升。希望帮到你。本文还有配套的精品资源点击获取
返回列表