☰
基于166张扬尘数据集的YOLO目标检测实战:小样本训练与避坑指南
2026/10/11 17:58:31 网站建设 项目流程

简介:面向建筑工地扬尘监测场景的YOLO目标检测数据集,标注类别为尘土,适用于YOLOv5、YOLOv7、YOLOv8、YOLOv11等主流系列算法,可直接用于训练与验证,适合从事智慧工地、环境监测方向的研究者与算法工程师快速搭建扬尘识别模型。压缩包共503个文件,约26.07MB,包含168张jpg现场图像、166个xml与166个txt标注文件,另附data.yaml数据集配置文件、md说明及pdf文档,训练集与验证集已划分完毕,开箱即可投入训练。目前已有17人学习下载。数据集覆盖多时段工地实拍画面,标注规范统一,配合内置配置文件可省去繁琐的数据整理环节,帮助读者将精力集中于模型调优与扬尘检测效果验证,是开展工地扬尘目标检测实验的实用基础素材。

1. 166 张扬尘图能训出什么:工地尘土检测的真实起点

工地扬尘检测这件事,难的不是模型结构,是数据。你拿 COCO 或者 VOC 直接跑,模型根本不知道“扬尘”长什么样——它见过汽车、行人、交通灯,但没见过灰蒙蒙一片里那团边界模糊的土黄色区域。所以当我看到“166 张、标注类别为尘土”这个数据集时,第一反应不是嫌它小,而是想搞清楚:这 166 张图到底覆盖了哪些工况,标注框画的是“扬尘区域”还是“扬尘源”,以及用 YOLO 系列哪一版能在这种量级上跑出可用的召回率。

这个数据集适合两类人:一是做智慧工地、环保监测方向,需要快速验证扬尘检测可行性的工程师;二是手头有少量标注数据、想跑通 YOLO 全流程但不想在数据采集上耗太久的学生或转行者。166 张确实不多,但扬尘检测有个特点——场景相对固定,摄像头角度变化不大,背景重复度高,所以小样本反而可能比通用数据集微调效果更好。前提是你得把数据增强和验证策略做对,否则过拟合是分分钟的事。

2. 扬尘数据集拆开看:166 张图里藏着什么

2.1 先搞清楚标注格式和类别定义

拿到一个 zip 包,别急着解压就往 YOLO 里塞。第一步是确认标注格式。常见的有三种:VOC 的 XML、COCO 的 JSON、YOLO 的 txt。标题里没写,但“标注类别为尘土”这个描述暗示可能是单类别。我一般会先解压看目录结构:

unzip 建筑工地扬尘目标检测数据集-166张-标注类别为尘土.zip -d dust_dataset find dust_dataset -type f | head -30

如果看到Annotations/和JPEGImages/,那就是 VOC 格式;如果看到labels/和images/,且 labels 里是 txt,那就是 YOLO 格式。单类别的话,txt 里每行应该是0 x_center y_center width height,所有值归一化到 0-1。

提示:如果标注是 VOC XML,需要转成 YOLO txt。转换脚本后面会给。

确认类别数很重要。如果只有“尘土”一类,那模型输出维度就是(5 + 1) = 6,即 4 个坐标 + 1 个置信度 + 1 个类别概率。别小看这个,我见过有人拿 COCO 预训练权重直接训,忘了改nc,结果模型一直在预测 80 类,loss 降不下去。

2.2 166 张图的分布决定你的验证策略

166 张图,如果按 8:2 划分,验证集只有 33 张。这 33 张里如果扬尘目标很少,mAP 波动会非常大。我的做法是:先统计每张图的标注框数量,看看有多少张是“有尘土”的正样本,多少张是负样本(没有尘土)。如果负样本太多,模型会偏向预测背景。

import os import glob label_dir = "dust_dataset/labels" total_boxes = 0 positive_images = 0 negative_images = 0 for txt_file in glob.glob(os.path.join(label_dir, "*.txt")): with open(txt_file, "r") as f: lines = f.readlines() if len(lines) == 0: negative_images += 1 else: positive_images += 1 total_boxes += len(lines) print(f"正样本图: {positive_images}, 负样本图: {negative_images}") print(f"总标注框: {total_boxes}, 平均每张正样本 {total_boxes/positive_images:.1f} 个框")

