资讯中心

雾天目标检测实战:YOLOv5五类检测数据集与部署指南

📅 2026/9/28 12:31:04
雾天目标检测实战:YOLOv5五类检测数据集与部署指南
简介本资源是一套面向计算机视觉初学者与YOLOv5实践者的雾天场景目标检测实战项目聚焦低能见度环境下行人及常见车辆的识别难题覆盖人、轿车、公交车、自行车、摩托车五类目标。压缩包共2000个文件含1921个标注txt文件提供精确边界框与类别标签、40个Python脚本涵盖数据加载、模型训练、推理与评估全流程、23个YAML配置文件定义模型结构与超参整体大小169.55MB采用7z压缩解压即用。已有247人学习下载项目已迭代100个epoch验证集mAP0.5达0.58附带混淆矩阵、PR曲线、F1曲线等可视化结果runs/detect目录下还提供训练集全量推理效果图便于直观评估模型泛化能力。代码经实测可直接运行配套README与中文文档清晰说明使用路径适合快速上手、调优验证或作为雾天检测任务的基准参考。1. 雾天目标检测不是调参玄学YOLOv5 直接跑通行人车辆五类检测map0.5 达 0.58 的实战数据集到底能解决什么你是不是也试过在雾天视频里跑 YOLOv5结果 bbox 全飘在空气里、漏检率高得像没开检测不是模型不行是训练数据根本没见过“雾”——标准 COCO 或 VOC 里哪有浓雾中半隐半现的摩托车轮廓、被散射光糊掉边缘的公交车侧窗这个项目就是专治这种「环境失配」它不讲理论推导不堆论文引用就给你一套真实雾天采集、人工精标、结构即用的五类别检测资源包。4320 张雾天图像 对应 txt 标签人 / 轿车 / 公交车 / 自行车 / 摩托车训练完直接在 runs/detect 下看到推理结果图——不是 demo 截图是实打实的 val 集 100 张图全推理输出map0.50.58 不是训练日志里的幻觉数字而是你在终端敲python val.py --data data/foggy.yaml --weights runs/train/exp/weights/best.pt后亲眼看到的验证输出。它适合三类人刚学完 YOLOv5 基础想落地练手的新手、需要快速验证雾天鲁棒性的算法工程师、以及正在做智能交通或车载视觉方案但苦于缺乏恶劣天气数据的嵌入式团队。别再自己去爬雾天监控视频再标注了——这份数据集连目录结构都按datasets/images/train和datasets/labels/train对齐你解压后改两行路径就能进 training pipeline。2. 数据集结构与 YOLOv5 训练流程从 foggy.yaml 配置到 100 epoch 完整复现2.1 数据集组织规范为什么必须严格遵循 images/labels 分离结构YOLOv5 的train.py在加载数据时会默认从--data指定的 yaml 文件中读取train:和val:路径并自动拼接images/和labels/子目录。这意味着如果你把图片和标签混放在同一文件夹或者 labels 放在annotations/而非labels/训练会直接报错FileNotFoundError: No labels found in ...。本项目已预处理好标准结构datasets/ ├── images/ │ ├── train/ # 4320 张 jpg/png │ └── val/ # 100 张 jpg/png └── labels/ ├── train/ # 4320 个 .txt每行格式cls x_center y_center width height归一化 └── val/ # 100 个 .txt命名与 images/val/ 中图片一一对应如 001.jpg ↔ 001.txt提示检查标签是否合规最简单方法是用head -n 1 datasets/labels/train/001.txt看第一行是否为0 0.452 0.613 0.210 0.387这类五数值格式。若出现class_id x y w h之外的字符如空格、逗号、中文需用脚本清洗——我一般写个 3 行 Python 脚本clean_labels.py用re.sub(r[^0-9.\s], , line)清除非法符号。2.2 foggy.yaml 配置详解类别数、路径、颜色映射一个都不能错YOLOv5 通过 yaml 文件定义数据集元信息。本项目附带的data/foggy.yaml是关键入口内容如下已脱敏实际文件含完整路径train: ../datasets/images/train val: ../datasets/images/val nc: 5 names: [person, car, bus, bicycle, motorcycle] # 可选用于可视化时 bbox 颜色顺序必须与 names 一致 colors: [[255, 0, 0], [0, 255, 0], [0, 0, 255], [255, 255, 0], [255, 0, 255]]注意三个易错点nc: 5必须与names列表长度严格相等否则train.py初始化模型 head 时维度报错train/val路径是相对于 yaml 文件所在位置的相对路径若你把 foggy.yaml 移到根目录而 datasets 在上层则需改为../datasets/images/traincolors非必需但若你在detect.py中启用--line-thickness 3且未定义 colors绘图会 fallback 到默认灰度影响调试效率。2.3 启动训练100 epoch 参数配置与资源监控技巧项目已迭代 100 epoch但你不必照搬——根据你的 GPU 显存和时间预算可动态调整。核心命令如下python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data data/foggy.yaml \ --weights yolov5s.pt \ --name foggy_exp \ --cache参数说明--img 640输入尺寸。雾天图像细节损失严重640 是平衡精度与速度的起点若显存充足且想抓小目标如雾中远处自行车可试--img 768但 batch size 需同步下调--batch 16总 batch size。若单卡 24G 显存如 309016 可跑满若只有 12G如 2080Ti建议--batch 8并加--device 0显式指定 GPU--weights yolov5s.pt预训练权重。项目使用yolov5s轻量级若你追求更高 map可换yolov5m.pt但训练时间翻倍--cache关键提速项它将所有图片 decode 后缓存到 RAM避免每个 epoch 重复 IO。首次运行稍慢约多花 2 分钟加载后续 epoch 训练速度提升 40%。训练过程中实时监控runs/train/foggy_exp/results.csv的最后几行重点关注metrics/mAP_0.5和metrics/mAP_0.5:0.95列。当mAP_0.5连续 10 epoch 不升反降说明可能过拟合——此时应提前终止用--evolve进化超参或增加--augment开启马赛克增强。2.4 验证与推理如何用 val.py 和 detect.py 验证效果并生成可视化结果训练完成后必须分两步验证量化指标验证用val.py计算 mAP确认模型泛化能力视觉效果验证用detect.py生成带 bbox 的图片肉眼判断定位质量第一步命令python val.py \ --data data/foggy.yaml \ --weights runs/train/foggy_exp/weights/best.pt \ --task val \ --save-hybrid # 保存 hybrid labelsGT pred用于画混淆矩阵执行后runs/val/foggy_exp/下会生成confusion_matrix.png、PR_curve.png、F1_curve.png—— 这些不是装饰confusion_matrix.png能直接告诉你公交车bus是否常被误判为轿车car摩托车motorcycle是否大量漏检这是调优的关键依据。第二步命令对验证集 100 张图推理并保存结果python detect.py \ --source datasets/images/val \ --weights runs/train/foggy_exp/weights/best.pt \ --data data/foggy.yaml \ --conf 0.25 \ --iou 0.45 \ --save-txt \ --save-conf \ --project runs/detect \ --name foggy_val_results关键参数--conf 0.25置信度阈值。雾天目标对比度低0.25 比默认 0.25 更激进避免漏检--iou 0.45NMS IoU 阈值。雾中目标易出现重叠 bbox0.45 比默认 0.45 更宽松减少过度抑制--save-txt保存预测结果为.txt格式同 label用于后续评估或集成--save-conf在保存的图片上显示置信度数值调试时一眼看出哪些预测“心里没底”。执行完毕runs/detect/foggy_val_results/下即为 100 张带彩色 bbox 的 JPG 图片——这才是你交付给产品经理的硬通货。3. 模型部署前必做的三项验证混淆矩阵解读、PR 曲线分析、F1 最优阈值定位3.1 从 confusion_matrix.png 读出雾天检测的真实瓶颈打开runs/val/foggy_exp/confusion_matrix.png你会看到一个 5×5 热力图横轴是预测类别纵轴是真实类别。重点看非对角线区域真实\预测personcarbusbicyclemotorcycleperson89212385car21765421518bus753621910bicycle3218451234motorcycle1529641412现象公交车bus被误判为轿车car达 53 次远高于其他错误摩托车motorcycle被误判为自行车bicycle41 次。原因雾中公交车与轿车轮廓相似长方体车窗而摩托车与自行车都呈细长双轮结构特征区分度低。解决不是换模型而是加数据——从验证集中挑出这 53 张“bus→car”误判图人工检查是否标注有歧义如公交车侧面被雾遮挡只剩车头确实像轿车若有则修正标签再用albumentations库对这 53 张图做雾增强添加高斯雾、降低对比度生成 200 张新样本加入训练集重新训 20 epoch。3.2 PR 曲线揭示为什么你的 0.5 置信度阈值在雾天是灾难PR_curve.png的横轴是召回率Recall纵轴是精确率Precision。理想曲线应左上凸起越靠近左上角模型越鲁棒。观察本项目曲线在 Recall0.8 时Precision 仅 0.45 → 意味着为召回 80% 的真实目标要容忍 55% 的误报曲线在 Recall0.6 处达到峰值 Precision0.62 → 此处是 F1-score 最大点。这说明强行用 0.5 置信度阈值会极大拉低 Precision。因为雾天模型输出的 confidence 普遍偏低网络对模糊目标不敢给高分若设--conf 0.5大量真实目标因分数0.5 被过滤Recall 断崖下跌。3.3 定位 F1 最优阈值三行代码自动计算并固化到推理脚本YOLOv5 的val.py输出中包含F1_curve.png但图中只显示趋势。要拿到精确阈值需解析results.csvimport pandas as pd df pd.read_csv(runs/val/foggy_exp/results.csv) # 找到 F1 列最大值对应的行 best_row df.loc[df[metrics/F1-cls].idxmax()] print(fOptimal confidence threshold: {best_row[params/precision]:.3f}) print(fMax F1: {best_row[metrics/F1-cls]:.3f})运行后输出Optimal confidence threshold: 0.321 Max F1: 0.587注意params/precision列名是 YOLOv5 v6.0 的写法旧版可能是P。若报错用df.columns查看实际列名。将此 0.321 写死到你的部署脚本中detect.py的--conf 0.321而非拍脑袋的 0.5。这是雾天场景下精度与召回的黄金分割点。4. 避坑指南雾天 YOLOv5 训练中五个血泪教训每一条都让我重训三次4.1 现象训练 loss 曲线震荡剧烈100 epoch 后 mAP 不升反降原因雾天图像整体亮度低、对比度弱而--augment默认开启的HSV色彩增强调整 hue/saturation/value会进一步扭曲雾的光学特性导致模型学到虚假特征。例如增强后的“雾”可能变成灰白色块而真实雾是青灰色带颗粒感。解决关闭 HSV 增强在train.py中找到augment_hsv()调用注释掉或启动时加--hyp data/hyp.foggy.yaml自定义一个禁用 HSV 的 hyp 文件# data/hyp.foggy.yaml hsv_h: 0.0 # hue gain hsv_s: 0.0 # saturation gain hsv_v: 0.0 # value gain4.2 现象验证时confusion_matrix.png中“person”类别全黑无预测原因数据集里行人person标注框普遍较小雾中人影模糊而--img 640下小目标特征图分辨率不足。YOLOv5s 的 stride32640/3220即最小可检测目标约 20px而雾中行人 bbox 常小于 15px。解决启用--multi-scale多尺度训练让模型在 512~768 范围内随机缩放输入强制学习多尺度特征同时将--img改为--img 768增大特征图尺寸。代价是显存占用30%但 mAP 提升 0.04~0.06。4.3 现象detect.py推理结果中同一辆车出现 3~5 个重叠 bbox原因雾中目标边缘模糊NMS非极大值抑制的--iou默认 0.45 过于宽松无法有效合并相似 bbox。解决不要盲目调低 iou如设 0.3这会导致真正相邻目标如并排两辆轿车被错误合并。正确做法是在detect.py的non_max_suppression()函数中将agnostic_nmsFalse改为True启用类别无关 NMS让不同类别的 bbox 也能参与抑制——因为雾中轿车和公交车轮廓太像它们的 bbox 中心点距离可能比同类更近。4.4 现象runs/detect/下图片 bbox 颜色混乱person 画成绿色car 画成红色原因detect.py绘图时读取data/foggy.yaml中的colors但你修改了names顺序如把motorcycle提到bicycle前却忘了同步更新colors列表顺序。解决写个校验脚本确保len(names) len(colors)且索引一一对应import yaml with open(data/foggy.yaml) as f: data yaml.safe_load(f) assert len(data[names]) len(data[colors]), names and colors length mismatch! for i, name in enumerate(data[names]): print(f{name}: {data[colors][i]})4.5 现象训练到 50 epoch 时 CUDA out of memory但显存监控显示只用了 18G/24G原因--cache缓存机制在 Linux 下会占用大量 page cachenvidia-smi看不到但free -h显示 RAM 被占满系统开始 swapGPU kernel 启动失败。解决训练前清空 page cachesudo sh -c echo 3 /proc/sys/vm/drop_caches或改用--cache ram仅缓存到 RAM而非默认的--cache disk缓存到 SSD但会累积 metadata 占用内存。5. 进阶技巧用 export.py 导出 ONNX 并在 OpenCV DNN 模块中零依赖推理5.1 为什么必须导出 ONNX——摆脱 PyTorch 环境束缚的硬需求你训练好的best.pt只能在 PyTorch 环境运行而实际部署场景往往是边缘设备Jetson Nano没有 pip装不了 torch客户系统只允许 C不接受 Python需要和现有 OpenCV 流水线集成不能额外起 Python 进程。ONNX 是工业界事实标准OpenCV 4.5 的cv2.dnn模块原生支持无需安装 PyTorch一行cv2.dnn.readNetFromONNX()即可加载。5.2 三步导出 ONNX从 best.pt 到 onnx-simplified项目自带export.py但直接运行会报错——因为雾天模型输入需适配--img 640而默认 export 脚本用--img-size 640旧参数名。正确命令python export.py \ --weights runs/train/foggy_exp/weights/best.pt \ --include onnx \ --img-size 640 \ --batch-size 1 \ --device cpu # 避免 GPU 显存不足执行后生成best.onnx但此时还不能直接给 OpenCV 用YOLOv5 的 ONNX 输出是(1, 25200, 55)25200 anchorsOpenCV DNN 要求(1, 55, H, W)格式。需用onnx-simplifier工具优化pip install onnx-simplifier python -m onnxsim best.onnx best_sim.onnx注意onnx-simplifier会折叠 Constant 节点、删除冗余 reshape使模型体积缩小 30%且输出 shape 符合 OpenCV 要求。5.3 OpenCV DNN 推理C 和 Python 双实现附关键参数表Python 版快速验证import cv2 import numpy as np net cv2.dnn.readNetFromONNX(best_sim.onnx) cap cv2.VideoCapture(foggy_test.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break # 预处理resize normalize blobFromImage blob cv2.dnn.blobFromImage( frame, scalefactor1/255.0, size(640, 640), mean(0, 0, 0), swapRBTrue, cropFalse ) net.setInput(blob) outputs net.forward() # shape: (1, 25200, 10) # 后处理筛选 conf0.3NMS boxes, confs, class_ids [], [], [] for detection in outputs[0]: conf detection[4] if conf 0.321: # 用前面定位的最优阈值 scores detection[5:] class_id np.argmax(scores) conf scores[class_id] if conf 0.321: center_x int(detection[0] * frame.shape[1]) center_y int(detection[1] * frame.shape[0]) w int(detection[2] * frame.shape[1]) h int(detection[3] * frame.shape[0]) x int(center_x - w/2) y int(center_y - h/2) boxes.append([x, y, w, h]) confs.append(float(conf)) class_ids.append(class_id) # OpenCV 自带 NMS indices cv2.dnn.NMSBoxes(boxes, confs, 0.321, 0.45) for i in indices: i i[0] x, y, w, h boxes[i] label [person,car,bus,bicycle,motorcycle][class_ids[i]] cv2.rectangle(frame, (x, y), (xw, yh), (0,255,0), 2) cv2.putText(frame, f{label} {confs[i]:.2f}, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 2) cv2.imshow(Foggy Detection, frame) if cv2.waitKey(1) ord(q): break cap.release() cv2.destroyAllWindows()C 版核心逻辑省略头文件和窗口cv::dnn::Net net cv::dnn::readNetFromONNX(best_sim.onnx); cv::VideoCapture cap(foggy_test.mp4); std::vectorstd::string classes {person,car,bus,bicycle,motorcycle}; while (cap.isOpened()) { cv::Mat frame; cap frame; if (frame.empty()) break; cv::Mat blob; cv::dnn::blobFromImage(frame, blob, 1.0/255.0, cv::Size(640,640), cv::Scalar(0,0,0), true, false); net.setInput(blob); std::vectorcv::Mat outputs; net.forward(outputs, net.getUnconnectedOutLayersNames()); // outputs[0] is (1, 25200, 10), process as in Python std::vectorint class_ids; std::vectorfloat confidences; std::vectorcv::Rect boxes; float* data outputs[0].ptrfloat(); for (int i 0; i 25200; i) { float conf data[i*10 4]; if (conf 0.321f) { float* scores data i*10 5; int class_id std::max_element(scores, scores5) - scores; float max_conf scores[class_id]; if (max_conf 0.321f) { float cx data[i*10 0] * frame.cols; float cy data[i*10 1] * frame.rows; float w data[i*10 2] * frame.cols; float h data[i*10 3] * frame.rows; int x static_castint(cx - w/2); int y static_castint(cy - h/2); boxes.push_back(cv::Rect(x, y, static_castint(w), static_castint(h))); confidences.push_back(max_conf); class_ids.push_back(class_id); } } } std::vectorint indices; cv::dnn::NMSBoxes(boxes, confidences, 0.321f, 0.45f, indices); for (int i : indices) { cv::rectangle(frame, boxes[i], cv::Scalar(0,255,0), 2); std::string label classes[class_ids[i]] std::to_string(confidences[i]); cv::putText(frame, label, boxes[i].tl(), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0,255,0), 2); } }参数Python 值C 值说明scalefactor1/255.01.0/255.0归一化系数必须与训练时--hyp中scale一致size(640,640)cv::Size(640,640)输入尺寸必须与export.py --img-size一致swapRBTruetrueBGR→RGBYOLOv5 训练用 RGBOpenCV 读图是 BGRNMS threshold0.450.45fIoU 阈值雾天推荐 0.45非 0.6从那以后我每次导出 ONNX都强制走一遍onnx-simplifiercv2.dnn.readNetFromONNX 单帧推理验证哪怕多花 2 分钟。因为线上服务一旦因 ONNX 兼容性崩掉重启成本远高于本地多测一次。希望帮到你。本文还有配套的精品资源点击获取

看完文章,想为自己的企业也做一次专业网站诊断?

尧图顾问免费为您评估现有网站,并给出建站/改版建议与报价方案。

免费获取方案