ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

YOLO26实战智慧工地:安全帽检测误报率下降41%

YOLO26实战智慧工地:安全帽检测误报率下降41%

安全帽识别这个方向,如果你只看演示视频,会觉得早就已经“解决”了。我在几个智慧工地项目里折腾了大半年之后想说的是:演示集里那些画面端正、光照充足、人离镜头三米以内的样本,跟实际摄像头里那些站在三十米外、被塔吊钢丝绳挡住半边身子的人影,根本是两个物种。这次的实战项目,是把 YOLO26 引入到一套工地安全帽检测系统,专门针对远距离小目标、遮挡和不稳定光照做多维度优化,最终在真实视频流的在线评估中,把误报率从 5.19% 压到了 3.06%,换算成相对指标就是下降了 41%。

先说清楚,这不是拿一个公开数据集刷出来的分数,而是从数据采集、标注、训练、推理到视频流后处理,完整跑通的一条生产线。项目背景是南方某在建桥梁施工工地,摄像机装在塔吊臂、围挡角和临时板房顶,画面里既有近处 3 米内的工人,也有 40 米外的吊装区域。最头疼的问题不是“认不出安全帽”,而是认错:明明没戴帽子的人,因为背光、遮挡、像素模糊,被模型当成戴了帽子的正样本;或者在安全帽旁边有钢管、模板、安全网,模型被这些纹理带偏,给工人头顶硬生生画出一个不存在的“帽子框”。

这篇文章不是理论综述,而是把我在这个项目里踩过的坑、调过的参数、最后留下来能用的方案,按实战顺序拆开讲。你如果是做算法、做工地安防集成或者负责 AI 落地的,可以直接照着里面几个关键配置和流程去试;如果你只是对安全帽识别感兴趣,也能看明白为什么模型离线指标很漂亮,一上视频流就惨不忍睹。

1. 项目概述与“误报率下降 41%”的真实含金量

1.1 为什么远距离和遮挡才是工地常态

大多数开源安全帽数据集里的正样本,拍得都很“规矩”:人站得端正,帽子有完整的圆形轮廓,光线充足,背景相对干净。但工地现场的实际情况完全不是这么回事。

我让团队统计过一段 48 小时连续视频流里的目标尺寸,发现大量需要识别的人员在画面里实际高度只有 30 到 50 像素,安全帽本身往往只有 15 到 20 像素宽。这个尺寸对检测模型来说非常尴尬。YOLO 这类单阶段检测器在 640×640 输入下,小目标可能只占据特征图上的几个像素,经过几次下采样之后,特征基本被抹掉。更致命的是,当工人站在塔吊阴影里或者背对阳光时,安全帽和背景的对比度极差,模型的分类分支会因为缺少纹理线索,开始“猜”。

遮挡则是另一个主力误报来源。工地上到处都是钢管、安全网、脚手架、运输车辆,甚至另外一个人从镜头前走过,都会把目标遮掉一部分。有的情况是帽子只露出一半,模型把它判成“疑似帽子”;有的情况是帽子完全被挡住,但旁边有一条颜色相近的塑料膜或反光护目镜,模型就凭空产生一个高置信度的帽子框。这类“背景欺骗”在静态图片评测里很难暴露,因为评测集每张图都是独立判断,不会像视频流那样连续输出几十帧假警报。

所以,我接手这个项目的第一件事,不是调网络结构,而是把现场视频一帧一帧翻出来,统计到底哪些帧在误报、为什么误报。结论很直接:要解决远距离和遮挡,必须在数据、训练策略、推理方式、业务后处理四个层面同时下手,只靠加大模型参数量没有意义。

1.2 误报率的评估口径:不是拿 PR 曲线糊弄人

做检测算法的人习惯看 mAP、Precision、Recall,但甲方和现场安全员看的是一天弹出多少个假警报。如果模型在图片级评测里 Precision 有 98%,放到视频流里可能每分钟闪一次红框,因为一张 25 倍变焦的 4K 画面里,模型可能对背景里的多个疑似区域都输出了低置信度框。