如果平均每张正样本只有 1-2 个框,说明扬尘区域通常只有一个大框,那 anchor 尺寸要调大。YOLOv5/v8 默认 anchor 是针对 COCO 的,小目标偏多,直接拿来用可能对大框不敏感。我一般会跑一遍 k-means 重新聚类 anchor,或者直接用 YOLOv8 的 anchor-free 头,省去这个麻烦。

2.3 数据增强:小样本的救命稻草还是过拟合的帮凶

166 张图,不做增强基本没戏。但增强不是越多越好。扬尘检测有个特殊性:扬尘的颜色和纹理是重要特征,如果你用 HSV 增强把色调调得太狠,模型可能把黄土和灰土当成两类。我一般会限制 HSV 的 H 通道在 ±10 以内,S 和 V 可以放宽到 ±40。

另外,Mosaic 增强对扬尘检测帮助很大,因为它能把四张图拼在一起,变相增加背景多样性。但 Mosaic 会引入大量边缘伪影,如果扬尘区域被拼接线切断,标注框会变得很奇怪。我的经验是:Mosaic 概率设 0.5 左右,不要开到 1.0。Copy-paste 增强也可以试,但扬尘区域边界模糊,复制粘贴后融合不自然,反而可能引入噪声。

# YOLOv8 训练时的增强参数示例 augment_config = { "hsv_h": 0.010, # 色调增强减半,避免扬尘颜色失真 "hsv_s": 0.400, # 饱和度可以大一点 "hsv_v": 0.400, # 亮度也可以大一点 "mosaic": 0.5, # Mosaic 概率降到 0.5 "mixup": 0.0, # 小样本不建议开 mixup "flipud": 0.0, # 工地摄像头一般不会上下翻转 "fliplr": 0.5, # 左右翻转可以开 }

注意:如果你用的是 YOLOv5,增强参数在data/hyp.scratch.yaml里改;YOLOv8 直接在训练命令里传参。

3. 用 YOLOv8 跑通 166 张扬尘数据:从环境到推理

3.1 环境配置:别在 CUDA 版本上翻车

YOLOv8 的环境配置比 YOLOv5 简单,但 CUDA 版本和 PyTorch 的匹配仍然是翻车重灾区。我一般用 conda 建环境,然后按 PyTorch 官网的命令装。如果你用的是 30 系或 40 系显卡,建议 CUDA 11.8 以上。

conda create -n dust_yolo python=3.10 -y conda activate dust_yolo pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics

装完之后验证一下 GPU 是否可用:

import torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))

如果输出True和你的显卡型号,说明环境没问题。如果输出False,先检查驱动版本,再检查 PyTorch 是不是装成了 CPU 版。我见过有人 pip 装的时候没指定 index-url,结果装成了 CPU 版,训练时 loss 降得极慢,还以为是数据问题。

3.2 数据组织:YOLO 格式的目录结构

YOLOv8 要求的数据目录结构是这样的:

dust_dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── dust.yaml

dust.yaml内容:

path: ./dust_dataset train: images/train val: images/val nc: 1 names: ['dust']

划分训练集和验证集时,我一般用 8:2,但会确保验证集里既有正样本也有负样本。如果 166 张里负样本很少,我会手动挑几张没有扬尘的工地背景图放进验证集,防止模型学会“看到工地就预测扬尘”。

import os import random import shutil image_dir = "dust_dataset/images/all" label_dir = "dust_dataset/labels/all" train_img_dir = "dust_dataset/images/train" val_img_dir = "dust_dataset/images/val" train_lbl_dir = "dust_dataset/labels/train" val_lbl_dir = "dust_dataset/labels/val" os.makedirs(train_img_dir, exist_ok=True) os.makedirs(val_img_dir, exist_ok=True) os.makedirs(train_lbl_dir, exist_ok=True) os.makedirs(val_lbl_dir, exist_ok=True) images = [f for f in os.listdir(image_dir) if f.endswith(('.jpg', '.png'))] random.shuffle(images) split = int(len(images) * 0.8) for i, img in enumerate(images): label = img.rsplit('.', 1)[0] + '.txt' if i < split: shutil.copy(os.path.join(image_dir, img), train_img_dir) shutil.copy(os.path.join(label_dir, label), train_lbl_dir) else: shutil.copy(os.path.join(image_dir, img), val_img_dir) shutil.copy(os.path.join(label_dir, label), val_lbl_dir)

3.3 训练命令与关键参数

