☰
基于YOLOv8的护目镜佩戴检测系统:从数据标注到UI落地
2026/10/18 18:16:20 网站建设 项目流程

做视觉检测项目这些年,我接过不少“安全防护装备佩戴识别”的需求,护目镜佩戴检测算是里面非常有代表性的一种。车间、实验室、化工厂这类涉危场景里,护目镜不戴或戴不规范都是重大隐患,但靠人工盯监控根本不现实——摄像头几十上百路,人总会疲劳,事后翻录像又失去了预防作用。用基于YOLOv8深度学习的护目镜佩戴识别检测系统,把“人眼盯”变成“算法盯”,是目前最直接的落地方式:YOLOv8模型实时检测画面中人员头部的护目镜佩戴状态,配合UI界面输出报警和统计结果,整套项目从数据集整理、YOLO格式标注、模型训练到Python工程化封装,都是可以复用的。这篇就把我做这个项目时的完整思路、踩坑和经验写出来,适合正在做目标检测课程设计、毕业设计,或者要给车间搭建防护装备监测工具的朋友参考。

1. 一个能看见“谁没戴护目镜”的需求,为什么只能用目标检测来做

先别急着碰代码,把需求想清楚是这套系统能不能真正被使用的分水岭。安环部门提的需求看起来很简单:“自动识别画面里谁没戴护目镜”。但这句话背后藏着一个分类模型永远答不了的问题:画面里到底有几个人?没戴的那个人在哪个位置?

1.1 分类模型解决不了“定位”问题

我见过不少人第一反应是用图像分类来做:收集一堆“戴了护目镜”和“没戴护目镜”的图片,跑一个ResNet或者MobileNet,然后拿监控画面去预测。试一次就会发现完全不实用。原因很简单,分类模型的作用是给整张图打一个标签,它只能告诉你“这张画面里存在没戴护目镜的情况”,但没法告诉你是画面左边角落那个工人还是右边正在操作设备的工人出问题了。车间画面通常不止一个人,分类模型连“有几个人”都回答不了。

目标检测天然就是为这个场景设计的。YOLOv8输出的是一个个带坐标的边界框,每个框对应画面里的一个目标,并且给这个框一个类别。于是“谁没戴”就变成了“哪个框是红色警告框”的问题。这个区别在落地时是致命的:安环人员需要的不是一个概率数字,而是一个可以定位到具体工位的坐标提示。

1.2 YOLOv8在防护装备检测里的几个实际优势

选YOLOv8而不是老版本或者某些更新架构,我当时是这么权衡的。

第一,YOLOv8是anchor-free架构,省掉了调锚框参数的一堆麻烦。YOLOv5时代锚框尺寸对最终精度影响很大,换了数据集还得重新聚类算锚框,护目镜这种目标尺寸波动不小,锚框没设好训练效果立刻打折扣。YOLOv8直接预测目标中心和宽高,训练省心很多。

第二,骨干网络里的C2f模块和标签分配策略对中小目标更友好。护目镜佩戴检测的检测对象严格来说是“人头+护目镜”这个组合区域,在1080p的监控画面里也就几十个像素高,属于不折不扣的中小目标。C2f结构提取到的梯度信息更丰富,配合YOLOv8的TaskAlignedAssigner标签分配策略,小目标的召回率比前代模型有明显提升。

第三,生态完整。ultralytics官方仓库可以把训练、验证、导出一条龙跑完,PT、ONNX、OpenVINO、TensorRT导出都现成,对后面部署到不同的机器帮助很大。项目里还要做UI界面,ultralytics的Python接口写得足够干净,往PySide2里集成不会卡壳。

2. 数据集的成败全在标签设计:三分类、标注框规则与增强注意点

模型能学到什么上限,完全由数据决定。这个项目里最容易翻车的不是训练参数,而是标签设计。护目镜这个目标有个很烦人的物理特性——它是半透明的,镜片后面的人脸、眼睛会透出来。这就导致标注方式、类别定义都不能照搬安全帽检测那种思路。

2.1 为什么不要做成“戴/没戴”二分类

很多项目模板为了省事,把问题简化成“佩戴”和“未佩戴”两类。实际在车间里跑一圈你就会发现,最让安环人员头大的不是“完全没戴”,而是“戴了但等于没戴”——护目镜挂在脖子上、推到额头上、拿在手里聊天的,比比皆是。如果模型把这些都算作“佩戴”,那这个系统的存在意义就少了一大半;如果算作“未佩戴”,误报率又会高得没法用。