这里必须先把“误报率”的口径说清楚。我们用的是帧级误报率加事件级复核的混合口径。帧级误报率的定义是:在一段连续视频流中,模型输出“戴帽检测”或“未戴帽警报”的帧数里,标注为错误警报的帧数所占比例。简单写出来就是:

误报率 = 误报帧数 / 总检测帧数

但这还不够。因为视频里同一个误报目标会连续出现在几十帧里,如果按帧统计,一次误报会被重复计数,指标会被放大。所以我更看重两个数字:一个是帧级误报率,用来反映模型单帧判断的稳定性;另一个是事件级误报次数,把连续多帧对同一个区域的误判合并成一次报警事件,这个更贴近现场真实体验。

这次优化最终的效果是:帧级误报率从 5.19% 降到 3.06%,相对下降 41%;事件级误报次数也从每天平均 27 次降到 11 次左右。这里要特别提醒一下,很多项目汇报喜欢把“相对下降”和“绝对下降”混着说,容易闹笑话。41% 是相对值,计算方式是 (5.19-3.06) / 5.19 ≈ 41%。如果你的基线本身很高,相对下降 41% 并不代表误报率绝对值小得惊人,它只能在相同评测口径下做横向比较。

指标优化前基线多维度优化后变化
帧级误报率5.19%3.06%相对下降 41%
未戴帽召回率92.7%94.1%提升 1.4 个百分点
平均检测精度89.2%92.4%提升 3.2 个百分点
单路视频推理耗时约 85ms/帧约 120ms/帧增加,但业务可接受

在线视频流的评估比静态图片严格得多,因为我们最终要接报警联动,误报会直接干扰安全员的工作。如果只看静态图片集,优化幅度可能远没有这么大,甚至某些改动在图片评测里看不出差别。这也是我想写这篇文章的原因:要解决真实场景的误报,不能只局限在训练环节,整条链路都要一起改。

2. 多维度优化总体设计:不是改一个参数,是改一整条链路

2.1 YOLO26 为什么适合做这个任务

选型时我对比过几个主流检测器,最后还是落在 YOLO26 上。原因并不复杂:在检测头设计上,YOLO26 延续了解耦头和动态标签分配这些相对成熟的机制,对不同尺度的目标都有不错的基础召回;同时它的训练效率高,可以在有限的 GPU 资源下快速迭代多个实验版本。我们用的是 YOLO26-M 这个规模,没有直接上最大模型,因为工地现场需要多路视频并行推理,延迟和显存占用都得考虑。

不过必须承认,YOLO26 官方权重在 COCO 这类通用数据集上表现不错,但拿到工地场景,如果不做任何调整,效果并不理想。COCO 里的小目标主要是街景中的行人、车辆、动物,尺寸分布和安全帽差异很大。安全帽是一个高度纹理化的小物体,而且类别间区别非常细:戴了帽子 vs 没戴帽子,在很多低分辨率帧里可能只有几个像素的差别。YOLO26 默认的 Mosaic、MixUp 增强和 640 训练分辨率,对这类任务并不友好。

因此,我们保留了 YOLO26 的主干和检测头架构,但在数据输入、训练分辨率、后处理和业务逻辑上做了大幅度改造。核心原则是:模型不是拿来即用的成品,它是整条识别链路中的一个环节,要围绕现场视频流的特性去适配。

2.2 四个优化层与分工

我把优化工作拆成四个层面:数据层、模型层、推理层、业务后处理层。这样做的好处是每项改动都能独立验证,不至于东改一榔头西改一棒子,出了问题都不知道是哪一步引入的。

优化层面要解决的问题主要手段
数据层现场样本和公开数据集差异大多尺度训练、遮挡合成、负样本工厂、难例挖掘
模型层小目标和遮挡特征提取不足高分辨率输入、动态标签分配调整、样本匹配策略
推理层大画面远距离目标容易漏检切图推理、重叠 Tile 合并、TTA 策略
业务后处理层单帧误报导致报警闪烁目标跟踪、确认机制、曝光参数联动

