在工地上管安全,真的不能只靠人盯着。
我做工业视觉这块有些年头了,最头疼的往往不是算法精度上不去,而是甲方的一句话:“我们装了摄像头,但还是要靠人盯屏幕,眼睛都快瞎了也拦不住违规。” 这句话背后,其实就是全天候、自动化的工人个人防护装备(PPE)佩戴检测需求。安全帽、反光衣,这两样东西在石化、建筑、电力巡检现场,就是保命符。用传统的图像识别做,光照一变、姿态一换,误报能烦死人;用YOLOv8这类老模型,精度和速度的平衡总觉得差口气。
最近我把一个实际交付的项目做了模型升级,核心就是用YOLO11-C3k2-MambaOut 这套组合来解决 PPE 检测和违规识别。项目跑了一阵子,效果很稳。这篇文章不聊虚的,直接把我从需求拆解到落地部署、调参、踩坑的过程拿出来分享一下,尤其是C3k2和MambaOut这两个模块在真实场景里到底带来了什么,给大家一个参考。
1. 项目核心需求与整体设计思路
先别急着谈模型结构,搞工程的第一件事是把需求问透。这个项目的甲方是某大型在建园区的施工总包,他们提供固定机位摄像头的 RTSP 视频流,要求实时识别两类核心违规:未佩戴安全帽、未穿反光衣。听起来简单,但细化下去全是坑。
1.1 现场工况带来的三个硬指标
第一,距离远、目标小。摄像头装在杆子上,覆盖几十米范围,工人肩膀以上的像素可能只有几十个点,这就是典型的小目标场景。第二,视角刁钻,俯视角度下安全帽的纹理信息很少,容易和背景里的圆形物体混淆。第三,光照剧变,工地室外有太阳直射、逆光、阴影交替,反光衣的荧光色在不同光照下的颜色偏移非常大。
基于这些,我对模型的要求定得很明确:mAP@0.5 要过 95%,mAP@0.5:0.95 要过 80%,单张 640x640 推理时间不超过 20ms(GPU),同时模型参数量不能太大,方便后续部署到现场的边缘盒子。这三个指标同时满足,其实不太容易。
1.2 为什么这次选了YOLO11而不是其他方案
之前我也试过两阶段的检测器,精度是高,但速度在边缘设备上跑不动。也试过YOLOv8,整体不错,但在小目标召回率上有些平庸。YOLO11出来以后,第一眼看到 C3k2 结构时,我就意识到它在保持轻量化的同时,对特征融合的利用率比 C2f 高不少。再加上我一直在关注Vision Mamba 这条技术路线,看到有团队把 MambaOut 模块塞进YOLO里做性能增强,这种组合很对我胃口——既有传统CNN的稳健,又有新结构对长距离特征的抓取能力。
整个方案从架构上分为三块:视频流接入与管理模块(负责 RTSP 拉流、断线重连、抽帧)、核心检测模型(YOLO11-C3k2-MambaOut,负责框出人、安全帽、反光衣)、违规判定逻辑模块(基于检测框做 IoU 匹配与逻辑规则判断)。
方案选型的核心逻辑,是解决“稳定”二字。工地上网络波动大,摄像头码率不稳,模型再强,输入流断了也是白搭。这个后面部署部分细说。
2. 核心模型拆解:C3k2与MambaOut模块在检测任务中的实战意义
好多朋友问我,YOLO11 不是已经有 C3k2 了吗,你再加个 MambaOut 进去,是不是为了蹭热度?还真不是。原因下面细说。
2.1 C3k2结构解决了PPE检测的什么问题
C3k2 是 YOLO11 在 backbone 和 neck 部分替换掉 C2f 的关键模块。它的核心变化,是把原来 C2f 中复杂的多分支结构收敛了一下,同时利用类似 C3k 的 kernel size(比如 3x3)卷积来提取特征。
在 PPE 场景里,C3k2 带来的直观感受是特征的错位信息更敏感。安全帽的特征是顶部反光曲面加上帽檐的直线边缘,这些特征在高维空间里的组合非常微妙。C3k2 通过深度可分离卷积对高维特征做更充分的交互,让模型在拟合时更容易捕捉到帽檐与外轮廓的强边界,顺带减少了一些参数冗余。
另一个关键是,C3k2 中卷积核尺寸的选择和感受野有关。我们最终在 backbone 用到的是 C3k2,在 neck 部分用了 C3k2-Faster 这种变体。Faster 版本引入了一种部分卷积的设计,把计算成本压低,但保持了多尺度特征的流动性。对于反光衣这种常常只有局部出现在画面里的目标,neck 层的特征传递质量直接影响召回率。
2.2 MambaOut模块是不是噱头?它到底在加什么能力
很多人看到 MambaOut 以为是把状态空间模型(SSM)硬搬进来。其实 MambaOut 的思路更巧妙:它把 Mamba 这种结构拆解为堆叠的 MLP 与残差连接,验证了单纯的“Out”部分(即无状态空间约束的 MLP 路径)在大规模视觉任务上也能work。
放到我们这个项目里,效果是实打实的。原本的网络在遇到人和脚手架混在一起、反光衣被钢管遮挡一部分的场景时,会因为局部特征不完整而漏检。MambaOut模块通过扩大感受野和强化远程依赖建模,让模型在决策时能捕捉到更广阔的上下文。
实操反馈就是:漏检率下降了不少,尤其是那种“半个身子被遮挡,只露出一个衣角”的工人,模型能根据周边人体关键点特征判断出这是个人且穿着反光衣。这种对上下文的利用,属于隐式的注意力机制,工程上非常好用。
2.3 模块集成之后的实际结构变化
我们在 YOLO11m 的基础上做了结构上的微调,其中最关键的是在骨干网络的第4层(最大特征分辨率较小的那一层)后叠加了一个轻量化的 MambaOut 模块,同时在检测头之前的 neck 部分引入了瓶颈注意力模块。
这里要特别提一个配置细节:C3k2 的 shortcut 连接和正则化。工地的视频流清晰度参差不齐,网络一旦深了,梯度流容易出问题。C3k2 的残差结构能解决梯度传递,但要注意在 MambaOut 模块中控制 dropout,我设置的是 0,防止过拟合的同时也保证推理阶段的确定性。
3. 数据集构建与标注细节:这是整个项目里最花时间的部分
如果只拿公开数据集来训,部署到现场十有八九会翻车。PPE检测的数据集市面上是有开源的,比如 SHWD 安全帽数据集,但那些数据普遍背景干净、视角单一。真实工地的场景多样性,必须靠自采数据来补。
3.1 数据采集策略与标注规范
我们花了两周时间,从现场 12 个摄像头截取了不同时间段(早晚低照度、正午强光、阴天)、不同天气(小雨、雾霾)的样本,一共筛选出 2.3 万张有效图片。标注类别设了 3 个:person、helmet、vest。这里有一个关键决定:没有单独设“未戴安全帽”这个类别,而是靠检测出 person 后与 helmet 做匹配来判定违规。
这么做的原因是,未戴安全帽是一个否定的状态,如果用单独的类去训练,模型很难学;而用正样本类去做逻辑判断,准确率和可解释性都更高。标注格式用的 YOLO 标准 txt 格式。
标注时要注意细节,不然模型会学歪:
- 被遮挡的目标一定要标注,哪怕只有一点点头部露出,也要画出框来。
- 像素极小的目标,比如 20 米外的背影,必须标出来。模型见多了,才知道小尺度长什么样。
- 反光衣的反光条不要单独标注,整体按 vest 区域框住即可。
3.2 数据增强与类别均衡处理
现场正样本(有戴帽、有穿反光衣)远远多于负样本(没戴帽),如果不做处理,模型会变得非常“保守”,倾向于把啥都预测成正样本,导致漏报安全隐患。我用的策略是:
- YOLO 自带的 Mosaic 增强,搞到每次迭代的最后一轮的 5% 关闭,防止过度增强导致语义漂移。
- 混合使用随机透视变换和 HSV 扰动,重点调高了 S(饱和度)的幅度,模拟雨天和阳光的色偏。
- 针对负样本不足的问题,我们从公开数据里裁剪出大量单独的“人”区域,合成到工地背景图中,相当于人工造违规样本。
最终训练集 2.1 万张,验证集 2000 张,测试集 2000 张,类别分布基本达到 1:1:1(person:helmet:vest)。
3.3 标注质量把关
标注这活最怕团队不统一。我定了一个坐标系的硬性规则:所有框必须贴边,即使用户只露出一只胳膊,框也要从胳膊的外缘画起,不能留白太多。因为MambaOut这类模块对特征的全局上下文很敏感,框与框之间的 IoU 噪声会直接影响正样本的分配质量,所以在这步多花点时间校验是很有价值的。
4. 模型训练细节与性能优化实录
进入训练阶段,反复调参是不可避免的。这里记录的是最终稳定下来的配置和中间踩过的坑。
4.1 训练超参数详解
用 2 张 GPU 并行训练,模型结构文件在 ultralytics/ultralytics/cfg/models/11/yolo11m.yaml 基础上修改。重点参数配置如下:
| 参数名 | 值 | 说明 |
|---|---|---|
| 输入尺寸 | 640x640 | 兼顾小目标与推理速度 |
| Batch Size | 64(单卡32) | 受显存限制,梯度累积保证等效batch |
| Epochs | 300 | 数据量不算大,300轮足够收敛 |
| 初始学习率 | 0.01 | 配合余弦退火调度 |
| 权重衰减 | 0.0005 | 防止大参数模型过拟合 |
| 数据增强 | Mosaic 1.0 / MixUp 0.2 / HSV 大幅扰动 | 增强鲁棒性 |
| 优化器 | AdamW | 比SGD收敛快,配合自定义MambaOut层 |
训练到第 200 轮左右时,我对比了三个版本的 backbone:原版 YOLO11m、只换 C3k2 不改 MambaOut、加上 MambaOut。直接列结果:
| 模型版本 | mAP@0.5 | mAP@0.5:0.95 | 参数大小 | 推理耗时(GPU) |
|---|---|---|---|---|
| YOLO11m 原版 | 93.6% | 77.2% | 20.1M | 18ms |
| YOLO11m + 注入C3k2优化 | 94.9% | 79.8% | 19.4M | 19ms |
| YOLO11m-C3k2 + MambaOut | 96.3% | 82.1% | 21.7M | 22ms |
MambaOut 给精度带来约 1.5 个点的提升,推理时间多 3ms,这个代价在可接受范围内。最终模型 21.7M 的参数量放到边缘设备里,跑 30 FPS 没问题。
4.2 训练中的两个关键观察点
第一个是损失函数曲线的抖动。在 80 到 120 轮之间,box loss 出现过阶梯式下降,我开始以为是数据里有脏标注,排查后发现是因为 MambaOut 的 MLP 层在训练中后期激活模式发生了变化,导致梯度的分布陡变。解决办法是给 MambaOut 那一层单独加了梯度裁剪,设置 max_norm=0.8,曲线立马顺滑了。
第二个是小目标漏检的专项调优。光靠调主网络不够,我把检测头的输出尺寸做了调整,专门增加了一个针对小目标的预测头(P2 特征层),并从 neck 输出位置接了一个额外的 4 倍下采样分支。为了保持推理速度,只在部署时启用这个分支。
5. 违规识别逻辑与边缘端部署实战
模型训出来只是第一步,把它做成一个真正能产生安全价值的系统才是重点。
5.1 核心违规判定:从检测框到告警
模型输出的只是三类 Bbox,怎么判断是“违规”?我的逻辑遵照一个简单规则:
未佩戴安全帽 = 存在未与任何 helmet 框 IoU 匹配成功的 person 框
匹配规则是 IoU 大于 0.3 且 person 框的中心点落在 helmet 框的顶部或内部。这里有个陷阱:如果工人弯腰,头盔可能不在头顶,IoU 会变成 0。所以我在逻辑里加了基于人体关键点信息的前置判断:如果 person 框的头部位置(框的上部三分一区域)出现了遮拦物,就先不算违规,等姿势恢复后再判断。
未穿反光衣的判定就更直接了,只需要看 person 框与 vest 框的 IoU(阈值设在0.2)以及 vest 框中心点是否落在 person 框的区域中下部。
5.2 工程化部署:RTSP流处理与断线重连机制
现场接 16 路 RTSP 流,我用了多线程处理。这里要提醒:不要每个摄像头单独起一个火焰模型的进程,内存容易爆,我采用了一个共享 CUDA context 的并发推理方案,跑在带 8G 显存的嵌入式 GPU 上。
具体流程就是:拉流线程将帧放入环形队列 → 检测线程批量推理 → 后处理线程做上述违规逻辑判定 → 告警线程抓图并上报。中断自动拉起重连这一块也很关键,采用指数退避策略,并丢掉积压的帧防止延迟无限增大。
对过曝、极暗的环境,我在入口处做了自适应直方图均衡化,实测下来保证了夜间低照度下的召回率不低于 90%。
5.3 模型转换与加速
模型跑 PyTorch 原生还是慢。最后我导出为 ONNX,再用 TensorRT 加速。TensorRT 的 int8 量化在这个任务上踩了坑:安全帽的圆形边缘在 int8 下掉点严重,mAP 直接掉了 1.2%,最后退回来用 16 精度保存,速度依然比 PyTorch 快 2.3 倍,且精度无损。
若你们部署环境是英伟达 Jetson 系列,建议直接用 TensorRT 的 trtexec 工具转换,并记得开启动态 shape,方便处理不同分辨率的输入。
6. 常见问题与排查技巧:现场运行中的七大坑
这里把现场实施中遇到的真实问题和排查思路整理成速查表,希望能帮大家少走两周弯路。
| 问题现象 | 可能原因 | 排查与解决 |
|---|---|---|
| 未戴帽子的工人频繁漏报 | 头部目标过小,非极大值抑制打得太狠 | 降低 NMS IoU 阈值到 0.45,检查是否启用了 P2 小目标头 |
| 下雨天反光衣检测崩溃 | 雨水反光导致颜色特征漂移 | 增加 HSV 饱和度增强,模型重训一个雨天的分支权重 |
| 同一个违规工人连续告警十次 | 无帧间去重逻辑 | 引入简单的目标跟踪,设定 10 秒内的重复告警抑制窗口 |
| 安全帽与红色钢管混淆 | 背景颜色与安全帽颜色互相干扰 | 收集红色钢管样本,添加入口难例挖掘库 |
| GPU 利用率偶尔跌到 0% | 拉流线程读取帧时出现了阻塞 | 改用异步 IO 读取,并对读帧超时做丢弃处理 |
| 模型在早上和下午表现不一样 | 影子变化影响特征分布 | 增加多时段测试集,加入原图直方图校正层 |
| CPU 占用过高,导致前端卡顿 | 解码占用了大量资源 | 使用硬件解码,或者降分辨率到 960x540 后送入模型 |
6.1 调试中的经验补充分享
上面表格里有几条是常规文档里找不到的,展开多说一点。
关于重复告警:工地安全员最烦的就是同一问题刷屏,这种情况下他们对系统的信任度会直线下降。我用的方法很老但有效,给每个 track ID 设置一个“违规状态位”,只有当状态位从 false 变成 true 时才发告警;持续违规期间,每隔 5 分钟再报一次。这样既不会漏掉持续性隐患,也不会制造信息垃圾。
关于模型鲁棒性:最终版本我坚持使用了三个不同天气(晴天、阴天、雨天)采集的混合数据集训练出的一个统一模型,而不是分开训练再切换。统一模型的泛化力更好,而且部署时少了一层动态切换逻辑,简单可靠。
再有就是现场标注的持续采集:我建立了一个小闭环,安全员对系统的误报/漏报图片会随手打标回流,每月做一次增量训练。这个机制运行久了,模型对现场环境的适应力就越来越强,基本告别了环境变化导致的精度衰减。
7. 最后谈谈这套方案的边界与可扩展方向
这套 YOLO11-C3k2-MambaOut 组合,在 PPE 检测上确实打出了效果,但我得客观说一句:它并非万能。如果场景里工人密集到互相严重遮挡,或者有夜间无补光的情况,单靠视觉方案依然会力不从心,需要融合雷达或红外传感做多模态方案兜底。另外,当前方案只能感知“佩戴状态”,还不能完全判断“佩戴是否规范”,比如帽子歪到后脑勺、反光衣敞开不拉拉链,这类细节角度需要更细粒度的姿态估计模型来补足。
如果是想把这套东西往更深处扩展,我个人觉得有两个方向值得尝试。一个是把检测结果对接现场的智能广播系统,一旦发现违规,立刻在作业区域定向播报语音提醒,把“事后取证”变成“事中干预”。另一个是把违规记录与人员实名制闸机做联动,构建每天的安全积分体系,从管理层面倒逼工人养成习惯。
我把这套方案里的模型文件、训练日志和推理脚本整理了一下,已经放到了我的资源库里,需要的朋友直接拿走。后续我会针对 MambaOut 模块的结构细节单独写一篇解析,包括怎么调整 hidden_dim 和 depth 来适配更苛刻的边缘设备,以及如何使用知识蒸馏把 21M 参数的模型再压到 10M 以内,感兴趣的话可以持续留意。