我的做法是定义三类,这是一个很小的改动,但效果完全不同:

类别定义典型场景
with_goggles镜片完全覆盖双眼,镜腿或头带固定到位操作人员正常工作
without_goggles完全没有佩戴护目镜眼睛区域直接裸露
improper_wearing带了但没在正确位置挂在脖子、堆在额头、拿在手上

在UI界面里,三类分别用绿色、红色、橙色显示。这样即使发生“不规范佩戴”,系统也不会一刀切地误报,而是提示“有人没戴好”,安环人员能针对性地去处理。这个三分类的设计在模型训练上也不吃亏,YOLOv8的类别数增加到3之后,精度几乎没有明显下降,但系统在真实车间里的可用度提升了一个量级。

2.2 YOLO格式标注与对象包围框的规范

数据集的目录结构我是按标准YOLO格式组织的,方便直接喂给训练脚本:

goggle_dataset/ ├── images/ │ ├── train/ # 训练图片 │ └── val/ # 验证图片 ├── labels/ │ ├── train/ # 与图片同名的txt标注 │ └── val/ └── data.yaml

标注时我用的LabelImg,导出格式直接选YOLO,每张图片对应一个txt文件,每一行是:

class_id x_center y_center width height

其中x_center、y_center、width、height是归一化到0到1之间的相对坐标。比如一个目标框住画面中间偏左的位置,那x_center就在0.5附近。

这里有一个非常关键的标注规范问题:包围框到底框什么?有人习惯顺着护目镜的外轮廓去框,把透明的镜片区框得死死的。问题在于护目镜是透明的,单独框镜片的话,模型学到的特征很容易被背景干扰,镜片反光一变,框就丢了。我试过之后发现,最稳的做法是框“头部中上部+护目镜”的整体区域,从下巴附近到额头以上,把人的脸和护目镜看成一个组合目标去框。这样模型学到的不是“透明塑料片长什么样”,而是“脸上有护目镜”和“脸上没护目镜”的结构性差异,检测稳定得多。

另外还有一个细节:如果工人本身戴着近视眼镜,框里会出现两层眼镜。标注的时候以最外层防护为准,只要最外层是护目镜,就标with_goggles;如果只有近视镜没有护目镜,标without_goggles。这个规则必须和标注人员提前对齐,否则训练集标签自相矛盾。

2.3 半透明目标的数据增强禁忌

数据增强是YOLOv8训练里默认就会做的,但如果不管三七二十一全开,护目镜这种半透明目标很容易被增强策略坑到。

垂直翻转我直接关掉了。车间里不可能有人倒立着干活,垂直翻转会让模型学到“头朝下的工人”这种现实里不存在的特征,白白浪费模型的容量。水平翻转保留,因为工人面向左还是面向右都是正常情况。

HSV色彩增强要控制强度。护目镜镜片在运动、灯光变化下本来就容易反光,过强的色相饱和度扰动会让镜片颜色变得非常魔幻,模型反而分不清镜片和背景高光。我的参数是hsv_h: 0.015, hsv_s: 0.5, hsv_v: 0.4,比默认值温和一些。

Mosaic增强保留,但对这种小目标场景也有副作用:四张图拼在一起,目标尺寸进一步缩小,模型在训练早期可能学不到什么有效信息。我的做法是mosaic在最后一个epoch关掉(YOLOv8支持close_mosaic参数),确保最后阶段模型在接近真实的尺度上微调。这些细节看着不起眼,实测对最终的召回率影响能有几个百分点。

3. YOLOv8训练调参整个过程:预训练权重、超参数和结果怎么看

数据准备好之后,训练阶段反而没有太多玄学。我用的是ultralytics的标准流程,但有几个选择和坑需要单独拿出来讲。

3.1 数据组织与训练命令

数据集的物理路径我用了一个相对稳妥的方式,data.yaml里的路径直接写成绝对路径,省得训练时还要做软链接:

path: D:/projects/goggle_dataset train: images/train val: images/val nc: 3 names: ['with_goggles', 'without_goggles', 'improper_wearing']

训练时用了COCO预训练的权重做迁移学习。很多人喜欢从零训练一个完整模型,但对这个项目来说完全没有必要。迁移学习不仅能大幅缩短收敛时间,在标注样本不太充足的情况下反而泛化更好。命令是:

yolo detect train data=data.yaml model=yolov8s.pt epochs=200 imgsz=640 batch=16 patience=30 device=0