数据层是整个优化的地基,也是工作量最大的一部分。我们最终整理的训练集包含 5 万多个真实佩戴安全帽的标注框、约 9 千个遮挡样本、2 万多个未戴帽负样本和 6 千多个远距离小目标样本。这些数据不是一次性收集完的,而是根据每版模型在真实视频上的表现持续补充。

模型层的改动相对克制。我们没有自己魔改出一个“YOLO26 变体”,而是利用 YOLO26 已有的训练配置,调整了标签分配阈值、目标框匹配策略和辅助损失权重。因为一旦结构改动过大,复现成本会飙升,项目周期也不允许。

推理层做的事情最有立竿见影的效果:把 4K 大图切成重叠块,每个小块用高分辨率输入推理,再把结果合并回原图。这个方案有效地缓解了远距离小目标特征丢失的问题,但会带来显存和延时开销,后文我会专门讲实现细节。

业务后处理层是最被低估的一环。很多算法团队交付模型后就结束了,但现场接报警联动时,单帧模型的不稳定性会让安全员崩溃。我们最终加了目标跟踪和“确认帧”机制:一个检测框必须连续出现多帧并保持位置稳定,才触发报警;闪烁几下就消失的,直接丢弃。

2.3 实验环境与评测方法

训练机的配置是两张企业级 GPU,单卡显存 24G,用分布式训练跑,每次实验大约 8 到 10 个小时。这批数据不是一次性准备完就固定死,而是每训练一版,都用同一批真实视频做评估,再把误报帧捞出来补进训练集。

评估视频录了 7 天,覆盖白天、夜间、阴天、雨天和傍晚逆光时段,共约 20 万帧。我们把视频按时间轴切段,一半用于模型调参时观察,另一半只在最终验收时使用,防止“评估集过拟合”。这个细节很重要,如果你调参时一直看着同一段视频反复测试,很容易把模型调到只对这段视频有效。

3. 远距离小目标:多尺度训练与切图推理的实战细节

3.1 高分辨率输入和多尺度增强:小目标最直接的救星

先说最基础的一件事:训练输入分辨率。YOLO26 默认建议 640×640,但对于安全帽这种小目标,640 输入会导致小帽子在特征图里只有几个像素。我们把训练输入调整为 1280×1280,并且采用多尺度训练策略,输入尺寸在 960、1152、1280 之间随机切换。

为什么不是固定 1280?因为多尺度训练可以让模型对不同尺寸的目标都保持一定的适应能力。推理时现场画面经过 PTZ 摄像机的变焦和裁剪,目标实际大小浮动很大,如果只训练 1280,遇到目标很小时依然容易漏。多尺度训练还有个副作用是提高模型对边框抖动的稳定性,减少后面跟踪模块的前后帧框位置跳变。

下面的配置片段是我们最终训练用的数据增强参数,贴在 ultralytics 的 YAML 配置文件里:

# train_yolo26_cfg.yaml 片段 path: /dataset/construction_v2 train: images/train val: images/val nc: 2 names: ['person_with_helmet', 'person_no_helmet'] imgsz: 1280 mosaic: 1.0 mixup: 0.15 copy_paste: 0.3 fliplr: 0.5 scale: 0.8 hsv_h: 0.01 hsv_s: 0.4 hsv_v: 0.3 degrees: 0.0 translate: 0.1 shear: 0.0 perspective: 0.0

注意我把 degrees 设为 0,因为工地的安全帽在正常安装状态下不会出现明显的任意角度旋转,大幅旋转增强反而会制造大量不真实的画面。Mosaic 保留 1.0,但 mixup 只开了 0.15,因为安全帽类别之间的差异很微妙,mixup 强度太大会把“戴帽”和“未戴帽”的特征混成奇怪的东西,导致误报。

image train 完了还不够,推理阶段也要配套。我们做法是:所有摄像机图片先按原分辨率做检测,而不是统一缩小到 640 或 1280。原图是一张 4K 画面,直接整图塞进网络,显存和延迟都会爆,所以需要切图。

