简介:一套被评为98分的YOLOv5安全帽检测毕业设计项目,面向计算机视觉、深度学习方向的毕设学生和实战学习者,也可用于课程设计或期末大作业。项目以安全帽佩戴检测为核心,提供完整源码、训练好的模型与权重、标注数据集及使用教程,经严格调试可稳定运行。
资源包共164个文件,大小约44.74MB,包含Python训练与推理脚本、YAML/CMake配置文件、C++/CUDA扩展模块、预训练权重(.pt)等,既可直接加载模型进行推理,也支持在自有数据上重新训练。目前已有240人学习/下载,适合作为整套毕设方案直接提交。
借助这些内容,可快速掌握YOLOv5在安全帽检测中的数据处理、模型训练、权重调用与可视化流程;附带的使用教程能帮助理解环境配置、参数调整和常见排错思路。代码结构清晰,涉及C++部署与CUDA算子部分,也为进阶学习者提供了模型优化与部署的参考。
1. 为什么“YOLOv5安全帽检测代码+训练好的安全帽模型+权重+数据集+使用教程”是一套能直接落地的方案
安全帽检测是工地视觉巡检里需求最固定、边界最清楚的目标检测任务之一:摄像头画面里出现的人,要么戴了帽子,要么没戴,最多再加一个“只检测头部”的场景。用 YOLOv5 做这件事,材料齐不齐决定了你是在做工程还是在补环境。这个标题给出的东西本质上是一套完整交付物——训练好的安全帽检测模型、对应权重文件、可继续训练的标注数据集、以及把模型跑起来的教程。它解决的不只是“能不能识别”,而是让一个没有完整数据集、没有算力经验的人也能在本地把推理跑通,并具备继续训练自家数据的基础。
适合的读者很明确:准备交毕业设计/课程设计的学生、给工地或工厂做安全巡检原型验证的开发者、以及第一次接触 YOLOv5 想少走弯路的入门者。不过你拿到的代码和权重只是起点,真正让你翻车的往往不是模型本身,而是数据集路径、类别顺序、标注格式这类细节。下面按“整理数据→训练→推理→排查→部署”的顺序,把这套东西从能跑变成能打。
2. 数据集与标注:从零整理 YOLOv5 能直接吃的一手安全帽数据
2.1 先搞懂 YOLOv5 的数据集结构
YOLOv5 训练时读的不是 XML 或 JSON,而是纯文本的 txt 标注,每张图片对应一个同名 txt 文件。图片在 images 目录下,标注在 labels 目录下,目录结构长这样:
datasets/ ├── images/ │ ├── train/ │ │ ├── img001.jpg │ │ └── ... │ └── val/ ├── labels/ │ ├── train/ │ │ ├── img001.txt │ │ └── ... │ └── val/ └── helmet.yaml每个 txt 文件里每一行代表一个目标,格式为:
class_id x_center y_center width height其中 x_center、y_center、width、height 都是归一化坐标(除以图片宽高后取值 0~1)。安全帽检测项目里类别通常是两类:戴帽(helmet/with helmet)和未戴帽(head/without helmet),也有项目把类别拆成三类,比如戴帽、未戴帽、以及人的头部。类别编号从 0 开始,在 data.yaml 里定义顺序。比类别多少更关键的是:labels 里 txt 的命名必须和 images 里 jpg 完全一致(不含扩展名),一旦对不上,训练时会直接报 “No labels found” 并跳过该图。
拿到这类高分项目的数据集时,第一件事不是急着训练,而是检查图片和标签的数量是否匹配、标签里有没有空文件、坐标有没有越界。常见做法是写一个十几行的扫描脚本,把空标签和缺标签的图片挑出来,不然后面训练出现 NaN loss 时你很难定位是哪张图的问题。
2.2 把 VOC/其他格式转成 YOLOv5 的 txt 标注:转换脚本
很多公开安全帽数据集给的是 PASCAL VOC 格式的 XML 标注,需要转成 YOLO 格式。这里给出一个我常用的转换脚本,核心是解析 XML 里的 bndbox 坐标并归一化:
import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_file, out_file, class_list): tree = ET.parse(xml_file) root = tree.getroot() img_w = int(root.find('size/width').text) img_h = int(root.find('size/height').text) lines = [] for obj in root.iter('object'): cls_name = obj.find('name').text if cls_name not in class_list: continue # 跳过未定义类别 cls_id = class_list.index(cls_name) box = obj.find('bndbox') x1 = float(box.find('xmin').text) y1 = float(box.find('ymin').text) x2 = float(box.find('xmax').text) y2 = float(box.find('ymax').text) # 坐标裁剪:防止标注越出图像边界导致训练报错 x1 = max(0, min(x1, img_w - 1)) x2 = max(0, min(x2, img_w - 1)) y1 = max(0, min(y1, img_h - 1)) y2 = max(0, min(y2, img_h - 1)) x_center = ((x1 + x2) / 2) / img_w y_center = ((y1 + y2) / 2) / img_h width = (x2 - x1) / img_w height = (y2 - y1) / img_h # 过滤掉宽或高为0的无效框 if width <= 0 or height <= 0: continue lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") with open(out_file, 'w') as f: f.write('\n'.join(lines)) if __name__ == '__main__': # 类别顺序必须和后续 data.yaml 中的 names 顺序一致 classes = ['helmet', 'head'] xml_dir = 'Annotations' out_dir = 'labels' for xml_name in os.listdir(xml_dir): if not xml_name.endswith('.xml'): continue stem = xml_name[:-4] # 去掉 .xml 后缀 voc_to_yolo( os.path.join(xml_dir, xml_name), os.path.join(out_dir, stem + '.txt'), classes )逻辑说明:脚本遍历 Annotations 目录下的每个 XML 文件,取出图片真实宽高,把 bndbox 的左上角和右下角坐标转成中心点加宽高的归一化表示。类别名通过 class_list 映射成整数 id,类别顺序一旦确定就不能再改,因为模型输出层的类别索引是按这个顺序训练的。代码里做了两件容易被忽略的事:坐标裁剪和空框过滤。很多数据集标注时会出现目标略微超出图像边界的情况,不做裁剪会让 YOLOv5 算 loss 时出现负坐标、甚至导致训练 loss 变成 NaN;空框不过滤则会产生全零行,让模型学到无意义的背景框。
2.3 类别定义和 data.yaml:决定训练成败的配置文件
转换完标注后,下一步是写 data.yaml。安全帽检测最常见的坑就在这里:data.yaml 里的 nc(类别数)和 names(类别名顺序)必须与标注文件里的 class_id 一一对应。如果你标注时把 helmet 放在 0、head 放在 1,那 names 必须是['helmet', 'head'],不能反过来。
# datasets/helmet.yaml train: datasets/images/train val: datasets/images/val # test: datasets/images/test # 可选,不参与训练 nc: 2 names: ['helmet', 'head']路径建议用相对路径,并且以你执行训练命令的工作目录为基准。很多人习惯写绝对路径,一旦项目文件夹移动,下次训练就报路径错误;相对路径配合固定的项目工作目录更省心。这里 train 和 val 指向的是 images 目录,不是 labels 目录,YOLOv5 会根据图片路径自动把images替换成labels去读标注文件,所以目录命名一定要按images/和labels/来放,否则会一直提示标签找不到。
2.4 数据量多少才够:先跑通再扩量
安全帽检测的目标和背景都比较单一,不像自动驾驶场景那样复杂。我的经验是:只有几百张图也能训练出能用的模型,但那是建立在迁移学习的基础上——用官方预训练权重初始化,再做微调。也就是说,哪怕你的数据集只有 500 张图,用 yolov5s.pt 起步也能在一个小时左右得到可演示的检测效果;而如果从零训练(weights=''),同样的数据量会让 mAP 掉得很明显。
高分项目里附带的数据集通常已经做过筛选和清洗,你要先确认两点:训练集与验证集是否重复(重复会导致指标虚高)、以及类别是否均衡(未戴帽因为样本少容易被忽略)。这两点不符合预期时,优先补数据,而不是先调超参数。模型、权重、数据集三者是一套系统,数据集质量决定上限,模型和权重决定你离上限有多近。
3. 环境配置与训练:把安全帽模型从源码跑起来
3.1 环境配置:conda、PyTorch、CUDA 的版本搭配
YOLOv5 的环境配置是新手最容易卡死的第一关。常见做法是创建独立 conda 环境,再按 requirement 安装。这里给出我验证过的一套稳定组合:
conda create -n yolov5 python=3.8 -y conda activate yolov5 pip install torch==1.13.1 torchvision==0.14.1 --index-url https://download.pytorch.org/whl/cu117 git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txtCUDA 11.7 配合 PyTorch 1.13 是兼容性很稳的组合,既支持 30 系显卡(CUDA 11.7 也支持 40 系),又避开了 PyTorch 2.x 引入的编译问题。如果你只有 CPU 环境,把 torch 安装命令换成 CPU 版本即可,但后面训练时间会达到 GPU 的几十倍,强烈建议至少用 Google Colab 或者云 GPU 完成训练环节。
装完依赖后验证 GPU 可用:
import torch print(torch.cuda.is_available()) # 输出 True 才说明能用 CUDA这一步出问题的原因大多是 torch 与 CUDA 版本不匹配,或显卡驱动太旧。建议先执行nvidia-smi看驱动支持的 CUDA 版本,再回头选 torch 的 cu 版本,不要盲目装最新版。
3.2 目录结构:把数据集放对位置,减少 80% 的路径报错
YOLOv5 官方仓库默认在项目根目录下找datasets/文件夹。你可以把数据集的 images 和 labels 按第 2 章的结构放进去,也可以创建软链接指向数据实际所在位置:
ln -s /path/to/your/datasets ./datasets训练之前先跑一次验证命令,确认 YOLOv5 能找到标签:
python train.py --data datasets/helmet.yaml --weights yolov5s.pt --img 640 --batch 8 --epochs 5如果只跑 5 个 epoch 能顺利起步,说明数据路径、标签格式没问题;如果报错,先去对照 2.1 节的目录结构,而不是直接调参。很多“训练翻车”本质上就是路径问题被拖到了训练中期才暴露,轻则浪费几小时,重则产出完全不可用的模型。
3.3 训练命令参数:从 yolov5s 起步,按显存调 batch
安全帽检测属于相对简单的中等目标检测任务,不需要一上来就用 yolov5x。我的建议是从 yolov5s 起步,训练 50~100 个 epoch 先看验证集 mAP 能不能到 0.85 以上,不够再换 yolov5m 或增加训练数据。
python train.py \ --data datasets/helmet.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --cache \ --name helmet_base参数含义说明:
--img 640:训练时缩放到的输入尺寸。640 是精度和速度的平衡点,不要随意降到 320,小目标会明显漏检;显存吃紧时可以用 512 过渡,但最终部署建议回到 640。--batch 16:单卡合适值。如果显存只有 6G,把 batch 降到 8;显存报错 OOM 时优先降 batch,而不是降分辨率。--epochs 100:100 个 epoch 足够收敛。训练日志里如果最后 20 个 epoch 的 mAP 已经不再上升,说明 100 是个合理上限。--cache:把图片提前加载进内存,减少磁盘 I/O。图片量不大时强烈推荐,能显著缩短每个 epoch 的时间。--name helmet_base:结果输出到runs/train/helmet_base/目录,方便区分多次实验。
训练过程会自动做 Mosaic、HSV 变换、随机翻转等增强,小数据集也能扩出足够多样的样本。训练结束后,runs/train/helmet_base/weights/里会生成best.pt和last.pt——best.pt是验证集 mAP 最高的权重,last.pt是最后一个 epoch 的权重。平时做推理和部署一律用best.pt。
3.4 超参数调整:什么时候需要动 hyp,什么时候别动
YOLOv5 提供了默认的data/hyps/hyp.scratch-low.yaml,大多数安全帽数据集直接用默认值就能达到不错的效果。我一般不推荐新手盲目改超参数,因为默认配置在 COCO 上已经调得很稳。真正需要调整的场景有三种:
第一,训练集很小(少于 1000 张),可以调低增强强度,比如把mosaic: 1.0降到0.5,防止模型见过太多被裁切变形的目标而学不到完整特征。第二,工地场景是大目标占比高,可以把anchor相关参数配合重算锚框使用。第三,显存紧张时在 train.py 里加上--noplots减少可视化图表生成,节约内存。
关于锚框,YOLOv5 默认会在每个 epoch 开始前自动用 k-means 重算锚框,前提是--noautoanchor没有被指定。安全帽的尺寸比例和 COCO 有些差异,自动重算是合理的;但如果数据集类别退化严重,自动算出来的锚框可能集中在某个尺度,这时可以手工指定。观察训练日志里autoanchor的提示,如果它显示 “optimal anchors” 说明锚框没问题,如果提示 “Better than old anchors”,说明自动重算后的锚框优于预置,保留即可。
4. 模型权重与推理:拿到权重后怎么验货、怎么用
4.1 权重文件有哪些类型,各自用来干什么
YOLOv5 训练完会产出.pt格式权重文件,这是最原始的格式,包含模型结构、权重参数和训练信息。但真正做部署时,.pt不是唯一选择,甚至不是首选。常见权重格式如下:
| 格式 | 来源 | 特点 | 适用场景 |
|---|---|---|---|
| .pt | 训练/导出 | 含检查点,可断点续训 | 继续训练、快速验证 |
| .onnx | 导出 | 跨平台,可被 ONNX Runtime 加载 | 服务端推理、边缘设备 |
| .torchscript | 导出 | 免 Python 依赖部署 | C++ 集成、移动端 |
| .engine | 导出 | TensorRT 优化,速度最快 | NVIDIA GPU 上低延迟推理 |
标题里提到的“权重”通常指训练好的.pt文件。拿到权重后先确认它的类别输出是否与你的数据集一致,方法有两种:一种是最小推理测试,另一种是用torch.load读取权重文件里的model.names。后者可能因为模型结构差异踩坑,所以更推荐直接跑一次单张图片推理,看输出框的类别名是否符合预期。
4.2 用 detect.py 跑通检测:命令与输出位置
YOLOv5 自带的 detect.py 是最快的验货方式:
python detect.py \ --weights runs/train/helmet_base/weights/best.pt \ --source data/images/sample.jpg \ --conf-thres 0.25 \ --iou-thres 0.45 \ --save-txt这段命令会加载 best.pt 权重,对 sample.jpg 做推理,并输出带框的图片到runs/detect/exp/目录。加上--save-txt后会把检测结果的坐标和类别存成 txt,方便后续做数据统计。--source可以传图片路径、视频文件路径、摄像头设备号或目录。安全帽检测的现场需求通常是视频流,你可以直接用--source 0调用本机摄像头,也可以传一段 MP4 工地图视频做脱机测试。
输出图片里绿色的框是置信度高于 conf-thres 的目标。第一次跑通后,建议把 conf-thres 设到 0.1 再看一遍结果——如果此时出现大量漏检,说明模型本身没问题,只是阈值设太高;如果 0.1 时疯狂误检,说明模型没训练好或者数据覆盖不足,需要回头优化。
4.3 置信度与 NMS 阈值:藏在效果背后的两个开关
安全帽检测的典型困境是:戴帽和不戴帽的目标都小,人与人的遮挡又常见。--conf-thres决定一个框是否保留,--iou-thres决定重叠框之间如何去重。动态监控场景建议 conf-thres 设 0.15~0.25,因为偏远距离的安全帽目标在 640 分辨率下像素很少,置信度天然偏低;如果设成 0.5,漏检率会很高。而离线审核场景可以把 conf-thres 提到 0.4 以上,宁可少报也别误报,避免给安全员推送大量无效告警。
--iou-thres默认 0.45,密集人群场景可以适当降到 0.3。如果设置过高,两个紧挨着的人会被合并成一个框;设置过低,同一个目标可能重复出框。这个参数和置信度是耦合的,通常先固定 conf-thres,再在 0.3~0.5 区间扫一遍 iou-thres,挑验证集效果最好的组合。
4.4 用 Python API 自己做推理:集成进业务系统的标准姿势
detect.py 适合测试和演示,但要集成到工地安全管理系统、告警通知脚本或树莓派边缘设备上,得用 YOLOv5 的 Python API 直接加载权重。下面是一个标准化推理模板:
import cv2 import torch import numpy as np # 加载模型,只能推理不参与梯度计算 model = torch.hub.load('ultralytics/yolov5', 'custom', path='runs/train/helmet_base/weights/best.pt', force_reload=True) model.conf = 0.25 # 置信度阈值 model.iou = 0.45 # NMS IoU 阈值 model.max_det = 50 # 单张图片最多保留的检测框数 # 读取图像并推理 img = cv2.imread('test.jpg') results = model(img) # 解析结果:框坐标、置信度、类别 boxes = results.xyxy[0].cpu().numpy() # [x1, y1, x2, y2, conf, class_id] for box in boxes: x1, y1, x2, y2, conf, cls_id = box label = model.names[int(cls_id)] print(f"{label}: {conf:.2f}, box=({x1:.0f},{y1:.0f},{x2:.0f},{y2:.0f})")逻辑说明:torch.hub.load会从本地缓存加载模型,custom表示加载你训练好的自定义权重而不是官方预训练模型。model.conf、model.iou、model.max_det是在模型内部设置推理参数,这样每次调用model(img)不需要重复传参。results.xyxy[0]返回的是第一张图片的检测结果,格式为 [左上角 x, 左上角 y, 右下角 x, 右下角 y, 置信度, 类别 id],这是做业务逻辑时最常用的数据源。
这套代码跑通后,你可以直接在循环里读摄像头帧,把cls_id == 0(未戴帽)的框单独挑出来触发告警。安全帽检测项目从“能出框”到“能落地”,差的就是这一步业务绑定。
5. 训练安全帽模型的常见问题排查:翻车最多的地方就这几处
5.1 报错 “No labels found in ...”
现象:训练刚开始,终端提示某个目录下没有找到标签,或者训练直接中断。
原因:绝大多数情况是图片和标签目录命名不匹配。YOLOv5 读取标签的规则是:把你的--data里 train 路径(指向 images)中的images替换成labels,再去找同名 txt。如果你把标注放在annotations目录而不是labels目录,或者图片目录叫train_img、标签目录叫labels,系统就会认为是空标签。
解决:统一目录结构为images/train、labels/train,并且确认每个图片文件名对应的 txt 文件名一致。批量替换目录名时用一段小脚本:
import os for root, dirs, files in os.walk('labels'): for f in files: if not f.endswith('.txt'): os.rename(os.path.join(root, f), os.path.join(root, f[:-4] + '.txt'))5.2 训练 Loss 正常,但检测时所有框都画在同一位置上
现象:训练过程 mAP 一直上涨,但推理时模型输出的框集中在图像中心或角落,完全没贴合目标。
原因:这是典型的标注坐标没有归一化导致的。如果 VOC 转 YOLO 时的宽高取的是像素绝对值而不是归一化值,模型会将 1~1000 的坐标当作 0~1 来处理,相当于只在图像左上角一小块区域里找目标。另一个可能是归一化时除的是 224 而不是真实宽高,同样会产生这种“集中出框”的怪现象。
解决:回到 2.2 节的转换脚本,核对x_center和y_center是否除以了img_w和img_h。转换完成后随意打开一个 txt 文件,确认所有数值都在 0~1 区间内。这条血泪经验说明:标注转换的验证环节不能省,宁愿多花 10 分钟抽查,也不要等训练完才发现数据是废的。
5.3 显存不足 OOM,批量训练直接崩
现象:训练几秒后报CUDA out of memory,即使降低 batch 也不行。
原因:常见是分辨率设置太高或 Mosaic 增强在缓存中间结果。很多时候 8G 显存跑--img 640 --batch 16是极限,但如果你同时开了很多程序,显存被吃满就会报 OOM。另外 Windows 下 PyTorch 的内存碎片化也会加剧 OOM。
解决:优先把 batch 降到 8,再不行降到 4;epochs 不变,训练时间会拉长但最终效果差别不大。还可以在训练命令里加--workers 0,减少数据加载进程占用的额外显存。若显存实在紧张(4G 以下),把--img 512,模型统一用 yolov5s。低显存运行模型的正确逻辑是优先缩 batch,而不是缩分辨率——分辨率影响检测精度,batch 只影响收敛稳定性。
5.4 戴帽子的漏检多,没戴帽子的误报多
现象:验证集 mAP 看起来还行,但视频测试里总是把反光背心、安全网误判成安全帽,或者远处戴白色帽子的人检测不到。
原因:一是类别样本不均衡,未戴帽样本偏少会让模型倾向把“无法确定”的框归类为戴帽;二是白色和黄色的安全帽在强阳光下与背景对比度低,模型学到的颜色特征不够稳定;三是训练图片里小目标占比太低,模型对 20×20 像素以下的帽子目标基本无感。
解决:先统计两个类别的标注数量,按比例补数据,head类比helmet类少时优先补未戴帽样本。然后在下采样增强上做调整:默认 Mosaic 会产生大量小目标,但如果小目标本身不足,反而会强化模型对大目标的偏好。可以用--hyp data/hyps/hyp.overfit.yaml降低增强强度实验一组对比。最后在训练里把--img 640提升到--img 896,通常小目标漏检会明显改善,代价是训练速度和显存占用增加。
5.5 训练过程中 loss 出现 NaN,后续指标全部失真
现象:训练到几十个 epoch 时,loss 突然变成 NaN,之后所有输出的检测框都是空的或全图满框。
原因:最常见的是学习率过高导致梯度爆炸,其次是标签值越界(坐标归一化后出现负值或大于 1),或者是训练图片里有全黑的纯色图。
解决:先查数据,把标签里所有超出 [0,1] 的值找出来并修复;然后降低初始学习率,往训练命令里加--hyp自定义配置,把lr0从默认 0.01 调成 0.005。如果是全黑图片导致的问题,直接删掉这些图。NaN 出现后最好的处理不是继续训练,而是从头开始,因为已经爆炸的权重很难自我修复。
6. 从权重到部署:导出 ONNX 并在树莓派/服务端跑安全帽检测的进阶技巧
6.1 导出 ONNX:让权重脱离 PyTorch 环境跑起来
训练好的.pt权重只能在 PyTorch 环境里用,生产环境更常见的做法是导出成 ONNX 格式,这样可以在不装 PyTorch 的服务器、树莓派上通过 ONNX Runtime 推理:
python export.py \ --weights runs/train/helmet_base/weights/best.pt \ --include onnx \ --opset 12 \ --simplify导出后同目录下出现best.onnx,大小通常比.pt小一些。--opset 12兼顾了旧版本兼容性和算子覆盖;--simplify会调用 onnx-simplifier 优化计算图,减少冗余节点。这一步最常见的坑是导出时报某些算子不支持,通常把 opset 版本调低或升级 onnx 库就能解决。
6.2 用 ONNX Runtime 在服务端推理:预处理不能丢
ONNX Runtime 推理的核心是:读图→letterbox 缩放到 640×640→归一化→输出→解析坐标→映射回原图。最容易漏的是 letterbox,直接 resize 会让目标宽高比失真,导致小目标检测能力下降。
import cv2 import numpy as np import onnxruntime as ort session = ort.InferenceSession('best.onnx') input_name = session.get_inputs()[0].name def letterbox(img, new_shape=(640, 640), color=(114, 114, 114)): shape = img.shape[:2] r = min(new_shape[0] / shape[0], new_shape[1] / shape[1]) new_w, new_h = int(round(shape[1] * r)), int(round(shape[0] * r)) dw, dh = (new_shape[1] - new_w) / 2, (new_shape[0] - new_h) / 2 resized = cv2.resize(img, (new_w, new_h)) canvas = np.full((new_shape[0], new_shape[1], 3), color, dtype=np.uint8) canvas[int(dh):int(dh + new_h), int(dw):int(dw + new_w)] = resized return canvas, r, dw, dh img = cv2.imread('site.jpg') padded, r, dw, dh = letterbox(img) blob = padded[:, :, ::-1].transpose(2, 0, 1).astype(np.float32) / 255.0 blob = np.expand_dims(blob, 0) outputs = session.run(None, {input_name: blob})这里的关键是字母桥(letterbox)的填充比例 r 和偏移量 dw/dh 必须保留下来,因为模型输出的坐标是相对于 640×640 画布的,要映射回原始图像尺寸时需要用它们做逆变换,否则画框位置会整体偏移。切换 BGR 和 RGB 通道也是 YOLOv5 推理的老坑——训练时图像是 RGB 顺序,读入时 OpenCV 默认是 BGR。
6.3 树莓派5部署与工地抓拍统计的落地思路
树莓派5上部署自己训练的 YOLOv5 模型是低成本边缘验证的经典路径。常见做法是把 ONNX 格式的权重配合 ONNX Runtime 在 ARM 上运行,CPU 推理一张 640 分辨率的图片大约需要 1~2 秒,做成定时抓拍而非实时视频流分析更现实。你可以把树莓派架在工地出入口,每 2 秒抓拍一张,把未戴帽结果通过 HTTP 上报到中心服务端。相比堆 GPU 服务器,这种方案单点成本低,但缺点是并发量有限,适合单路口或多路口的低速巡检。
服务端推理场景则可以进一步引入批量处理:把视频流按帧抽稀,只对画面中有人的帧做检测。先用一个轻量人头检测器过滤,再把裁剪后的区域送进安全帽模型,这样能把推理次数降一个数量级。安全帽检测项目做到这一步已经不只是“能识别”,而是真正具备了工程雏形。
验证部署效果的正确方式:跑一遍验证集并计算 mAP 和混淆矩阵。训练输出目录里的confusion_matrix.png能告诉你两个类别具体在哪些地方互相混淆。如果树莓派上同一张图片和 GPU 上的检测结果不一致,优先检查预处理是否一致——letterbox 的填充颜色、缩放比例、通道顺序差异是最大变量。不要拿模型的输出直接跨环境对比,先统一预处理逻辑再做比较。
我用这套方案做过多次落地验证,最大的教训永远是:先花 30 分钟检查数据路径、类别顺序和预处理一致性,再花 3 小时调模型。工具链已经很成熟了,真正拉开效果差距的从来不是模型本身,而是数据整理和部署细节这些“脏活”。希望这篇笔记能帮你把标题里的那套东西真正变成能演示、能交付、能继续迭代的成果。
本文还有配套的精品资源,点击获取