简介:面向施工现场、化工园区、厂区作业等需要实时监测安全帽佩戴的场景,基于YOLOv11n训练的安全帽检测模型包可直接用于二次开发,供算法工程师与安防集成商复用。包内共9个文件,压缩包约49.47MB,包含通用部署所需的.onnx、.pt格式,适合GPU或通用深度学习框架;也有面向瑞芯微RK3588/RK3576平台优化的.rknn格式,以及配套的.bin权重、.xml网络结构、.yaml配置,便于在嵌入式NPU或边缘设备上高效运行。用户可根据实际硬件环境选择对应文件,并结合视频监控、智能安全帽、施工现场人员管理或无人机巡检等系统,实现实时或定时的未佩戴安全帽检测、提醒与记录,降低人工巡查成本。YOLOv11n兼顾速度与精度,能够及时识别违规情况,有效辅助管理人员预防高处坠落等事故。已有369人学习下载,适合有目标检测基础、正在做工业安全落地的开发者快速接入。
1. 安全帽检测模型yolo11n:为什么我推荐用n级小模型而不是l级大模型
在工地安全帽检测这件事上,yolo11n这个最小的模型往往比yolo11l更实用。这不是玄学,而是由部署环境决定的:摄像头旁边通常就是一台几百块钱的边缘盒或旧工控机,算力有限,大模型推理速度跟不上,而yolo11n在保持较高mAP的同时,能把单帧检测压到几十毫秒。我手上这份资源就是一套用yolo11n训练好的安全帽检测模型,包含训练权重、标注转换脚本和部署配置,适合正在做工地智能监控、出入闸机安全合规检查的开发者。如果你正想用yolo11做安全帽识别,又纠结从哪个版本下手,这篇笔记能帮你在数据准备、训练调参、落地部署三块少走弯路。
2. yolo11n网络结构拆解:轻量化Backbone与解耦头在安全帽场景的适用性
2.1 C3k2与SPPF:轻量化Backbone的设计意图
yolo11n的Backbone用C3k2模块替换了前代常见的C2f模块。从名字就能看出来,C3k2本质上还是CSP结构,但中间瓶颈层的通道数被压得更低,并用两个并行分支做梯度融合,计算量一下子降了下来。对于安全帽这种类别只有两类、目标特征又比较单一的任务,这种设计是合适的——不需要几百类大模型那样的容量,硬上大模型只会让训练更慢、部署更卡。
在640x640输入下,yolo11n的FLOPs大约在6.5G左右,参数量2.6M级别。对比yolo11l的几十G FLOPs,一个边缘设备上的差距可能是实时和卡顿的区别。我一般拿到模型会先跑一次模型信息统计,确认参数量正确再开始训练。标准训练框架的CLI里可以直接打:
yolo model=yolo11n.pt mode=val data=helmet.yaml这条命令会加载预训练权重并执行一次验证,日志第一屏会打印模型层数、参数量和GFLOPs。如果你的框架版本不打印,也可以写个两行脚本调用内建的模型信息接口,输出参数量和每层输出张量形状,用作后续改通道数的参照。
SPPF模块在yolo11n里继续保留,作用是把Backbone输出的特征图做多尺度池化拼接,让后续Neck能同时拿到全局和局部信息。安全帽检测有个典型情况:摄像头装在高处,人离镜头远,帽子在画面里常常只有二三十像素。SPPF能保住一部分小目标语义信息,但真正解决小目标问题还要靠后面讲的数据切图和输入分辨率调整。
2.2 解耦检测头:分类与回归不再共享卷积
yolo11n的检测头采用了Anchor-Free的解耦设计,分类分支和回归分支各自用不同的卷积输出。分类分支只负责判断“这是戴帽还是没戴帽”,回归分支负责输出框的中心点、宽高。解耦的头结构比早期YOLO那种全连接共享头更容易训练,收敛也稳定得多。
安全帽场景里,两个类别在视觉上高度相似:都是人头附近的一个半圆形物体。区别只在于颜色、纹理和反光。如果分类和回归共享卷积,模型很容易被边框回归的梯度带偏;解耦之后,分类分支可以独立学习“圆顶+系带”的细节,回归分支单独学框的贴合度。我经常在日志里看到回归的CIOU loss已经降到零点零几,而分类的BCE loss还在缓慢下降,这正是解耦头在独立优化分类特征的信号。
你不需要改模型结构,只需在数据配置里定义好类别数。一个典型的helmet.yaml是这样:
path: /data/helmet train: images/train val: images/val names: 0: with_helmet 1: without_helmet这里把“戴安全帽”和“未戴安全帽”作为两个类别,而不是只检测“安全帽”一类。实际部署中,两类输出的好处是你可以分别调阈值:戴帽误报影响小,未戴帽漏报才是大问题。于是通常会把without_helmet的置信度阈值调低一点,把戴帽的阈值调高一点,这个技巧在第五章展开说。
2.3 从n到x:用一张表决定安全帽场景该选哪个版本
yolo11提供了n、s、m、l、x五个规模,我直接按安全帽场景的实际体验整理成下面这张表:
| 版本 | 参数量(约) | FLOPs(约) | 典型推理环境 | 安全帽场景建议 |
|---|---|---|---|---|
| yolo11n | 2.6M | 6.5G | 边缘盒、手机、Jetson类设备 | 首推,精度够用且速度快 |
| yolo11s | 9.4M | 21.5G | 嵌入式高端板卡、低端GPU | 精度略高,但部分设备吃紧 |
| yolo11m | 20.1M | 68.0G | GTX 1060以上独显 | 小目标多的复杂场景备用 |
| yolo11l | 25.3M | 86.9G | 中高端GPU | 不建议,部署成本高 |
| yolo11x | 56.9M | 194.9G | 服务器级GPU | 只有测试才用,不做产品 |
这个表里的数字都是公开benchmark里可查的,具体版本稍有差异不影响选型。我的经验是:安全帽检测的难点不在类别数量,而在小目标和遮挡。yolo11n的backbone下采样比大模型更激进,但通过把输入分辨率从640提到960,或者做切图训练,小目标问题可以明显缓解。同理,如果你跑yolo11x,高分辨率下显存占用会成倍增长,反而不如n级配合高分辨率输入来得灵活。
选型结论:只要推理设备不是服务器,无脑先试yolo11n。如果现场误检实在压不住,再考虑yolo11s。我接触过的实际项目里,90%的工地安全帽检测最后都落在了yolo11n+960分辨率这个组合上。
3. 安全帽数据准备:VOC转YOLO的转换脚本与四类易错标注
3.1 数据来源与标注规范
训练安全帽检测模型,数据来源我一般分三块:一是现场监控截图,这是最贴近真实场景的,但需要人工清洗;二是公开的工业安全帽数据集,能快速补充样本量;三是自己拍的模拟场景,比如戴着不同颜色的棒球帽、工程帽在车间走动,补足各类姿势和光照。
标注工具推荐用LabelImg,保存为VOC格式的XML文件。项目里只保留两个类别名:with_helmet和without_helmet。这里有个容易踩的坑:安全帽和普通帽子的区别不只是“有没有帽子”,而是“有没有安全帽的帽壳和系带”。有些标工人会把棒球帽也标成with_helmet,导致模型学到的特征是“头部有物体”,而不是“有安全帽”。我处理时会把棒球帽、草帽、遮阳帽这类直接归入without_helmet或者丢弃,避免语义混淆。
标注框也有讲究。安全帽框要贴着帽壳边缘,不要包含肩膀。很多人为了省事把上半身一起框进去,结果模型学习的是“人+帽子”的组合特征,一旦人只露半身就漏检。正确的框是包住帽壳最外层边缘,下边切到额头或眉毛位置。
3.2 VOC转YOLO格式脚本
VOC的XML里存的是绝对坐标,而YOLO训练需要的是相对于图片宽高的归一化中心坐标。转换脚本不复杂,但边界情况很多。下面这版是我在实际项目中完整用过的:
import os import xml.etree.ElementTree as ET from glob import glob CLASS_NAMES = ['with_helmet', 'without_helmet'] def voc_to_yolo(xml_path, out_dir, class_names): os.makedirs(out_dir, exist_ok=True) tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find('size').find('width').text) img_h = int(root.find('size').find('height').text) lines = [] for obj in root.findall('object'): name = obj.find('name').text if name not in class_names: print(f"[skip] {xml_path}: unknown class {name}") continue cls_id = class_names.index(name) box = obj.find('bndbox') xmin = float(box.find('xmin').text) ymin = float(box.find('ymin').text) xmax = float(box.find('xmax').text) ymax = float(box.find('ymax').text) x_center = ((xmin + xmax) / 2) / img_w y_center = ((ymin + ymax) / 2) / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h x_center = max(0.0, min(1.0, x_center)) y_center = max(0.0, min(1.0, y_center)) w = max(0.0, min(1.0, w)) h = max(0.0, min(1.0, h)) lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") if lines: out_path = os.path.join(out_dir, os.path.basename(xml_path).replace('.xml', '.txt')) with open(out_path, 'w') as f: f.write('\n'.join(lines)) print(f"[write] {out_path}") else: print(f"[warn] {xml_path} has no valid objects, " "output file not written") for xml_path in glob('data/helmet/xmls/*.xml'): voc_to_yolo(xml_path, 'data/helmet/labels', CLASS_NAMES)这段脚本的核心是三个动作:解析宽高、遍历object、计算归一化坐标。代码里的max(0.0, min(1.0, ...))不是多余的,很多标注工具导出时允许框超出图像边界,如果不裁剪,YOLO训练时会出现loss为NaN或者框漂移。对于无效目标,我没有写空txt,而是直接跳过,这样训练集里不会混进没标注的文件。
参数说明:CLASS_NAMES的顺序必须和训练配置里的names顺序完全一致,否则类别ID错位,模型会学出一个混乱的分类映射。out_dir建议和图片目录平级,YOLO框架默认会按图片路径去同级目录找同名txt,例如images/train/001.jpg对应labels/train/001.txt,这个对应关系要保持。
3.3 验证转换结果:可视化脚本
转换完不能只看txt内容,必须把框画回图像上对比一眼。画图脚本如下:
import cv2 def draw_yolo_boxes(img_path, label_path, class_names, out_path): img = cv2.imread(img_path) h, w = img.shape[:2] with open(label_path) as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue cls_id = int(parts[0]) x_c = float(parts[1]) y_c = float(parts[2]) bw = float(parts[3]) bh = float(parts[4]) x1 = int((x_c - bw / 2) * w) y1 = int((y_c - bh / 2) * h) x2 = int((x_c + bw / 2) * w) y2 = int((y_c + bh / 2) * h) color = (0, 255, 0) if cls_id == 0 else (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, class_names[cls_id], (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imwrite(out_path, img) print(f"save to {out_path}")这里要注意OpenCV的坐标系统:原点在左上角,x向右,y向下。算出的x1, y1是整数,OpenCV画矩形时x2, y2是右边界,所以框是闭合的。如果y1小于0,putText会抛出异常,所以用max(0, y1 - 5)做保护。跑完脚本后,随机抽20张图看一遍,重点看框是否贴帽沿、是否覆盖了全部目标、有没有目标漏标。
3.4 小目标增强:切图训练与Mosaic策略
安全帽在1080P画面里经常只有二三十像素,直接resize到640x640会缩成几个像素,成了典型的小目标问题。我在项目里常用的做法是切图:把大图按640x640滑窗裁剪成块,块与块之间重叠50像素,然后只保留包含标注框的块。切图后样本数量会成倍增加,同时目标在块中的相对尺寸也变大了,模型更容易学到帽子边缘纹理。
切图脚本里有一个关键参数:过滤阈值。如果某个块里目标面积小于块面积的0.3%,这个目标基本不可见,我一般直接丢掉,否则会给训练集灌入噪声。Mosaic增强在yolo11的标准训练里已经默认开启,它能在一张合成图里塞进4张图,让模型看到不同尺度的目标。但如果你用的是切图训练,Mosaic反而可能把已经变大的目标又拼小,所以我通常会在切图后关闭Mosaic,用额外的复制粘贴增强替代。
4. 训练yolo11n的常见问题排查:五类翻车现象与调参对策
4.1 显存不足,训练直接退出
现象:训练启动大约几轮后,终端报CUDA out of memory,进程崩溃。
原因:安全帽场景往往用高分辨率输入,比如1280x1280,再加上batch size比较大,显存瞬间被打满。yolo11n虽然参数少,但特征图在输入端占用显存并不小。
解决:先把batch size降到8甚至4,然后把输入分辨率降到640。在标准训练框架里,一条训练命令长这样:
yolo detect train data=helmet.yaml model=yolo11n.pt \ epochs=100 imgsz=640 batch=8 workers=4如果降batch还不够,可以开启梯度累积,让模型每4个小batch累积一次梯度再更新权重。梯度累积在框架里对应accumulate参数,设置成4之后,显存占用大约等价于batch=2,但训练效果接近batch=8。注意梯度累积只影响更新频率,不改batch内数据分布,所以降低batch后要把学习率也按比例调低一点,否则loss会震荡。
4.2 loss震荡不收敛
现象:训练前50轮loss上下跳动,val mAP一直在零点几附近徘徊,不上升。
原因:最常见是学习率太高,yolo11n是轻量模型,参数少,同样学习率下对大模型合适,对小模型就偏大。另一个原因是标签噪声太大,比如之前说的把棒球帽标成安全帽。
解决:先把初始学习率从默认的0.01降到0.005,并把warmup轮数提高到5轮。如果数据集很脏,我还会冻结Backbone前10轮,只训练检测头,等分类分支稳定后再解冻全部层。冻结的命令是:
yolo detect train data=helmet.yaml model=yolo11n.pt \ epochs=100 imgsz=640 batch=8 freeze=10freeze=10的意思是前10轮冻结Backbone,只更新Neck和Head。解冻后模型会快速适应数据,但学习率要同步调低,否则容易破坏预训练特征。另外检查一下数据集里每个类别有多少张图,如果没有戴帽和未戴帽比例超过5:1,先在数据层面做sampling均衡,别指望loss自动修好。
4.3 把轮胎、水桶误检成安全帽
现象:训练完在测试视频里,模型把地上的轮胎、红色灭火器、甚至是圆形铁桶都框出来,置信度还不低。
原因:yolo11n本身是Anchor-Free的,圆形物体天然容易激活检测头。如果训练集中负样本(没有安全帽但类似圆形的物体)太少,模型只能学到“圆形+高饱和度”这种粗特征。
解决:回到数据层面,收集一批“干扰物”图片,里面包含轮胎、铁桶、球类、安全帽背景里的圆形元素,标注成背景或者干脆不标注,作为负样本加入训练。在训练配置里,可以调高类别权重cls_pw,让模型更关注分类错误。我一般会把cls_pw从默认的1.0调到1.2,加大分类loss的惩罚。同时,部署时对with_helmet类别设置一个更高的置信度阈值,比如0.6,高于默认的0.25,能有效压掉大部分圆形误检。
4.4 远距离小目标漏检
现象:人离摄像头远的时候,戴安全帽但模型没检出,靠近了却正常。
原因:yolo11n在20x20的特征图上负责大目标,80x80特征图负责小目标,但安全帽太小,经过32倍下采样后只剩几个像素,特征图里基本没有对应响应。
解决:最见效的是把imgsz从640提到960或1280。训练和推理用同一尺寸,mAP通常能涨3到5个点。如果设备推理慢,就改成训练时用1280,推理时切成两块或三块分别在设备上跑。另一个手段是使用切图推理:把原图按640切成块,逐块检测后再映射回原坐标。这种方法我常用在工地出入口的1080P视频上,效果比单纯提分辨率更稳定,因为小目标在块里相对变大了。
如果还想改进模型本身,可以尝试在Neck里增加P2层(80x80上面的160x160特征图),但yolo11n标准结构没有P2,需要自己改代码。对大部分安全帽项目来说,切图+高分辨率已经足够,没必要动网络结构。
4.5 训练完精度反而低于预训练模型
现象:用yolo11n.pt预训练权重开始训练,几十轮后mAP反而不如直接用预训练权重跑测试集。
原因:预训练权重是COCO类别的,它本身能把人、帽子、头这类通用物体检出来。如果你的安全帽数据集只有几百张,微调时模型在大量无关旧特征上发生灾难性遗忘,精度就掉了。
解决:先把epoch减到30,让模型在小数据集上快速收敛;同时用更低的初始学习率,例如0.001,防止预训练特征被剧烈改写。如果数据集实在太小,可以采用冻结Backbone策略,只训练Head。另一个有用的做法:先在不含负样本的干净子集上跑一轮,确认模型能拟合训练集,再把所有数据加全。这样能区分是数据问题还是调参问题。
现象:训练日志显示loss在最后几轮还在下降,但val mAP没有变化,甚至微微下降。
原因:过拟合。安全帽场景里训练集和实际场景的光照差异大,模型记住了训练集里的背景纹理。
解决:开启数据增强中的HSV抖动和随机平移缩放;将epochs减到50并使用早停机制,框架默认会在连续20轮mAP不提升时停掉;如果已经过拟合,把训练集里重复度过高的连续帧去掉,一般监控视频里相邻10帧的标注几乎一样,去重后能减少模型死记背景的机会。
5. 从训练到部署:yolo11n导出ONNX与边缘推理的量化避坑
5.1 导出ONNX并验证输出一致性
训练完成后,先用best.pt导出一个ONNX文件用于部署测试:
yolo export model=runs/detect/train/exp/weights/best.pt format=onnx opset=12 dynamic=True simplify=Trueopset=12是兼容性和功能性的折中点,太低的opset不支持某些算子,太高的opset在老旧推理引擎里反而跑不起来。dynamic=True让输入尺寸可动态变化,这样部署时可以灵活调整分辨率。simplify=True会用图优化工具把一些冗余算子合并,文件大小会小一点。
导出后必须对比PyTorch模型和ONNX的输出,不能只验证推理时间。写一个简单的对比脚本,分别用两种方式跑同一张图,比较输出框的坐标差。如果坐标差大于0.005个相对宽度,说明算子转换出了问题,优先检查Focus层或SPPF在ONNX下的实现。yolo11n在导出时偶尔会遇到个别算子的Reduce维度问题,解决办法是换一个opset版本重试。
5.2 在边缘设备上转成NCNN格式并量化
如果你的部署环境是嵌入式端或者电脑上没有CUDA,ONNX不一定适合直接跑,转成NCNN格式会更轻量。常见流程是先用ONNX转NCNN,再做int8量化校准:
onnx2ncnn model.onnx model.param model.bin ncnnoptimize model.param model.bin model_opt.param model_opt.bin 0第二步是优化网络结构,0表示不fp16混合量化;如果你的设备支持VFP16,可以换成65536,但安全帽检测精度优先,我建议先不做fp16,保留fp32跑。量化校准命令:
quantize-ncnn-model model_opt.param model_opt.bin model_int8.param model_int8.bin images.txt这里的images.txt是校准图片列表,不需要带标注,但尽量覆盖多种光照下的安全帽场景。校准图片数量我一般用200张,太少会导致量化后误检剧增。量化后一定要重新跑一遍验证集,int8模型mAP通常比fp32低1到2个点,如果降太多,先检查校准图片是否和真实场景分布一致。
5.3 部署技巧:对戴帽、未戴帽分别设置阈值
在安全帽检测场景,两类错误的代价完全不对等:戴帽被误报成未戴帽,会造成虚警;未戴帽被漏检,则是安全事故。所以我会在部署代码里分别设置两个阈值,而不是用同一个值。对without_helmet设0.35,保证漏检率低;对with_helmet设0.65,压低误报率。这样即使有人从远处走过,只要帽顶特征模糊,模型倾向于不给出“未戴帽”的高置信度判断,避免后台频繁误报。
我经历过一次翻车:第一次部署时全局用0.25的阈值,结果视频里每隔几秒就把地上的影子或反光误报成未戴帽,现场值班人员三天就骂了两次。后来我把阈值分离,并加了“连续3帧检测到未戴帽才报警”的逻辑,误报率直接降到原来的十分之一。从那以后我每次部署安全帽项目,都会强制走一遍阈值分离+多帧确认这个流程,再急也没跳过。
如果你正在用这份资源做安全帽检测,训练好的yolo11n权重可以覆盖大多数工地出入口和室内作业场景,但请务必在自己的测试集上跑一遍,注意光线剧烈变化和大逆光环境。希望帮到你。
本文还有配套的精品资源,点击获取