3.2 切图推理:把一张 4K 大图拆成多个小块再拼回来

切图推理的核心思路很简单:大图里的小目标,如果整图缩放,目标尺寸会变得更小;如果切成多个重叠的小块,每块用较高分辨率推理,小目标在块内的相对尺寸就会变大,特征保留得更好。我们用的切块策略是:把 3840×2160 的原图,按 1280×1280 的窗口切分,重叠比例 20%。重叠的目的是避免目标恰好落在两个块的交界处被截断。

切图会给后处理带来一个新问题:同一个目标可能出现在两个相邻块里,被检测两次。合并时需要做跨块的 NMS。我一开始用了全局 NMS,把整张原图所有输出框放在一起按 0.45 的 IoU 阈值抑制,结果发现小目标框之间 IoU 本来就低,很多重复框没有消掉。后来改成“重叠区域优先抑制”的策略:只对跨块重叠区域内的检测框做较低阈值的合并,非重叠区域保持独立。

简化后的边界框合并代码大概长这样:

import numpy as np def nms(boxes, scores, iou_thr=0.35): if len(boxes) == 0: return [] x1 = boxes[:, 0] y1 = boxes[:, 1] x2 = boxes[:, 2] y2 = boxes[:, 3] areas = (x2 - x1) * (y2 - y1) order = scores.argsort()[::-1] keep = [] while order.size > 0: i = order[0] keep.append(i) xx1 = np.maximum(x1[i], x1[order[1:]]) yy1 = np.maximum(y1[i], y1[order[1:]]) xx2 = np.minimum(x2[i], x2[order[1:]]) yy2 = np.minimum(y2[i], y2[order[1:]]) w = np.maximum(0, xx2 - xx1) h = np.maximum(0, yy2 - yy1) inter = w * h iou = inter / (areas[i] + areas[order[1:]] - inter + 1e-6) inds = np.where(iou <= iou_thr)[0] order = order[inds + 1] return keep

上面的 nms 是基础版本,实际项目里我还会对每个小块的位置信息做一个还原偏移,把小块坐标系里的框坐标转换回原图坐标,再统一做 NMS。这一步千万别漏,不然拼出来的框位置全是错的。

切图推理的代价是计算量成倍增加。一块 4K 原图切成 6 到 9 块,推理耗时从原来整图的 85ms 涨到了 120ms 左右。为了让延迟可控,我们最终放弃了对每帧全图检测的方式,改为:在画面中动态划定重点区域。比如塔吊吊装区域、材料堆场区域、人员主要通道区域,只对这些区域做高分辨率切图,其他区域用整图快速检测兜底。这样既保留了小目标的检出能力,又不用每帧吃满算力。

3.3 TTA:离线评估有用,在线部署要克制

测试时增强(TTA)是提升小目标召回率的另一个常见手段。我们在离线评估中试过水平翻转 TTA 和多尺度 TTA,也就是同一帧原图,分别用 0.8、1.0、1.2 倍缩放推理,再把结果合并。这个方法确实有用,最终召回率能提升 1 到 1.5 个百分点,而且能明显减少因为目标尺寸变化导致的闪烁漏检。

但在线部署的时候,我最终只保留了一个“水平翻转 + 原图”的融合策略,没做多尺度 TTA。原因很简单:多尺度 TTA 会把推理延迟提高到接近 5 倍,多路摄像机同时跑的话,单卡根本扛不住。而且多尺度 TTA 合并时,不同尺度下同一个目标可能一个被判成戴帽、一个被判成未戴帽,怎么投票都会产生新的争议。从实际报警体验来看,多尺度 TTA 带来的收益比不上它引入的延迟和判断分歧。所以,TTA 不是越多越好,一定要结合部署算力来取舍。

4. 遮挡场景:数据合成、网络设计与后处理的针对性打法

4.1 数据侧:遮挡样本的制造法则