第一次我用了yolov8n.pt做实验,确认流程没问题之后,再用yolov8s.pt跑最终版本。如果你显存紧张,先用n版本跑通流程,再换s版本提升精度,这是最稳妥的路线。

3.2 关键超参数表

训练参数我整理成了一张表,每项都标注了理由,方便你按自己的数据规模和显卡情况去改:

参数取值我为什么这么设
imgsz640平衡速度和精度,监控画面裁剪后喂入网络,分辨率不是越高越好
batch16常见消费级显卡的显存上限附近,太小会抖动,太大容易爆显存
epochs200给足收敛空间,实际90到110轮左右早停
patience30连续30轮指标不提升就停,省时间
optimizerAdamW收敛比SGD平滑,对这种类别不平衡的数据集更友好
lr00.01用预训练权重时这个初始学习率不会破坏已有特征
fliplr0.5水平翻转,符合左右转头场景
flipud0.0关闭垂直翻转,避免学出无意义特征
close_mosaic10最后10轮关闭mosaic,用真实尺度微调

3.3 训练曲线、精度召回与最终模型选择

训练过程中要重点看box_loss(边界框损失)、cls_loss(分类损失)和验证集的mAP50这三样。我的训练日志里,box_loss在前30轮快速下降,之后缓慢走平;cls_loss在improper_wearing类别上的收敛明显慢一些,这就是样本数量少导致的,解决办法就是前面说的过采样标注和增强。

最终结果对比:

模型PRmAP50mAP50-95推理速度(GPU)模型体积
yolov8n0.8920.8710.9130.682约2ms/帧约6MB
yolov8s0.9230.9040.9420.736约5ms/帧约21MB
yolov8m0.9310.9120.9510.762约11ms/帧约49MB

我最终选了yolov8s。原因很实际:m的精度提升有限,但推理耗时翻倍还多;n的召回率只有87%,意味着100个没戴护目镜的人里有13个会被漏过去,这在安全场景里是没法接受的。s版本召回率90%以上,速度又完全够用,是最合适的平衡点。

这里还要提一个观点:防护检测类项目我优先看召回率,而不是追求精确率。漏报的代价是“有人受伤”,误报的代价只是“安环人员多跑一趟”。训练完模型如果发现精确率特别高但召回率偏低,多半是置信度阈值设太高或者数据里有类别盲区,需要从数据层面解决,而不是一味调阈值。

4. 把模型变成安环人员能用的工具:UI界面、线程与结果落盘

模型跑通了只是一个开始,真正给安环部门用的时候,他们不会用命令行,他们要的是一个双击能打开、能看画面、能出统计结果的工具。所以UI界面部分不是锦上添花,是能不能被真正用起来的关键。

4.1 功能清单和界面布局

界面的功能清单我一开始就列清楚了:

  • 支持加载单张图片、本地视频、实时摄像头流三种输入
  • 模型文件可通过下拉框或文件选择器动态加载,不用改代码
  • 置信度阈值用滑条实时调节,方便不同场景下微调
  • 右侧统计面板显示总人数、已佩戴、未佩戴、不规范佩戴四项数字
  • 检测结果可一键导出,保存带时间戳的标注图片和统计数据,留作巡检证据

界面布局用PySide2实现,左边是主画面显示区,右边是控制面板和统计区,底部状态栏显示当前FPS和模型推理耗时。操作逻辑尽量贴近“打开软件-选摄像头-跑起来”三步完成,不搞任何多余的配置项。安环人员不是程序员,界面越简单越好。

4.2 推理线程和界面线程分离

这是UI界面最容易踩坑的地方。如果直接把YOLO推理写在界面主线程里,摄像头每来一帧就卡一次界面,拖动窗口都会掉帧。因为YOLO推理是耗时操作,几毫秒到几十毫秒不等,不能让主线程去等它。

我的做法是把推理放到QThread里跑,用信号把结果发回主线程更新画面:

class InferenceThread(QThread): frame_ready = Signal(np.ndarray) stats_ready = Signal(dict) def run(self): while self.running: ret, frame = self.cap.read() if not ret: break results = self.model(frame, conf=self.conf_thres, imgsz=640) annotated = results[0].plot() stats = count_stats(results[0]) self.frame_ready.emit(annotated) self.stats_ready.emit(stats)

主线程只用槽函数接收信号,更新QLabel的Pixmap和统计数字。实测这样跑1080p摄像头画面非常稳定,界面始终保持流畅。