YOLOv8 的训练命令很简洁,但参数设不对,166 张图可能训出个“只会预测背景”的模型。

yolo detect train \ data=dust_dataset/dust.yaml \ model=yolov8n.pt \ epochs=200 \ imgsz=640 \ batch=16 \ lr0=0.001 \ lrf=0.01 \ patience=50 \ device=0 \ project=dust_train \ name=exp1

几个关键参数的解释:

  • model=yolov8n.pt:用 nano 版,因为数据量小,大模型容易过拟合。如果你有 3090 或 4090,可以试yolov8s.pt,但 166 张图用 s 版可能反而更差。
  • epochs=200:小样本需要多训几轮,但配合patience=50,如果 50 轮验证集 mAP 不升就早停。
  • lr0=0.001:初始学习率。默认是 0.01,但小样本用 0.001 更稳。如果 loss 震荡,降到 0.0005。
  • imgsz=640:扬尘区域通常比较大,640 够用。如果显存不够,降到 512,但别低于 416,否则大框可能被切掉。

训练过程中重点看val/box_loss和metrics/mAP50。如果box_loss一直不降,检查标注框是不是归一化错了。如果mAP50在 0.3 左右徘徊,可能是增强太狠或者学习率太高。

3.4 推理与验证:别只看 mAP

训完之后,用验证集跑一遍推理,把预测框画出来看。mAP 高不代表模型真的学到了扬尘,可能只是学到了“工地背景”。

yolo detect predict \ model=dust_train/exp1/weights/best.pt \ source=dust_dataset/images/val \ conf=0.25 \ save=True

conf=0.25是置信度阈值。扬尘检测建议从 0.25 开始调,如果误检多就提到 0.4,如果漏检多就降到 0.15。我一般会跑三组阈值,对比看哪个平衡点最好。

提示:如果模型在验证集上 mAP 很高,但实际推理时全是误检,大概率是验证集和训练集背景太相似。这时候需要找一些完全没见过的工地场景图来测。

4. 扬尘检测的避坑清单:166 张数据踩过的 5 个坑

4.1 坑一:标注框画的是“扬尘源”而不是“扬尘区域”

现象:模型训练 loss 正常下降,但推理时框总是落在车轮、土堆上,而不是扬尘弥漫的区域。

原因:标注时把“产生扬尘的地方”当成了目标,比如车轮碾过的地面、挖掘机铲斗。但扬尘检测真正要框的是空气中悬浮的尘土区域。

解决:重新检查标注规范。如果标注框明显偏小且集中在物体上,需要重新标注或者用分割模型先提取扬尘区域再转检测框。166 张重标工作量不小,但比训一个废模型强。

4.2 坑二:负样本太少导致模型“见工地就报尘”

现象:验证集 mAP 不错,但拿几张没有扬尘的工地照片去测,模型仍然输出高置信度的“尘土”框。

原因:训练集里几乎没有负样本,模型没学会“没有扬尘”是什么样。

解决:从工地监控里截取 20-30 张没有明显扬尘的图,作为负样本加入训练集。YOLO 对负样本的处理是:如果某张图没有对应 txt 文件,或者 txt 为空,就只计算背景 loss。这能显著降低误检。

4.3 坑三:Mosaic 增强把扬尘区域切碎了

现象:训练时 loss 正常,但推理时模型对大块扬尘区域检测不全,总是只框出一部分。

原因:Mosaic 把四张图拼在一起,扬尘区域被拼接线切断,模型学到的都是“半个扬尘”。

解决:把 Mosaic 概率从 1.0 降到 0.3-0.5,或者用mosaic=0先训一轮 baseline,看看是不是增强的问题。另外可以开close_mosaic,在最后几轮关掉 Mosaic,让模型在完整图上微调。

4.4 坑四:学习率太高导致 loss 爆炸

现象:训练前几轮 loss 直接变成 NaN,或者从 0.5 跳到 10 以上。

原因:YOLOv8 默认 lr0=0.01,对 166 张图来说太大了。小样本梯度噪声大,高学习率容易震荡。

解决:把lr0降到 0.001 甚至 0.0005,同时开warmup_epochs=3,让学习率从低到高预热。如果已经 NaN 了,检查数据里有没有标注框宽高为 0 的情况,那会导致除零错误。

4.5 坑五:验证集 mAP 虚高,实际部署翻车

现象:验证集 mAP50 到了 0.85,但部署到工地摄像头后,白天还行,傍晚和夜间几乎全漏。