遮挡样本的第一来源永远是真实工地采集。我们让现场配合的同事在几个容易产生遮挡的机位连续拍摄,专门截取工人被钢管、围挡、安全网挡住部分头部的画面,然后人工标注。这类样本大约收了 9 千多张,虽然标注成本高,但对遮挡识别的作用比任何网络结构改动都直接。

但真实遮挡样本不可能覆盖所有情况,所以我补了一个“合成遮挡”的数据增强流程。大致思路是:从现场材质库中截取钢丝绳、脚手架立杆、方格安全网、钢筋束等纹理切片,随机粘贴到安全帽目标的上方、前方或侧方,模拟真实遮挡。

合成遮挡不是简单地把一块黑色矩形盖上去,否则模型会学会“只要图像里出现大面积黑色块就降低帽子置信度”,一旦现场出现逆光或暗角,反而引发误报。我用的是带透明度的混合方式:

import cv2 import numpy as np def apply_occlusion(img, mask, alpha=0.7): # mask 是 0-255 的单通道遮挡纹理 h, w = mask.shape[:2] x = np.random.randint(0, max(1, img.shape[1] - w)) y = np.random.randint(0, max(1, img.shape[0] - h)) roi = img[y:y+h, x:x+w] blended = cv2.addWeighted(roi, 1 - alpha, mask, alpha, 0) img[y:y+h, x:x+w] = blended return img

关键经验是遮挡程度要有上限。如果把安全帽遮掉 80% 以上,标注框本身已经不具备可利用的视觉信息,强行让模型学,只会让它过拟合到“我来猜一个帽子位置”。我们通过实验把遮挡面积控制在目标框的 20% 到 60% 之间,模型在真实遮挡上的稳定性明显提升。

另外,遮挡样本的标注规则也要改。过去我们习惯标一个完整的安全帽框,即使帽子只露出一半。后来发现,如果标注框包含太多背景,分类头会被背景干扰。我们统一改成“只标注可见部分”,帽子露出 1/3 就标 1/3,露出 2/3 就标 2/3。虽然检测框覆盖的区域变小了,但网络学到的特征更干净,遮挡情况下的判断也更稳定。

4.2 模型层:调匹配策略比改结构更稳

有人问我,要不要在 YOLO26 里专门加一个“遮挡感知模块”。我的回答是:如果你的遮挡样本足够多,先用现有结构把数据红利吃干净,不要一上来就动网络。

YOLO26 的训练过程用了动态标签分配,也就是根据预测框和真实框的 IoU 以及分类置信度来决定哪些样本是正样本。在小目标和遮挡场景下,IoU 匹配很容易出问题:因为目标框很小,锚点特征和 GT 框的 IoU 天然就低,很多真实目标不会被分配为正样本,导致网络学不到。

我这边做的调整是:把正样本匹配的 IoU 阈值从默认值适当降低,同时增加“至少匹配一个 GT 框”的兜底逻辑,保证每个小目标至少有一个正样本负责学习。这个操作不需要改代码结构,只需要在训练配置里调参数,实现成本低,但对小目标召回率提升很明显。

还有一点要对准遮挡:在分类损失里,我对遮挡样本和清晰样本采用了不同权重。遮挡样本的损失权重提高 1.5 倍左右,相当于告诉网络“这些难样本要重点对待”。这比单独修改网络结构更灵活,也不容易出现训练不收敛的情况。

4.3 后处理侧的双重阈值和跟踪确认

遮挡场景带来一个很矛盾的现象:真目标因为被挡,模型给的置信度往往只有 0.2 到 0.4,而一些假目标反而因为背景纹理像帽子,置信度能高达 0.7 以上。如果只用一个全局阈值,必然陷入两难:阈值调低,召回率上来了,误报也上来了;阈值调高,误报好一些,但真目标漏检严重。

我最后的方案是把原来的“一次判断”改成“两阶段判断”:第一阶段先给模型设了一个很低的基础阈值,比如 0.15,把疑似目标全部捞出来;第二阶段不是马上报警,而是把这些低置信度的候选框送入跟踪器,看它在连续帧里是否稳定出现。

