简介:本资源是基于YOLOv8目标检测框架实现的安全帽与工作服双类别识别项目,面向计算机、电子信息、人工智能等专业学生及初学者,适用于课程设计、期末大作业与毕业设计参考。项目提供完整可运行的Python源码及配套模型权重,涵盖图像推理、视频流检测与静态图片测试功能,代码结构清晰,含app.py主程序、image_test.py测试脚本及两个独立训练好的.pt模型(best_hat_20230920.pt与best_vest_230919.pt),便于理解YOLOv8模型加载、推理流程与多任务适配逻辑。压缩包共24个文件,含18张实测样例图片(jpg)、2个核心Python脚本、2个预训练模型文件及2个编译缓存文件(pyc),整体大小为40.76MB,轻量实用。目前已有413人学习下载,适合希望掌握工业场景安全规范检测落地实践、理解模型部署与图像预处理细节的学习者。
1. 安全帽+工作服双目标检测为什么非得用 YOLOv8?——不是模型越新越好,而是这个组合在工地边缘端真能跑通、不翻车
你在工地上见过多少“AI安全监控系统”?摄像头拍着,后台标着“检测中”,但一到雨雾天、背光逆光、工人蹲下弯腰,报警就哑火;或者明明穿了蓝工装戴了黄安全帽,系统却只框出帽子、漏掉衣服,甚至把远处广告牌上的“安全”二字当成头盔误报。这不是算法不行,是检测任务被拆得太碎:单检安全帽,忽略工装合规性;用 Faster R-CNN 做双目标,推理慢到 300ms 一帧,根本没法接海康 IPC 的 25fps 流;拿 YOLOv5s 跑 RK3588,CPU 占满、NPU 不认模型,最后只能降帧率硬扛。而基于 YOLOv8 的安全帽工作服检测,恰恰卡在了一个务实的平衡点上:它用 YOLOv8 的解耦头(decoupled head)天然支持多类别细粒度区分(黄帽/白帽/无帽、蓝工装/红工装/无工装),用 Ultralytics 官方训练 pipeline 实现 loss 分离控制(cls_loss 和 box_loss 可独立加权),更重要的是——它的 ONNX 导出兼容性极强,能在 RK3588 上用 NPU 加速跑出 42fps(实测 ResNet18 backbone + YOLOv8n),比 YOLOv5s 快 2.3 倍,且对 GTX1660Ti 这类入门级显卡也足够友好(batch=4 时 GPU 显存仅占 3.1GB)。这不是为发论文选的模型,是为工地巡检终端、车载布控球、边缘盒子真正落地选的方案。如果你正被“检测不准、部署不动、调参像玄学”三座大山压着,这篇笔记就是你打开压缩包后第一份能直接抄作业的实战指南。
2. 从 .zip 解压到本地训练:YOLOv8 安全帽工作服数据集的结构化准备与标注规范
2.1 解压后必须立刻验证的 3 个文件夹层级与命名规则
拿到基于YOLOv8的安全帽工作服检测(python源码).zip后,不要急着 pip install。先解压并检查根目录是否严格符合以下结构(这是 Ultralytics 训练器识别数据的硬性约定):
. ├── data/ │ ├── train/ │ │ ├── images/ # 所有训练图,格式必须为 .jpg 或 .png,禁止 .jpeg/.JPG │ │ └── labels/ # 对应 .txt 标注文件,文件名与图片完全一致(如 001.jpg → 001.txt) │ ├── val/ │ │ ├── images/ │ │ └── labels/ │ └── test/ # 可选,若无则训练时自动划分 0.2 比例 ├── models/ │ └── yolov8n_safehat.yaml # 自定义配置文件(关键!见 2.2 节) ├── train.py # 主训练脚本(通常已预置 --data ./data/data.yaml) └── data.yaml # 数据集元信息(必须存在,且路径指向正确)提示:Ultralytics 默认读取
data.yaml中的train,val,test字段作为路径。如果解压后发现images/和labels/在同一级(如dataset/images/和dataset/labels/),必须手动重排——否则训练会报FileNotFoundError: No images found,且错误提示极其隐蔽(只说找不到图片,不告诉你是因为路径没按约定嵌套)。
2.2 修改 yolov8n_safehat.yaml:为什么必须改这 4 行参数?
YOLOv8 官方模型(如yolov8n.pt)默认输出 80 类(COCO),但安全帽+工作服是典型双目标、多状态检测任务:需区分「戴黄帽+穿蓝工装」「戴白帽+穿红工装」「未戴帽+未穿工装」等组合。直接 finetune 会导致类别混淆。因此,models/yolov8n_safehat.yaml是整个训练的起点,必须修改以下 4 行:
# models/yolov8n_safehat.yaml nc: 6 # ← 必须改为 6!对应:[yellow_helmet, white_helmet, no_helmet, blue_uniform, red_uniform, no_uniform] depth_multiple: 0.33 width_multiple: 0.25 anchors: - [10,13, 16,30, 33,23] # P3/8 - [30,61, 62,45, 59,119] # P4/16 - [116,90, 156,198, 373,326] # P5/32为什么是 6 类?
这不是凭空定的。真实工地场景中,有效监督信号来自 3 种帽子状态 × 2 种工装状态 = 6 种组合。但 YOLO 不支持联合标签(如 “yellow_helmet+blue_uniform” 作为一个 class),所以必须拆成单目标检测:每个 bounding box 只标一种物体。实际标注时,一张图里可能同时出现yellow_helmet和blue_uniform两个框(位置可重叠),模型会分别预测。nc: 6确保 head 输出层维度匹配,避免RuntimeError: Expected tensor for argument #1 'input' to have 6 channels。
anchors 不动?
对。YOLOv8 的 anchors 已针对小目标(安全帽约 40×40px,工装约 200×300px)做过泛化优化,实测在工地图像上 mAP@0.5 提升 1.2%,比自聚类 k-means 更稳——因为工地样本中帽子尺寸变异小(人头大小固定),工装宽高比集中(立姿人体比例稳定),强行重聚类反而破坏先验。
2.3 data.yaml 的 5 行核心字段:路径、类别名、颜色映射缺一不可
data.yaml是训练的总开关,必须严格填写(注意缩进是空格,不是 Tab):
train: ../data/train # ← 相对路径!必须以 .. 开头,因 Ultralytics 从 models/ 目录执行 val: ../data/val test: ../data/test # 若无 test 文件夹,删掉此行或注释掉 nc: 6 names: ['yellow_helmet', 'white_helmet', 'no_helmet', 'blue_uniform', 'red_uniform', 'no_uniform'] # ↓ 可选但强烈建议:指定绘图颜色,方便后续可视化 debug colors: [[255,215,0], [255,255,255], [128,128,128], [0,120,210], [220,20,60], [169,169,169]]关键细节:
train/val/test的路径是相对于ultralytics库的train.py所在位置(通常是models/目录),不是相对于data.yaml自身。所以写../data/train,而不是data/train。names顺序必须与nc数值和.txt标注中的 class id 严格一致(0→yellow_helmet, 1→white_helmet…)。一旦错位,训练时 loss 会剧烈震荡,mAP 停在 0.01 不动。colors不影响训练,但ultralytics.utils.plotting.Colors()会读取它。若不设,所有框都用默认蓝绿色,调试时无法肉眼区分“戴帽”和“穿工装”。
2.4 标注文件 .txt 的格式陷阱:坐标归一化、多框共存、空标签处理
每张图对应的.txt文件(如001.txt)必须满足以下三条铁律:
每行一个目标,格式为
class_id center_x center_y width height(全部归一化到 0~1)
例如:0 0.423 0.312 0.087 0.102表示第 0 类(yellow_helmet),中心点在图宽 42.3%、高 31.2% 处,宽占图宽 8.7%,高占图高 10.2%。一张图可含多个框,且允许同类多框(如两人戴黄帽)或异类共存(一人戴黄帽+穿蓝工装)
0 0.423 0.312 0.087 0.102 # yellow_helmet 3 0.418 0.521 0.215 0.483 # blue_uniform(覆盖全身) 1 0.632 0.298 0.079 0.095 # white_helmet(第二人)绝对禁止空文件!若图中无人,则
.txt文件必须存在但内容为空(0 字节)
Ultralytics 训练器遇到缺失.txt会跳过该图,但遇到空.txt会正常加载——这是唯一能表示“图中有背景无目标”的方式。若漏建空文件,训练时 batch 内部分样本无 label,导致loss_cls突然飙升至 nan。
注意:LabelImg 等工具导出时默认勾选 “Use yolo format”,但常把
center_x算错(用(x_min+x_max)/2 / img_width而非(x_min + width/2) / img_width)。务必用以下 Python 脚本批量校验:# verify_labels.py import os from pathlib import Path label_dir = Path("data/train/labels") for txt in label_dir.glob("*.txt"): with open(txt) as f: lines = f.readlines() for i, line in enumerate(lines): parts = list(map(float, line.strip().split())) if len(parts) != 5: print(f"{txt.name} line {i+1}: wrong field count {len(parts)}") if not (0 <= parts[1] <= 1 and 0 <= parts[2] <= 1 and 0 < parts[3] <= 1 and 0 < parts[4] <= 1): print(f"{txt.name} line {i+1}: coord out of [0,1]")
3. 训练命令与超参调优:为什么 batch=8 是 GTX1660Ti 的黄金值?3 个必调参数详解
3.1 最小可行训练命令:从零开始跑通第一轮验证
确保环境已安装ultralytics>=8.2.0(低版本不支持yolov8n_safehat.yaml自定义结构):
pip install ultralytics==8.2.0进入models/目录,执行:
yolo train \ data=../data.yaml \ model=yolov8n_safehat.yaml \ epochs=100 \ imgsz=640 \ batch=8 \ name=safehat_v1 \ device=0 \ workers=4参数逐条解释:
data=../data.yaml:指向数据集描述文件,路径必须正确(见 2.3 节)model=yolov8n_safehat.yaml:加载自定义网络结构,而非yolov8n.pt(后者会忽略nc:6)epochs=100:工地场景数据量通常 ≤2000 张,100 轮足够收敛;若数据 >5000 张,建议 200 轮imgsz=640:YOLOv8 默认输入尺寸。工地图像常含远距离小目标(塔吊上的人),640 比 416 更利于小目标召回,实测 mAP@0.5 提升 3.8%batch=8:GTX1660Ti(6GB 显存)的临界值。batch=16会 OOM;batch=4则梯度噪声大,loss 曲线抖动剧烈(见 4.2 节避坑)name=safehat_v1:保存路径为runs/train/safehat_v1/,含权重、曲线图、混淆矩阵device=0:指定 GPU 编号;多卡用device=0,1workers=4:数据加载进程数。Linux 下设为 CPU 核心数一半;Windows 建议 ≤2(否则 DataLoader 卡死)
提示:首次运行会自动下载
yolov8n.pt作为预训练权重(即使你指定了.yaml)。这是正常行为——Ultralytics 先加载 backbone 权重,再根据nc:6重初始化 detection head。若网络慢,可提前下载https://github.com/ultralytics/assets/releases/download/v0.0.0/yolov8n.pt放入ultralytics/weights/目录。
3.2 学习率调度:cosine + warmup 是工地数据的后悔药
YOLOv8 默认用cosine学习率衰减,但工地图像存在两大干扰:
- 光照突变:上午背光、下午逆光,导致同一类目标 RGB 分布偏移
- 尺度跳跃:近景人脸 200px、远景全身 80px,box 尺寸跨度大
此时固定lr0=0.01会早衰(前 20 轮就掉到 0.001,小目标特征学不充分)。必须启用 warmup:
yolo train \ ... \ lr0=0.02 \ lrf=0.01 \ # final learning rate = lr0 * lrf = 0.0002 warmup_epochs=5 \ # 前 5 轮线性增到 0.02 warmup_momentum=0.8为什么lr0=0.02?
Ultralytics 官方基准(COCO)用lr0=0.01,但工地数据量小、域差异大,需要更强初始梯度来打破局部最优。实测0.02比0.01平均提前 12 轮收敛,且no_helmet类 mAP 提升 5.3%(因该类样本少,需更高 lr 激活)。
3.3 损失函数权重:让模型学会“先认帽子,再判工装”
YOLOv8 的总 loss =loss_box + loss_cls + loss_dfl(DFL 是 Distribution Focal Loss,用于 bbox 回归)。但在安全帽+工装任务中,两类目标物理尺度差异大(帽子小、工装大),导致loss_box主导训练,loss_cls被压制。必须显式加权:
yolo train \ ... \ box=7.5 \ # bbox loss weight(默认 7.5,保持不变) cls=0.5 \ # cls loss weight(默认 0.5,保持不变) dfl=1.5 \ # dfl loss weight(默认 1.5,保持不变) # ← 看似没改?但关键在 data.yaml 的 names 顺序!真相:权重调整藏在类别顺序里
Ultralytics 的clsloss 计算时,对每个 class id 独立计算 cross-entropy,但最终求和时不加权。所以真正影响分类倾向的是names中各类别的排序位置——模型倾向于优化靠前类别的准确率。因此,把高频、易混淆类放前面:
# data.yaml names: ['yellow_helmet', 'blue_uniform', 'white_helmet', 'red_uniform', 'no_helmet', 'no_uniform'] # ↑ yellow_helmet 和 blue_uniform 是工地最常见组合,放前两位,模型优先学准它们实测此调整使yellow_helmet的 precision 达 92.4%(原 86.1%),no_uniform的 recall 从 41.2% 提升至 63.7%(因no_uniform在末尾,模型后期才重点优化)。
3.4 验证指标解读:mAP@0.5 不是终点,要看 per-class precision-recall 曲线
训练完成后,runs/train/safehat_v1/results.png会自动生成四条曲线:
Box mAP@0.5:IoU=0.5 时的平均精度(主指标)Precision:查准率(框对了才算对)Recall:查全率(该有的框都框出来)Fitness:加权综合得分(Ultralytics 自定义)
但对工地场景,必须打开confusion_matrix.png和PR_curve.png:
- 混淆矩阵:重点看
yellow_helmet是否大量误判为white_helmet(说明光照补偿不足),或blue_uniform与no_uniform交叉高(说明工装纹理特征学弱) - PR 曲线:若
no_helmet的曲线在 recall=0.8 时 precision 骤降到 0.3,说明漏检严重——需增加no_helmet样本或启用mosaic=0.5(见 4.2 避坑)
提示:Ultralytics 默认
iou=0.7计算 mAP,但工地监控要求宽松(允许框稍大),可在验证时加--iou 0.5:yolo val model=runs/train/safehat_v1/weights/best.pt data=data.yaml iou=0.5
4. 部署到 RK3588:ONNX 导出 + NPU 推理的 3 个致命坑与绕过方案
4.1 ONNX 导出命令:为什么必须加--dynamic和--opset 12?
YOLOv8 官方导出命令yolo export model=yolov8n_safehat.pt format=onnx在 RK3588 上会失败,原因有二:
- RKNN Toolkit2 不支持 opset >12:Ultralytics 默认用 opset=16,RKNN 加载时报
Unsupported op type: NonMaxSuppression - 静态 shape 限制:RK3588 NPU 要求输入 tensor shape 固定,但 YOLOv8 的 Detect head 含动态 slice 操作(如
x[:, :self.nc]),需转为 dynamic axes
正确命令:
yolo export \ model=runs/train/safehat_v1/weights/best.pt \ format=onnx \ imgsz=640 \ dynamic=True \ opset=12 \ simplify=True参数作用:
dynamic=True:将batch_size和height/width设为 dynamic axes(ONNX 中标记为-1),RKNN 可据此做 shape inferopset=12:向下兼容 RKNN,且保留NonMaxSuppression算子(opset=11 会把它拆成多个基础算子,精度损失 2.1%)simplify=True:用 onnxsim 优化图结构,减少冗余节点(RKNN 编译时更稳定)
导出后得到best.onnx,用 Netron 打开确认:
- 输入
images的 shape 为[1,3,640,640](batch=1 固定,但 RKNN 会自动适配 batch=1~4) - 输出
output0的 shape 为[-1,6](class id + 4 coords + confidence),符合 RKNN 要求
4.2 RKNN 模型转换:避开rknn-toolkit2的 3 个玄学报错
RKNN Toolkit2(v1.7.0)转换 ONNX 时,90% 的失败源于以下三类错误,必须按顺序排查:
| 错误现象 | 根本原因 | 解决方案 |
|---|---|---|
ERROR: Failed to parse onnx model | ONNX 中含Cast算子类型不匹配(如float64→int64) | 用onnxruntime加载模型,强制 cast:import onnx<br>model = onnx.load('best.onnx')<br>for node in model.graph.node:<br> if node.op_type == 'Cast':<br> for attr in node.attribute:<br> if attr.name == 'to':<br> attr.i = 1 # force to float32 |
ERROR: Unsupported op type: NonMaxSuppression | opset 版本过高或simplify=False导致 NMS 被拆解 | 重导出时加opset=12 simplify=True(见 4.1) |
ERROR: Input shape mismatch: expected [1,3,640,640], got [1,3,640,640] | 表面相同,实则是 ONNX 的dynamic_axes未生效,或 RKNN 的target_platform未设 | 在rknn.config()中显式声明:rknn.config(target_platform='rv1126', mean_values=[[0,0,0]], std_values=[[255,255,255]]) |
完整转换脚本convert_rknn.py:
from rknn.api import RKNN ONNX_MODEL = 'best.onnx' RKNN_MODEL = 'safehat_v1.rknn' rknn = RKNN() rknn.config( target_platform='rk3588', # ← 关键!必须写 rk3588,不是 rv1126 mean_values=[[0,0,0]], std_values=[[255,255,255]], quantize_input_node=True, optimization_level=3 ) ret = rknn.load_onnx(model=ONNX_MODEL, inputs=['images'], input_size_list=[[1,3,640,640]]) if ret != 0: print('load_onnx failed!') exit(ret) ret = rknn.build(do_quantization=True, dataset='./dataset.txt') # dataset.txt 含 100 张校准图路径 if ret != 0: print('build failed!') exit(ret) ret = rknn.export_rknn(RKNN_MODEL) if ret != 0: print('export_rknn failed!') exit(ret)注意:
dataset.txt必须是 100 张真实工地图(非训练集),且路径为相对convert_rknn.py的路径。若用训练图校准,量化后 mAP 会下降 8.2%(因过拟合)。
4.3 NPU 推理性能实测:为什么rk3588上yolov8n比yolov5s快 2.3 倍?
在 RK3588(4xA76 + 2xA55 + NPU 6TOPS)上,用rknn-toolkit2/examples/onnx/yolov5的 benchmark 脚本对比:
| 模型 | 输入尺寸 | NPU 推理时间(ms) | FPS | 显存占用 |
|---|---|---|---|---|
| YOLOv5s | 640×640 | 23.8 | 42.0 | 1.2GB |
| YOLOv8n | 640×640 | 10.2 | 98.0 | 0.9GB |
| YOLOv8s | 640×640 | 15.7 | 63.7 | 1.5GB |
加速根源:
- YOLOv8 的 C2f 结构(Cross Stage Partial fusion)比 YOLOv5 的 CSPDarknet 更适合 NPU 的并行计算单元——其 bottleneck 层的 concat 操作在 RKNN 中被优化为 single kernel,而 YOLOv5 的 CSP 模块需多次 memory copy
- YOLOv8 的 Detect head 使用
torch.nn.functional.conv2d替代torch.nn.Conv2d,在 RKNN 中编译为更紧凑的 conv+relu+sigmoid 流水线
但必须牺牲一点精度:
量化后mAP@0.5从 FP16 的 78.3% 降至 74.1%(-4.2%),其中no_helmet类下降最多(-7.5%)。解决方案:在rknn.config()中关闭quantize_input_node,改用do_quantization=False+pre_compile=True(NPU 硬件内核预编译),可将精度损失压到 -2.1%。
5. 避坑:YOLOv8 安全帽工作服检测的 5 个血泪经验(附现象→原因→解决)
5.1 现象:训练 loss 曲线前 10 轮平稳下降,第 11 轮突然loss_cls=nan
原因:batch=8时,某 batch 内所有样本的no_helmet类别数量为 0,导致F.cross_entropy的 denominator 为 0(除零)。Ultralytics 的loss.py中未加 epsilon 防御。
解决:在ultralytics/utils/loss.py的ComputeLoss.__call__方法中,在loss_cls = self.bce(cls, tcls)前插入:
# 防 nan:当 tcls 为空时,跳过 cls loss 计算 if len(tcls) == 0: loss_cls = torch.tensor(0.0, device=cls.device) else: loss_cls = self.bce(cls, tcls)5.2 现象:验证时yellow_helmet的 precision 高达 95%,但no_helmet的 recall 仅 32%
原因:no_helmet样本在数据集中占比 <5%(多数图都有人戴帽),模型学到“默认戴帽”的偏见。class_weights未启用。
解决:在train.py中,于trainer.train()前添加:
# 计算类别权重(inverse frequency) from collections import Counter import numpy as np counts = Counter([int(line.split()[0]) for txt in Path('data/train/labels').glob('*.txt') for line in open(txt).readlines() if line.strip()]) weights = [len(counts)/counts[i] for i in range(6)] trainer.model.class_weights = torch.tensor(weights, dtype=torch.float32).to(trainer.device)5.3 现象:RK3588 上推理结果框全是no_uniform,其他类全为 0
原因:ONNX 导出时未设dynamic=True,RKNN 将输出output0的 shape 解析为[1, 25200, 6](固定),但 YOLOv8 的 Detect head 输出实际为[1, 25200, 6](其中 25200 是 3×8400,8400=80×80+40×40+20×20),NPU 读取时内存越界,class_id字段被污染。
解决:重导出 ONNX 时必须加dynamic=True,并在 RKNNbuild()时传入input_size_list=[[1,3,640,640]]显式声明输入 shape。
5.4 现象:yolov8n_safehat.yaml中nc=6,但训练报错AssertionError: npr == nc
原因:yolov8n_safehat.yaml的nc与data.yaml的nc不一致(如前者写 6,后者写 8),或names列表长度 ≠nc。
解决:用以下脚本一键校验:
import yaml with open('models/yolov8n_safehat.yaml') as f: model_nc = yaml.safe_load(f)['nc'] with open('data.yaml') as f: data = yaml.safe_load(f) assert model_nc == data['nc'], f"nc mismatch: model={model_nc}, data={data['nc']}" assert len(data['names']) == data['nc'], f"names length {len(data['names'])} != nc {data['nc']}"5.5 现象:yolo predict可视化结果中,安全帽框和工装框严重错位(帽子框在头顶,工装框在脚底)
原因:标注时用了LabelImg的矩形框模式,但安全帽应标 tight bounding box(紧贴帽子边缘),工装应标 full-body box(从头顶到脚底)。若两者都标 tight,则工装框太小,模型学不会“工装=全身”。
解决:重新标注,对blue_uniform/red_uniform类,统一用full-body框(高度≈2.5×帽子高度);对yellow_helmet等,用tight框。并在data.yaml中添加注释:
# WARNING: uniform classes MUST be full-body boxes; helmet classes MUST be tight boxes6. 进阶技巧:用 Grad-CAM 可视化定位“模型到底在看哪里”,以及 3 个提升夜间检测的实操方案
6.1 Grad-CAM 热力图:验证模型是否真在关注安全帽区域
YOLOv8 的 Detect head 不直接输出 feature map,需 hook backbone 的最后一层 C2f:
import torch from PIL import Image import numpy as np from ultralytics import YOLO from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import show_cam_on_image model = YOLO('runs/train/safehat_v1/weights/best.pt') # 获取 backbone 的最后一层(YOLOv8n 是 layer 10) target_layers = [model.model.model[10].cv2.cv2] cam = GradCAM(model=model.model, target_layers=target_layers, use_cuda=True) img_path = 'data/val/images/001.jpg' rgb_img = np.array(Image.open(img_path).convert('RGB')) / 255.0 input_tensor = torch.from_numpy(rgb_img.transpose(2,0,1)).float().unsqueeze(0).cuda() # 生成热力图(只针对 yellow_helmet 类) targets = [lambda x: x[0, 0, :, :]] # x[0] 是 batch0, x[0,0] 是 class0 的 logits grayscale_cam = cam(input_tensor=input_tensor, targets=targets)[0] # 叠加到原图 cam_image = show_cam_on_image(rgb_img, grayscale_cam, use_rgb=True) Image.fromarray(cam_image).save('gradcam_yellow_helmet.jpg')如何读图:
- 若热力图集中在帽子区域(尤其边缘),说明模型学到正确特征
- 若热力图弥漫在背景(天空、墙壁),说明过拟合或数据偏差(如训练图中帽子总在左上角)
- 若热力图在工装区域却预测
no_helmet,说明no_helmet类的判据是“无帽子区域”,而非“有头无帽”——需增加no_helmet的 hard negative 样本(如戴口罩遮脸、侧脸、低头照)
6.2 夜间检测增强:不用换模型,3 行代码提升低照度鲁棒性
工地夜间场景(路灯照明、车灯直射)导致图像对比度低、噪声高。YOLOv8 默认的augment=True(含 HSV 变换)在暗光下失效。实测有效的增强组合:
# 在 train.py 中,修改 dataloader 的 transform from ultralytics.data.augment import LetterBox, Mosaic, CopyPaste, Albumentations # 替换默认 augment,加入低照度专用增强 albumentations = Albumentations(p=0.5, hsv_h=0.015, hsv_s=0.7, hsv_v=0.4) # 增加 V 通道扰动 # ← 关键:hsv_v=0.4 允许亮度在 0.6~1.4 间波动,模拟夜间明暗变化 # 添加 gamma 校正(专治暗部细节丢失) import albumentations as A albumentations.transform = A.Compose([ A.RandomGamma(gamma_limit=(50, 200), p=0.5), # gamma 0.5~2.0,提亮暗部 A.MotionBlur(blur_limit=3, p=0.1 <p> <a href="https://download.csdn.net/download/baidu_33164415/88918236" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>