原因:166 张图可能集中在白天某个时段,模型没学过傍晚的低对比度场景。

解决:如果数据里没有夜间或傍晚的图,别指望模型能泛化。要么补数据,要么在推理前做图像增强(比如 CLAHE 直方图均衡化),把低对比度图像拉回来。但增强参数需要调,调不好会引入噪声。

5. 小样本扬尘检测的进阶技巧:从 166 张到可用模型

5.1 用预训练权重做特征冻结再解冻

166 张图直接训整个 YOLOv8n,前面几层学到的通用特征会被破坏。我的做法是:先冻结 backbone,只训 head 20 轮,然后解冻全部再训 100 轮。YOLOv8 里可以通过freeze参数控制。

# 第一阶段:冻结 backbone yolo detect train \ data=dust_dataset/dust.yaml \ model=yolov8n.pt \ epochs=20 \ freeze=10 \ lr0=0.001 \ project=dust_train \ name=stage1 # 第二阶段:解冻全部 yolo detect train \ data=dust_dataset/dust.yaml \ model=dust_train/stage1/weights/best.pt \ epochs=150 \ lr0=0.0005 \ project=dust_train \ name=stage2

freeze=10表示冻结前 10 层。YOLOv8n 总共约 130 层,冻结前 10 层相当于锁住浅层特征提取。两阶段训练在小样本上通常比端到端训提升 3-5 个点 mAP。

5.2 用 TTA 和模型集成榨干最后一点精度

如果你已经训了好几个模型(不同 seed、不同增强参数),推理时可以用 TTA(Test Time Augmentation)或者 WBF(Weighted Boxes Fusion)集成。YOLOv8 自带 TTA:

yolo detect predict \ model=dust_train/stage2/weights/best.pt \ source=dust_dataset/images/val \ augment=True \ conf=0.25

augment=True会对每张图做多尺度+翻转推理,然后合并结果。速度会慢 3-5 倍,但 mAP 通常能涨 1-2 个点。如果部署对速度有要求,TTA 不适合,但离线分析可以用。

5.3 一个具体技巧:用 SAHI 切图检测小扬尘

扬尘区域有时候在图像里占比很小,640 分辨率下可能只有 20x20 像素。YOLO 对小于 32x32 的目标召回率会明显下降。SAHI(Slicing Aided Hyper Inference)的思路是把大图切成小块,分别检测再合并。

from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction detection_model = AutoDetectionModel.from_pretrained( model_type='yolov8', model_path='dust_train/stage2/weights/best.pt', confidence_threshold=0.25, device='cuda:0' ) result = get_sliced_prediction( "test_image.jpg", detection_model, slice_height=320, slice_width=320, overlap_height_ratio=0.2, overlap_width_ratio=0.2 )

slice_height和slice_width设 320,意味着把原图切成 320x320 的块,每块之间有 20% 重叠。这样小扬尘在切块里会变大,更容易被检测到。代价是推理速度变慢,但召回率能提升 10-15 个点。我一般只在离线分析或者对召回率要求极高的场景用。

5.4 验证方法:别只看 mAP,看 PR 曲线

mAP 是个综合指标,但扬尘检测有时候更关心“在高召回率下误检有多少”。我一般会导出 PR 曲线,看 recall=0.8 时 precision 是多少。如果 precision 低于 0.5,说明误检太多,需要调置信度阈值或者补负样本。

from ultralytics import YOLO import matplotlib.pyplot as plt model = YOLO('dust_train/stage2/weights/best.pt') metrics = model.val(data='dust_dataset/dust.yaml', split='val') # 打印各类别的 PR 曲线数据 print(metrics.box.p) # precision print(metrics.box.r) # recall print(metrics.box.ap50) # AP50

如果ap50只有 0.4 左右,别急着换模型,先检查标注质量。166 张图里如果有 10 张标注框画错了,对 mAP 的影响可能超过 5 个点。我一般会随机抽 20 张验证集的预测结果,人工看一遍,把误检和漏检的原因归类,再决定是补数据、调增强还是换模型。

最后说个血泪经验:小样本数据集,别一上来就追求 SOTA 模型。YOLOv8n 在 166 张图上训好了,比 YOLOv8x 训崩了强得多。先把 baseline 跑通,mAP50 到 0.6 以上,再考虑换模型或者加数据。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询