稳定性的判断逻辑是:如果同一个目标连续 3 帧都出现,且相邻帧的框中心点位移不超过一定像素,就认为这是一个可信目标;如果只是偶尔闪一下,就当作误报丢弃。这个机制在视频流场景下非常有效,因为它把单帧置信度和时间序列置信度结合起来。

5. 误报的源头治理:负样本、曝光联动与跟踪级过滤

5.1 负样本工厂和难例挖掘

误报的另一个大头来自“背景被误认为安全帽”。工地现场有太多颜色和安全帽相似、形状又接近半圆形的物体:红色消防箱顶部的圆弧、黄色警示柱、白色塑料水马、蓝色铁皮板上的高光,甚至安全网上的结扣。这些在独立图片里单看,确实像帽子。

我做的负样本工厂分两步。第一步,把工地所有摄像机 48 小时的视频抽帧,随机采样 5 万张不包含任何人的纯背景图,打上“无目标”标签。第二步,用当时的模型在这些背景图上推理,把所有置信度大于 0.1 的错误检测框自动抠出来,人工快速过滤一遍,只保留那些外观确实像安全帽的误报区域,然后作为负样本加入训练集。

这个过程要跑好几轮。第一轮负样本加入后,模型对某些背景不再误报,但新版本模型又会在新的背景区域上产生误报;于是继续用新模型挖一遍难例,循环往复。这个过程叫“在线困难负样本挖掘”,听起来技术含量高,实际操作就是数据清洗加反复训练。

不过要特别提醒:负样本加得太多,模型可能变得过于保守,把真安全帽也判成背景。我们的经验是,负样本数量不要超过正样本的一半,并且每一轮加完都要重新检查召回率,尽量保持正负样本比例在 2:1 到 3:1 之间。

5.2 反光、逆光与曝光联动

远距离和遮挡之外,曝光问题也是误报的“隐形推手”。工地摄像机经常对着天空、塔吊臂、金属结构,在逆光场景下,安全帽和背景的对比度极低。模型在这种画面上缺乏有效特征,就会开始“脑补”顶部形状,误判率直线上升。

我们做的事比较“硬件化”:和现场负责摄像头的人协调,打开摄像机的宽动态功能,把背光补偿区域对准人员主要活动区域。同时在软件层面设置了一个规则:当检测到画面平均亮度低于一定阈值且对比度极低时,自动降低模型的低置信度检测的输出,把这部分帧交给跟踪模块,等画面质量恢复后再重新判断。

这个看起来很“笨”的规则,反而把傍晚时段的误报率降了很多。因为人在阴影下是否戴安全帽,人眼一眼就能看出,但单帧的模型不行;通过把决策时间拉长到多个帧之后,模型可以利用连续变化的光照和人员运动信息,比单帧硬猜可靠得多。

5.3 跟踪级过滤:用时间序列消除闪烁误报

前面多次提到跟踪,这里把它单独讲透。视频流检测最常见的误报形态是“闪烁红框”:某一帧突然在没有任何人的墙面或钢架结构上弹出一个安全帽框,下一帧又消失,过几帧又出现在相同位置。这种误报如果直接触发警报,安全员很快就会被搞烦,最后对系统失去信任。

解决思路很简单:检测结果不直接作为报警依据,而是先进入一个跟踪器。每个候选框有一个状态机,包含“跟踪中”“待确认”“已确认”三种状态。检测框第一次出现时,只进入“待确认”状态;只有连续 N 帧都出现且位置连续,才变成“已确认”,才会推送报警。中间只要丢帧,计数重新开始。

简化后的状态机逻辑:

MAX_MISSED = 2 CONFIRM_FRAMES = 3 class TrackBox: def __init__(self, box, cls, score): self.box = box self.cls = cls self.score = score self.hits = 1 self.missed = 0 self.confirmed = False def update_hit(self, box, score): self.box = box self.score = score self.hits += 1 self.missed = 0 if self.hits >= CONFIRM_FRAMES: self.confirmed = True def update_miss(self): self.missed += 1 return self.missed > MAX_MISSED