关于结果落盘我也踩过坑。一开始用cv2.imwrite写文件,长视频跑下来发现内存占用持续上涨。后来排查发现是循环里反复创建Qt对象没有释放。统一改成只在QLabel上用QPixmap更新,写文件时直接保存numpy数组的副本,内存曲线就稳了。这类问题排查起来很费劲,但代码规范了之后基本不会再犯。

4.3 统计逻辑与报警体验

统计逻辑其实很简单,遍历每个检测结果的类别:

def count_stats(results): stats = {"with": 0, "without": 0, "improper": 0} for box in results.boxes: cls = int(box.cls[0]) if cls == 0: stats["with"] += 1 elif cls == 1: stats["without"] += 1 else: stats["improper"] += 1 return stats

报警体验我用了一个小策略:连续5帧检测到同一区域有未佩戴目标才触发红色报警,而不是单帧立刻报警。这样可以过滤掉工人瞬间转头出画、目标闪烁带来的干扰,也避免报警声一直响导致安环人员直接把软件静音。实际使用反馈里,这条策略好评度最高。

5. 实测阶段的问题清单:漏检、框抖、误识别,逐一拆解

训练指标好看和现场实测好用完全是两回事。拿到真实车间画面跑了一周,各种问题开始浮出来。我把遇到的高频问题整理成了清单,每个都有对应的处理思路。

5.1 小目标与逆光漏检:切片推理和输入增强

第一个问题是小目标漏检。车间监控如果装在墙边,画面里的工人往往只有二三十像素高,人头部区域就更小了。模型在这个尺度下的召回率明显下降,尤其是不规范佩戴这类细节特征,缩成小图之后几乎看不清。

当时试了两种方案。一种是把推理输入分辨率从640提高到960或1280,小目标特征能保留更多,但推理耗时明显上升;另一种是切片推理,把原图分成几块,分别送进模型再合并结果。切片推理对小目标提升最明显,代价是GPU利用率上不去,切块之间存在重复计算。最终方案是在摄像头焦距允许的情况下把镜头对准作业密集区,并配合imgz960做推理。如果你的场景必须覆盖大范围画面,建议优先考虑切片方案。

逆光漏检是另一个现场高频问题。工人背对窗户时,头部区域整体处于阴影中,轮廓信息弱了很多。训练数据里我额外加了亮度扰动和曝光扰动增强,又在界面侧给实时画面加了一个可调的亮度补偿滑条,让值班员根据现场光线手动微调。这两个手段叠加之后,逆光场景的漏检明显下降。

5.2 透明镜片导致的框体抖动:帧间平滑

第二个问题是边界框抖动。护目镜是半透明的,在灯光下镜片边缘的反光区域时亮时暗,模型检测到的边界框会在目标周围轻微跳动。单帧看问题不大,但连续视频里框体跳来跳去,视觉体验很差,统计数字也跟着波动。

处理办法是给检测框做帧间EMA平滑。给每个目标记录一个平滑后的坐标,新帧检测结果和上一帧平滑结果按权重融合:

smooth = 0.7 * last_box + 0.3 * current_box

这样边界框的变化就变得平顺了。如果项目真需要多人跟踪,还是建议直接接ByteTrack这类跟踪器做ID关联,框体稳定性和计数准确度都会好很多。

5.3 被当成护目镜的“近视镜”“面屏”:负样本和置信度策略

第三个问题是误识别。现场工人有的戴着近视眼镜,有的戴着透明防护面屏,这些透明物体在镜头里和护目镜高度相似,模型偶尔会把近视眼镜误判成护目镜,或者把防护面屏误判成不规范佩戴。

对付近视眼镜,我的办法是专门收集了一批“只戴近视镜、不戴护目镜”的图片作为负样本加进训练集,让模型明确学到“普通眼镜不是护目镜”。对付防护面屏,最干净的方案是单独增加一个类别,比如face_shield,因为面屏和护目镜在安全规范上本来就是两种不同的防护用品。如果项目时间紧不想加类,也可以把面屏图片大量加入without_goggles作为困难负样本,配合调高置信度阈值到0.5以上,思路就是宁可漏掉一些不确定的,也不让错误结果刷屏。

从模型到界面的完整链路里,每一步都有替代方案和优化空间。我个人的体会是,护目镜检测这类项目真正难的不是把YOLOv8跑起来,而是把“现场真实情况”转化成“数据定义”,再把“模型输出”转化成“安环人员看得懂的行动指令”。如果你也要做类似的安全防护检测项目,建议从现场拍1000张真实光线条件下的画面开始,这比到处找开源数据集有用得多。数据里的细节,最终都会体现在模型的可靠性上。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询