简介:目标检测是计算机视觉中应用最广泛的技术之一,其核心在于同时完成物体定位与分类。以YOLOv8为代表的一阶段检测模型,凭借端到端的训练方式和高效的推理速度,在工业场景中备受青睐。然而,当目标不仅需要检测“有无”,还需区分颜色、型号等细粒度特征时,任务就变成了检测与细粒度分类的结合。工地安全帽的颜色识别正是典型场景:不同颜色代表不同人员角色,自动识别帽色能显著提升巡检效率。本文从数据准备、标注规范、模型训练到ONNX/TensorRT部署,详细展示了使用YOLOv8训练多色安全帽检测模型的完整链路。针对光照变化、视角差异、类别不平衡等工程实际问题,给出了可复用的解决方案。对于手头有数千张图片、希望落地目标检测项目的工程师,尤其值得参考。 管过工地安全的人应该都有这种体会:每天巡检最费精力的不是“有没有戴安全帽”,而是“戴的是什么颜色”。施工现场的帽子颜色是有管理语义的——黄帽一般是一线作业人员,白帽多为管理人员或监理,红帽可能是访客或专职安全员,蓝帽通常是电工、焊工这类特殊工种。每个区域能不能进、人员构成合不合理、有没有违规串岗,都要靠帽色快速判断。以前全靠人的眼睛盯,现在摄像头铺得到处都是,自然想用目标检测把这活儿自动化。这也是我这次用YOLOv8做不同颜色安全帽检测的出发点,数据集一共3000张,最终训练好的模型可以直接拿去做实时推理。这篇博文就把整个流程完整拆开讲讲:数据集怎么规划、标注要注意什么、训练参数怎么调、模型怎么部署,以及实战里最容易踩的坑。适合手里有个几千张图片、想把目标检测真正落地的工程师,尤其是第一次接触YOLOv8的读者。
1.1 从“二分类”到“细分色”的定位转变
普通安全帽检测任务本质上是一个二分类目标检测:画个框,判断框里是“戴了”还是“没戴”。但加上颜色维度之后,任务性质就变了。它不再只是检测安全帽的有无,而是检测+细粒度分类同时完成——既要保证框能稳定框住目标,还要让模型在同类目标里区分出黄、白、红、蓝几种颜色。YOLOv8的多类别输出机制天然支持这个需求,它的检测头每个锚点都会输出一组类别概率,类别之间的差异学习完全交给损失函数去拉大,所以把不同颜色定义成不同类别,模型训练起来并不会增加架构复杂度。
但不要因为架构没变就掉以轻心。颜色分类在实际场景里比想象中难得多。同一个黄色安全帽,在正午阳光下和建筑物阴影里的RGB值差距非常大;不同厂商摄像头的白平衡处理也会让同一种颜色偏暖或偏冷;安全帽用久了会褪色,工地上沾满灰尘后,黄色可能看起来像灰白色。更麻烦的是背景干扰,挖机机身、反光背心、脚手架的涂料都可能和某一种帽色高度接近。我在标注阶段就遇到过一张图,黄色帽子靠在黄色围挡旁边,人眼都得盯两秒才分得清边界,这种样本对模型来说就是高压题。
1.2 为什么选YOLOv8而不是分割方案
拿到需求后,我第一反应先排除了实例分割方案。安全帽在画面里经常只有几十个像素大小,分割模型在这种小目标上优势不明显,反而推理开销大。检测方案用矩形框,对遮挡、小目标、密集场景的鲁棒性更好,后处理也更简单。YOLOv8相比v5和v7,最大的提升在于anchor-free检测头简化了正负样本匹配逻辑,训练更稳定,而且ultralytics官方仓库把训练、验证、导出全链路包装好了,对做工程的来说省了很多事。
另外一个实际考虑是部署生态。工地上不可能都配一台顶配GPU服务器,边缘盒子、NVR、嵌入式板卡才是主流硬件,YOLOv8导出ONNX、TensorRT都很顺畅,OpenCV DNN和ONNX Runtime都能直接推理,RK3588这类国产板子也有现成的NPU适配工具链。可以说从训练到落地,整个链路是通的,这是它相比一些学术模型最大的优势。
2. 3000张数据集的来源规划与标注规范
2.1 数据从哪来:公开数据集打底,现场图片补量
先说结论:这3000张图,我建议不要全部从网上扒,也不要全部自己拍。最好的方式是“公开数据集打底,现场图片补量”。目前能搜到的安全帽公开数据集以二分类为主,真正带有颜色标注的不多,数据质量也参差不齐,很多标注框贴得不严,或者类别命名混乱。我的做法是用公开数据里画质较好的一部分作为底子,大约1500张,再结合现场巡检相机、手机实拍大约1500张,凑成3000张。
有人会觉得3000张是不是太少了,但实测下来,对于安全帽这种类别外观高度统一的目标,3000张是够用的。安全帽不像猫狗那样有千奇百怪的姿态,它的形状、外观相对固定,模型需要学的主要是不同场景下的颜色差异和尺度变化。我建议遵循6:2:2的划分,也就是训练集1800张、验证集600张、测试集600张。这里要特别注意:划分时必须以“场景”为单位,而不是以“单张图”为单位。同一段视频序列里抽出来、画面几乎相同的图片,不能一部分进训练集一部分进测试集,否则验证结果虚高,部署到新场景就现原形。
图片收集时还要注意覆盖不同天气和时间段。我当时专门分了几类:晴天上午、晴天下午、阴天、黄昏、夜间灯光、室内灯光,每类至少占5%的比例。颜色检测最怕环境光单一,如果3000张全是晴天正午拍的,模型到了阴天环境基本就废了。
2.2 类别设计:四色加未戴人头,一共五类
类别定义是这类项目里最容易被忽略、却最影响结果的地方。安全帽颜色在实际工程中并没有统一标准,不同工地甚至不同地域的管理惯例都不一样,所以我这里给出一个通用的设计思路,具体颜色可以按自己项目替换。我最终用的是这五类:
| 类别ID | 类别名称 | 说明 |
|---|---|---|
| 0 | yellow_helmet | 黄色安全帽,一线作业人员 |
| 1 | white_helmet | 白色安全帽,管理人员/监理 |
| 2 | red_helmet | 红色安全帽,访客/专职安全员 |
| 3 | blue_helmet | 蓝色安全帽,特殊工种 |
| 4 | head | 未佩戴安全帽的人头/头部 |
为什么要单独加一个head类?这是很多人做安全帽检测时漏掉的一步。如果只检测帽子,模型其实学的是“帽子的外观”,而很难学会“没戴帽子”这个负例。加上head类后,模型被迫去学习头部和帽子之间的形态差异,实际推理时就能区分“没戴帽子”和“帽子被遮挡”这两种情况。后处理逻辑也可以简化:如果某个head检测框与任何helmet检测框重叠率低于阈值,就判定为未戴安全帽。
颜色类别数量不建议超过五到六类。工地现场常见的帽色就是黄、白、红、蓝,再加一个橙色(多为救援或外来参观人员)已经顶天了。类别越多,颜色相近的类之间就越容易互相混淆,标注成本也会成倍上涨。如果你拿到的数据里某种颜色特别少,比如红色只有几十张,我建议先把红色合并到“其他颜色”里,等数据补够了再单列,而不是硬上。
2.3 标注工具与YOLO格式要点
标注工具我用的是LabelImg和X-AnyLabeling,两个都是免费的。LabelImg是老牌工具,轻量,适合快速框选;X-AnyLabeling带了SAM模型辅助分割,半自动标注效率更高,适合那种帽子目标很多、一张图要框十几二十个框的场景。至于具体选哪个,看个人习惯,导出格式只要是YOLO txt格式就行,label标注软件内部会帮你转换成归一化的坐标。
一个典型的YOLO标注文件长这样,图片img_001.jpg对应img_001.txt:
0 0.482031 0.351562 0.186719 0.183594 4 0.629688 0.398438 0.072656 0.145312每一行分别是:类别ID、归一化后的中心点x坐标、中心点y坐标、归一化后的框宽、框高。所有坐标都用图片宽高做了归一化,取值范围在0到1之间。这里有个最容易犯的低级错误:标注软件导出时会自动归一化,但手工改文件或者用脚本处理时经常忘记除以图片宽高,训练结果就是loss炸裂、bounding box全乱。
标注时的框贴合度也要注意。我的经验是:帽子目标框要刚好包住实体部分,上下左右留1%到2%的边距即可,不要把帽子下方的人脸或肩膀也包进来。因为类别学习的重点是帽子颜色,包进来太多无关像素,模型就会把脸的颜色也当作特征之一,换个人种或换种肤色就可能误判。遮挡情况下,如果安全帽被遮挡超过50%,我建议直接不标,避免给模型传递错误信息;如果遮挡小于30%,按完整框标注,让模型学着去处理部分遮挡。
2.4 标注中最容易翻车的三个坑
第一是颜色标注不一致。这是最致命的。如果两张图中同样是黄色安全帽,一张标成yellow_helmet,另一张因为光线暗标成了white_helmet,模型就会学到错误的颜色边界,损失函数怎么调都降不下来。所以在标注规范里我要求:一律按“肉眼在自然光下看到的真实颜色”来标,严格禁止按照像素颜色值判断。第二是远距离小目标漏标。画面角落里比拇指还小的安全帽最容易漏。漏标等于给模型提供了错误负样本,训练后模型会倾向于漏检小目标。第三是类别不平衡。我一开始五类的分布大致是:黄色约40%,白色约25%,蓝色约20%,红色约10%,未戴人头约5%。红色样本太少导致训练后红帽的recall只有0.7左右,明显低于其他颜色。后期我给红色样本单独做了水平翻转、亮度扰动、HSV色相微调,把有效样本补到了800张,指标才追上。
3. 训练前配置:GTX 1660 Ti也能跑的硬件与软件清单
3.1 硬件选型与显存估算
先说结论:训练这套模型不需要多么夸张的硬件,GTX 1660 Ti 6GB这种级别的显卡完全够用。我实际训练用的就是一台1660 Ti的机器,选的模型是YOLOv8s,输入分辨率640x640,batch size设为16,显存占用大约5.2GB,刚好卡在边界上跑完300个epoch。如果你手里的显存只有6GB,那就别硬上YOLOv8m或者更大的模型,训练速度慢,batch size还只能压到8以下,反而影响BN层统计稳定性。
推理阶段就更宽松了。CPU用ONNX Runtime跑单张640x640图片,大概100到300毫秒,做一两路视频流没问题;要是上了GPU或者RK3588的NPU,单帧推理时间可以压到30毫秒以内,完全满足实时视频分析的需求。如果你用的是Jetson Nano或树莓派这类板子,也可以用YOLOv8n模型,参数更少,帧率能再翻一倍。
显存和batch size的关系可以用一个粗略公式估算:YOLOv8s在640分辨率下,单张图的训练显存占用大约在300到400MB之间,再加上模型权重、梯度、优化器状态,实际开销会翻倍。6GB显存跑batch 16已经是极限了,batch 20很可能直接OOM。如果你发现显存不够,优先调小batch size,不要急着调小图片分辨率——分辨率太低,小尺寸安全帽会直接丢失特征,颜色更分不清。
3.2 环境安装:别在版本上浪费时间
新手的卡点七成在环境配置上,这里直接给一套经过验证的版本组合:
conda create -n yolov8 python=3.10 -y conda activate yolov8 pip install torch==2.1.2 torchvision==0.16.2 --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics==8.2.0PyTorch版本用2.1.x或2.2.x都行,不需要追求最新。网上有人问PyTorch 2.13支持不支持YOLOv8,先别管这个,ultralytics库对PyTorch版本的要求其实很宽,只要不是太老的PyTorch 1.x,都能正常跑,重点是CUDA版本和显卡驱动要匹配。装完之后跑一句验证命令:
yolo predict model=yolov8s.pt source=https://ultralytics.com/images/bus.jpg能输出推理结果,就说明环境没问题。注意第一次运行会自动下载预训练权重,如果下载比较慢,可以提前手动把yolov8s.pt下载好放到当前目录下,ultralytics会自动识别。
3.3 数据目录组织与YAML配置
数据目录按下面的结构组织,这是ultralytics默认约定的格式,照做就好:
helmet_dataset/ ├── images/ │ ├── train/ # 1800张 │ ├── val/ # 600张 │ └── test/ # 600张 ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── helmet.yamlhelmet.yaml文件内容如下,path是数据集根目录的绝对路径或相对路径,train/val/test指向图片子目录,names里的类别名称必须和标注文件里的类别ID一一对应,顺序错一个,整个模型就废了:
path: ./helmet_dataset train: images/train val: images/val test: images/test nc: 5 names: 0: yellow_helmet 1: white_helmet 2: red_helmet 3: blue_helmet 4: head一个很容易踩的坑:val目录里的每张图片必须在labels/val里有对应的txt文件,哪怕这张图一个目标都没有,也要放一个空的txt文件,没有对应标注文件的图片会被ultralytics跳过,但不会报错,最终导致实际参与验证的图片数量比预期少很多。
4. 训练全流程:模型选择、超参设置与收敛判断
4.1 训练命令与关键参数解释
我最终的训练命令长这样:
yolo detect train \ data=helmet.yaml \ model=yolov8s.pt \ epochs=200 \ imgsz=640 \ batch=16 \ patience=30 \ device=0 \ workers=4 \ optimizer=auto \ seed=42逐项解释一下。model=yolov8s.pt表示从官方预训练权重开始微调,这是几乎所有场景的默认最佳选择。预训练模型已经在COCO数据集上学过通用的特征提取能力,底层的边缘、纹理、形状特征直接迁移过来,比自己从零初始化训练少花三倍时间,最终精度也更高。epochs=200不是越多越好,我这里设置了patience=30,意思是验证集指标连续30个epoch没有提升就自动早停。optimizer=auto让ultralytics根据模型自动选择优化器,实测下来它自己选SGD或AdamW的效果通常比手动指定更好。
imgsz=640是速度和精度的平衡点。安全帽这种目标在监控画面里经常很小,如果你有大量远距离目标,可以考虑把imgsz提到960,小目标recall会有明显提升,但训练时间会增加一倍。对3000张图的数据量来说,我用640训练后测试,mAP已经足够高,就没必要加分辨率。
4.2 训练过程的损失曲线怎么判断
训练开始后,会在runs/detect/train目录下生成本轮的results.png,这是判断训练是否正常的最重要依据。这张图里包含三类损失曲线:box_loss边界框回归损失、cls_loss分类损失、dfl_loss分布焦点损失。以我的训练过程为例,三者的曲线趋势非常典型:前30个epoch快速下降,30到80个epoch慢速下降,80到150个epoch进入平台期,只有微小的波动。
判断训练是否正常的几条经验:第一,train_loss和val_loss应该同步下降。如果train_loss还在下降但val_loss开始回升,说明已经过拟合了,这时候调低epochs或者加数据增强才有意义,继续跑只会让测试集指标变差。第二,cls_loss曲线如果在某个epoch出现突然跳升,几乎可以肯定是数据集里有标注错误的样本,模型正在努力拟合一个错误标签,损耗就炸了。第三,mAP50曲线通常是阶梯式上升的,不要因为中间出现几个epoch的平台期就急着重启训练,给它一点耐心。
4.3 收敛后主要看哪些指标
训练结束或早停后,ultralytics会在runs/detect/train/weights/下保存两个权重文件:best.pt和last.pt。best.pt是验证集mAP最高的那一个epoch的权重,last.pt是最后一个epoch的权重。我最终用best.pt做后续验证。
自己训练的模型,我建议重点看这几个指标:
| 指标 | 含义 | 参考合格值 |
|---|---|---|
| mAP50 | IoU阈值0.5下的平均精度均值 | 0.90以上为优秀 |
| mAP50-95 | 不同IoU阈值下平均精度的均值,更严格 | 0.70以上可接受 |
| Precision | 所有预测框里预测正确的比例,误检越少越高 | 0.90左右 |
| Recall | 所有真实目标里被找出来的比例,漏检越少越高 | 0.88以上 |
| F1-score | 精度和召回率的调和平均 | 0.90左右 |
我的最终结果是mAP50约0.938,mAP50-95约0.746,红色帽子的recall从0.7提升到0.87。对于安全帽检测这个场景,mAP50达到0.93已经具备实际部署的条件了。
5. 验证、混淆矩阵与嵌入式部署实践
5.1 验证集跑一遍,重点看混淆矩阵
训练结束后,先用下面的命令在验证集上正式评估一次:
yolo detect val data=helmet.yaml model=runs/detect/train/weights/best.ptultralytics会在结果目录里生成confusion_matrix.png,这个图是判断颜色分类是否做对的关键。对于本任务,混淆矩阵的主要观察点有两个:一是yellow_helmet类别是否和white_helmet类别互相混淆。这两个颜色一个偏亮黄一个偏白,在过曝的图片里确实容易混,我在第一版模型里就看到了大约5%的黄帽被预测成白帽。解决方法是补充一批过曝和强烈阳光下的图片,同时把标注规范里“以自然光下真实颜色为准”的标准再强调一遍。二是head类是否频繁被预测为某一种帽子。如果一张没戴帽子的头被预测成yellow_helmet,说明模型学到的帽子特征还不够聚焦,需要检查标注框是否包进了人脸。
验证集上确认没问题后,再用test目录里的600张图测试一次,确认mAP与验证集相差不超过2个百分点,差的多了说明数据划分有问题,或者某些场景在验证集里过拟合了。
5.2 导出ONNX并部署到嵌入式设备
训练好的模型最终要落到硬件上跑。ultralytics官方的导出命令很简洁:
yolo export model=runs/detect/train/weights/best.pt format=onnx opset=12 imgsz=640导出后会得到一个best.onnx文件,opset选12是为了兼容更多推理框架,如果目标设备是最新的TensorRT或者OpenCV,opset可以适当选新版。用ONNX Runtime做CPU推理的示例代码:
import cv2 import numpy as np import onnxruntime as ort session = ort.InferenceSession("best.onnx") input_name = session.get_inputs()[0].name img = cv2.imread("test.jpg") img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) resized = cv2.resize(img_rgb, (640, 640)) input_tensor = resized.astype(np.float16 if "float16" in session.get_inputs()[0].type else np.float32) / 255.0 input_tensor = np.transpose(input_tensor, (2, 0, 1))[None] outputs = session.run(None, {input_name: input_tensor}) # outputs[0] 形状为 (1, 84, 8400),需要做NMS后处理推理输出的原始数据是一个维度为[1, 4+类别数+1, 8400]的特征图,8400是YOLOv8在不同尺度特征图上的预测框总数,需要自己写非极大值抑制后处理,把重叠的框去掉,保留置信度最高的那一个。这个后处理逻辑如果不想自己写,可以直接用ultralytics的YOLO类加载ONNX模型,它会自动完成前处理和后处理:
from ultralytics import YOLO model = YOLO("best.onnx") results = model("test.jpg")但如果是部署到RK3588、Jetson这类嵌入式设备,我强烈建议用厂商自带的NPU工具链做量化导出。RK3588对应的工具链支持把ONNX模型转成RKNN格式,Jetson系列可以直接用TensorRT加速。需要注意两个点:一是量化时要用一批真实场景图片作为校准集,不能只用训练集图片,否则量化损失会比较大;二是安全帽检测部署到板子上之后,输入分辨率不要盲目调小,我实测过降到416x416后,远距离小目标的漏检率明显上升,实时性和精度最终选了512x512作为折中方案。
5.3 部署中遇到的硬坑
最典型的坑是推理结果和训练时看到的可视化效果不一致。训练时有NMS后处理,有置信度阈值过滤,导出的ONNX裸输出是不过滤的,如果你直接拿模型输出全部框去画图,会看到一大堆置信度只有0.1的垃圾框。部署时要自己设定置信度阈值,一般设为0.25到0.35之间,NMS的IoU阈值设为0.5左右。
另一个坑是颜色偏移。嵌入式设备摄像头通常有明显的色偏,用PC上训练时用的图片做测试没问题,一接到现场摄像头就发现黄色帽子全变成橙色了。解决思路是在采集训练数据时,把现场摄像头的截图也放一部分进去,让模型提前适应设备色彩特性。如果做不到,就在摄像头端先做白平衡校正,再喂给模型。这种数据域不一致的问题,靠调模型参数是解决不了的,只能在数据环节下功夫。
6. 踩坑记录与下一步可选的改进方案
6.1 我的实测踩坑清单
回看这轮从数据到部署的全过程,有几个坑花了很长时间才定位到根因,值得记录一下。
第一个是过曝图片导致黄白混色。有一批来自现场摄像头的图片,安全帽区域严重过曝,黄色帽子在画面上看起来几乎就是白色,被模型全部预测成了white_helmet。我当时以为是模型没训好,反复调损失函数和超参,最后才发现是数据分布的问题。解决方法是把过曝图片挑出来,做了曝光补偿处理和亮度增强后重新加入训练集,同时让模型看到更多“看似白色但其实是黄色”的难例。这件事让我明白,模型的错误往往是数据问题的镜像,先查数据再调参数,是处理一切检测异常的优先顺序。
第二个是顶视摄像头视角下的安全帽几乎只有帽顶一个圆面,和正常视角的安全帽外观差异巨大。第一版模型在这类视角下recall直接掉到0.6以下。后来补了一批从高处俯拍的工地图片,模型才学会跨视角识别。
第三个是类别不平衡对recall的影响。红色帽子样本太少,cls_loss虽然总体下降,但红色类的召回率始终上不去。补充增强后的红色样本后问题解决。这提醒我在设计数据分布时,要让每个类别至少占15%,否则模型很容易学会“偷懒”,把所有不确定的框都分给高频类别。
6.2 模型结构层面的改进选项
如果你的数据集比3000张更大,或者项目有更高的精度要求,光靠YOLOv8s本身的空间可能不够,可以在模型结构上动手。网上讨论比较多的几个方向,我按性价比排序说一下看法。
第一是把注意力机制嵌入到C2F模块里。EMA或ECA这类轻量注意力模块,参数量增加很少,但能明显提升颜色特征的区分度。实现方式是在ultralytics/nn/modules.py里定义新的C2F变体,再把yolov8.yaml中对应层的编号替换进去。这个改法对颜色分类尤其管用,因为注意力机制能帮模型更聚焦于帽子区域的颜色特征,而不是背景干扰。EMA结合进C2F的代码实现,在ultralytics改进教程里已经有现成方案,直接搜索“EMA注意力机制融入C2F”就能找到对应的配置说明。
第二是替换下采样模块。YOLOv8默认的下采样是简单的步长为2的卷积,有研究者提出ADown模块,先用平均池化和最大池化分支降低特征丢失,再融合。对小目标的改善比较明显,代价是推理耗时增加一点点。如果摄像头画面距离远、帽子很小,这个方向值得试。
第三是增加小目标检测头。YOLOv8的检测头通常输出三个尺度,如果你发现大量安全帽只有几个像素大小,可以考虑在P2层新增一个更浅尺度的检测头,专门负责小目标。代价是显存占用明显增加,6GB显卡基本跑不动,需要上12GB以上显存的卡。
6.3 数据层面的后续优化方向
结构改进做完了,再回头看数据。当前3000张数据覆盖了晴天、阴天、黄昏、夜间灯光等基本光照条件,但还缺少雨天、雾天、逆光直射这三种恶劣场景。如果你的项目要部署的工地恰好处于多雨地区,这几种缺失场景会严重影响实际表现。补数据的思路不是盲目增加数量,而是每类场景补200到300张关键样本,让模型见过足够多的“坏情况”。
另一个被低估的方向是针对性测试和标注校验。在训练完第一版模型后,我特意把测试图片按“容易识别”和“难以识别”分成两组,让标注员重新审视难例组的标注质量,结果发现不少漏标和错标。每轮重新训练前先修正旧数据,比添加新数据更重要,因为干净数据+中等数量,效果往往强过脏数据+大数量。
最后再分享一下我这次做完的整体体会:安全帽颜色检测这个项目,真正的瓶颈从来不在模型结构,而在数据组织和设备适配。3000张图配合YOLOv8s,已经能训练出具备落地能力的模型,关键是每个环节都要按规范做。如果你手头恰好有几千张工地图片想做类似的事,我的建议是不要纠结模型选yolov8s还是yolov8m,先把数据整理干净、把颜色一致性统一好,训练出来的模型一定不会让你失望。这个流程跑通一次之后,后面再加新颜色、新场景,都是流水线式的活,会顺手很多。
本文还有配套的精品资源,点击获取