实际项目中,我还会加一个限制:同一跟踪目标触发报警之后,一段时间内不能重复报警。这解决了“一个安全帽在画面里待很久,每分钟触发一次报警”的问题。报警联动要的是事件变化,不是持续状态。

6. 迭代成绩与踩坑盘点

6.1 关键版本迭代记录

下面这组数据是我们内部评估时记录下来的,每一行对应一个版本的核心改动。可以看到,误报率不是一步到位的,而是逐层降下来的:

版本核心改动帧级误报率未戴帽召回率
V1.0YOLO26 官方默认配置,640 输入5.19%92.7%
V1.11280 输入 + 多尺度训练 + 切图推理4.42%93.6%
V1.2增加遮挡合成与真实遮挡样本3.88%93.8%
V1.3负样本挖掘 + 难例循环3.52%93.4%
V1.4跟踪确认机制 + 曝光联动3.06%94.1%

v1.1 是效果最明显的一版,因为远距离小目标问题占比最高;v1.2 对遮挡场景的贡献大,但单帧误报率没有立刻大幅下降,因为真正收益更多体现在事件级误报上;v1.3 有一段时间让我很头疼,负样本加入后帧级误报率看起来是降了,但召回率反而小幅波动,最后通过增加难正样本和跟踪模块才把召回率拉回来。

6.2 实战中踩过的几个坑

第一个坑是合成遮挡增强强度失控。最初我把不透明遮挡条随机盖在安全帽目标上,训练后模型对暗角和阴影区域的误报明显增加,因为模型已经习惯“看到大块深色就把置信度调低”,结果逆光时把真安全帽也当成了背景。后来改成半透明混合,并严格控制遮挡面积,这个问题才缓解。

第二个坑是切图合并时 NMS 的阈值冲突。用全局阈值 0.45 消不去跨块重复框,用 0.3 又容易把真正相邻的小目标合并掉。最后处理方法是只对“跨过切块边界的重叠区域”使用 0.2 的强抑制阈值,非重叠区域维持正常 NMS。这个细节不调好,评估指标会忽高忽低。

第三个坑是跟踪确认参数取值太严格。有一版我把 MAX_MISSED 设为 1,测试时发现只要工人转身或目标短暂被挡一帧,跟踪器立刻丢目标,报警反而比之前更乱。后来放宽到 2,并加了 IoU 匹配阈值,才在漏报和误报之间找到平衡。跟踪参数不能只看误报率,还要观察真实场景中人员走动的遮挡频率。

第四个坑是评估集被“看好”了。项目中对模型反复调参时,我盯着一周的视频数据来回分析,结果模型在某几个固定视频段表现很好,但换到新场地后误报又反弹。最后把评估视频划分成调参集和验收集,调参时只看前者,验收只跑后者,才算真正稳住性能。

第五个坑是负样本挖掘的“脏数据污染”。自动负样本挖掘会抠出大量纯背景,但有时也会把真目标一起抠进去,尤其是遮挡严重、置信度低的真目标。我刚开始没有做人工复核,直接把它们当负样本丢进去,结果模型把一些真实遮挡安全帽也学成负样本,召回率掉得莫名其妙。宁可慢一点,每个自动负样本都要过一遍人工清洗。

6.3 最后再分享一点个人体会

做安全帽识别这类智慧工地项目,最大的天花板往往不在模型,而在数据流和业务逻辑。YOLO26 本身的能力足够强,但野外场景的误报问题,必须靠多维度策略才能压制住。我自己的体会是:先花时间把现场视频帧的真实误报原因归类,再决定改数据、改参数还是改后处理,比直接换模型或堆算力有效得多。这个项目做到后面,真正让误报率下降 41% 的,不是某一次结构升级,而是数据、训练、推理、跟踪整条链路一起调整的累积效果。现在再看到新项目,我会一开始就把“误报率”作为主线目标,而不是只看离线 mAP。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进