简介本资源面向自动驾驶感知、恶劣天气目标检测方向的研究者与算法工程师提供大雾场景下的道路目标图像检测数据集可直接用于模型训练与验证省去自行采集与标注的成本。压缩包内共约2000个文件以1999个VOC格式xml标注文件和1个Python脚本为主整体约430MB标注覆盖car、person、motor、bus、truck、bike、rider共7类目标按文件夹组织无需额外处理即可接入检测流程。目前已有506人学习下载适合作为雾天鲁棒性实验、数据增强对比或YOLO系列实战训练的数据来源。配套脚本便于快速查看与校验标注xml文件可直接转换为YOLO等框架所需格式帮助读者把精力集中在模型改进与效果分析上。1. 大雾里的目标检测为什么你的模型一上路就“瞎”了城市道路监控和自动驾驶感知团队都遇到过同一个尴尬晴天 mAP 0.85 的模型切到大雾天直接掉到 0.4 以下漏检的往往是行人和非机动车——恰恰是最不能漏的那一类。这不是模型结构不行而是训练集里根本没有雾。目标检测数据集在恶劣天气下的稀缺是行业里公开的痛点COCO、VOC2007/2012 这些经典数据集采集时都刻意避开了雨雾天导致模型学到的是“清晰世界”的先验。大雾恶劣天气下的道路上目标图像检测数据正是为补这个缺口存在的——它用 VOC 标注格式组织雾天道路场景的图像与标签让 YOLO 系列、SSD、Faster R-CNN 这类检测器能在低能见度条件下重新校准。这篇文章面向三类人想给现有检测模型做雾天微调的算法工程师、需要 VOC 格式数据快速跑通 baseline 的学生、以及评估“这个方向值不值得投入”的技术负责人。我会把 VOC 结构怎么读、雾天数据怎么清洗、YOLOv8 怎么接、微调时哪些参数是命门一条条拆开讲清楚。2. VOC 标注格式拆解雾天数据的目录结构与标签语义2.1 VOC 的三层结构以及雾天数据为什么必须按它组织VOC 格式之所以在 2026 年还没被淘汰是因为它的结构足够“笨”——笨到任何框架都能一眼解析。一个标准的 VOC 数据集根目录下有三个关键文件夹JPEGImages放原图Annotations放同名 XML 标签ImageSets/Main放划分文件train.txt、val.txt、test.txt每行一个不含扩展名的文件名。雾天道路数据沿用这套结构有个实际好处你不需要为“雾”单独设计一套 IO 逻辑雾只是图像内容的一种属性标注体系完全复用。但雾天数据在 VOC 框架下有两个特殊之处直接决定后面训练能不能收敛。第一object里的difficult字段要慎用。雾天远处目标边界模糊标注员容易把“看不清”误标成difficult1而多数训练脚本默认跳过 difficult 目标结果就是远处小目标被系统性丢弃。我的做法是只有目标被遮挡超过 70% 才标 difficult单纯因为雾导致对比度低的一律标 0。第二size里的width/height必须和实际图像严格一致雾天图像常经过增强处理去雾、直方图均衡如果增强后没同步更新 XML坐标框会整体偏移。一个典型的雾天 XML 长这样annotation folderJPEGImages/folder filenamefog_road_0001.jpg/filename size width1920/width height1080/height depth3/depth /size object namecar/name difficult0/difficult bndbox xmin412/xmin ymin603/ymin xmax587/xmax ymax712/ymax /bndbox /object object nameperson/name difficult0/difficult bndbox xmin890/xmin ymin540/ymin xmax934/xmax ymax688/ymax /bndbox /object /annotation坐标是左上角xmin/ymin到右下角xmax/ymax的绝对像素值不是归一化值——这一点和 YOLO 的 txt 格式正好相反转换时最容易翻车。类别名name建议统一小写英文中文类别名在部分训练框架里会触发编码问题。2.2 用脚本体检三行代码查出标签里的“脏数据”拿到一份 VOC 数据集别急着转格式训练先做一次体检。雾天数据最常见的三类脏数据是坐标越界xmax 超过图像宽度、零面积框xmaxxmin、类别名拼写不一致Car和car被当成两类。下面这段脚本一次性全查出来import os import xml.etree.ElementTree as ET from collections import Counter ANN_DIR Annotations IMG_DIR JPEGImages classes Counter() problems [] for xml_file in os.listdir(ANN_DIR): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(ANN_DIR, xml_file)) root tree.getroot() w int(root.find(size/width).text) h int(root.find(size/height).text) for obj in root.findall(object): name obj.find(name).text classes[name] 1 box obj.find(bndbox) xmin int(box.find(xmin).text) ymin int(box.find(ymin).text) xmax int(box.find(xmax).text) ymax int(box.find(ymax).text) # 越界检查 if xmin 0 or ymin 0 or xmax w or ymax h: problems.append((xml_file, name, out_of_bound)) # 零面积检查 if xmax xmin or ymax ymin: problems.append((xml_file, name, zero_area)) print(类别分布:, classes) print(问题样本数:, len(problems)) for p in problems[:10]: print(p)逻辑说明遍历每个 XML读取图像尺寸和每个目标框做两类校验。classes计数器能直接暴露类别不均衡——雾天数据里car通常占 70% 以上person和bicycle可能不到 10%这个比例直接决定你后面要不要做重采样。参数上out_of_bound的框要手动修或丢弃zero_area的框必须删否则转 YOLO 格式时会生成非法标签导致训练报错。跑完这一步你对自己手里这份雾天数据的质量就有了底。3. VOC 转 YOLO转换脚本、划分策略与雾天样本的坑3.1 转换脚本从 XML 到归一化 txt 的完整实现YOLOv8 训练需要的是每张图对应一个 txt每行class_id cx cy w h全部归一化到 0~1。转换本身不难难的是类别映射要固定、坐标要防越界。下面是我常用的转换脚本import os import xml.etree.ElementTree as ET CLASSES [car, person, bicycle, motorbike, bus, truck] ANN_DIR Annotations IMG_DIR JPEGImages OUT_DIR labels os.makedirs(OUT_DIR, exist_okTrue) for xml_file in os.listdir(ANN_DIR): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(ANN_DIR, xml_file)) root tree.getroot() w int(root.find(size/width).text) h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text.lower() if name not in CLASSES: continue # 未登记类别直接跳过避免 class_id 错位 cls_id CLASSES.index(name) box obj.find(bndbox) xmin max(0, int(box.find(xmin).text)) ymin max(0, int(box.find(ymin).text)) xmax min(w, int(box.find(xmax).text)) ymax min(h, int(box.find(ymax).text)) cx (xmin xmax) / 2.0 / w cy (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) out_name xml_file.replace(.xml, .txt) with open(os.path.join(OUT_DIR, out_name), w) as f: f.write(\n.join(lines))关键点有三个。CLASSES列表的顺序就是最终的 class_id一旦定下就不能改否则模型学到的类别全乱。max(0, ...)和min(w, ...)是防越界的后悔药宁可裁掉一点边缘也不要让归一化值超过 1。name.lower()统一小写解决Car/car分裂问题。转换完记得抽查几张用cat labels/xxx.txt看数值是否都在 0~1 之间再拿一张图用 OpenCV 把框画出来肉眼比对。3.2 划分策略雾天数据不能随机切分常规做法是随机 8:1:1 切 train/val/test但雾天数据有个陷阱同一段路、同一个摄像头连续采集的帧高度相似随机切分会让几乎相同的图同时出现在训练集和验证集验证指标虚高实际部署一塌糊涂。正确做法是按“采集场景”或“时间段”切分——比如按视频片段 ID 分组整段进训练或整段进验证。import os import random random.seed(42) all_files [f.replace(.jpg, ) for f in os.listdir(JPEGImages) if f.endswith(.jpg)] # 假设文件名前缀是场景ID如 scene01_0001 scenes {} for f in all_files: scene_id f.split(_)[0] scenes.setdefault(scene_id, []).append(f) scene_ids list(scenes.keys()) random.shuffle(scene_ids) n len(scene_ids) train_scenes scene_ids[:int(n * 0.8)] val_scenes scene_ids[int(n * 0.8):int(n * 0.9)] test_scenes scene_ids[int(n * 0.9):] for split, sids in [(train, train_scenes), (val, val_scenes), (test, test_scenes)]: with open(fImageSets/Main/{split}.txt, w) as f: for sid in sids: f.write(\n.join(scenes[sid]) \n)按场景分组后验证集才是真正“没见过”的雾天场景指标才有参考价值。如果你的数据文件名没有场景前缀退而求其次按采集日期或连续帧号分块切分原则是让验证集和训练集在内容上尽量不重叠。4. 用 YOLOv8 跑通雾天微调参数、增强与显存权衡4.1 数据配置文件与最小训练命令YOLOv8 的数据配置是一个 yaml指向图像和标签目录path: /data/fog_road train: ImageSets/Main/train.txt val: ImageSets/Main/val.txt nc: 6 names: [car, person, bicycle, motorbike, bus, truck]注意train/val指向的是 txt 文件txt 里每行是相对path的图像路径不含扩展名YOLO 会自动去找同名.txt标签。最小训练命令yolo detect train \ datafog_road.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.001 \ patience20 \ device0参数逐个说modelyolov8s.pt是从 COCO 预训练权重起步雾天数据量通常不大几千张量级从头训必然过拟合微调是唯一理性选择。imgsz640是速度和精度的平衡点雾天小目标多如果你显存够上 960 对小目标召回有明显帮助。lr00.001比默认的 0.01 低一个量级因为微调要保护预训练特征学习率太大会把 COCO 学到的东西冲掉。patience20是早停雾天数据容易在 30 轮左右就 plateau早停能省时间。4.2 雾天专属增强别把雾“增强”没了YOLO 默认增强里有 HSV 抖动、随机翻转、mosaic。雾天数据要特别小心两点。第一HSV 的 V亮度抖动幅度要调小因为雾天图像本身对比度就低大幅亮度抖动会让目标彻底淹没。第二mosaic 四图拼接在雾天场景下会制造出“半张清晰半张有雾”的诡异样本模型可能学到错误的雾先验。我的配置是保留 mosaic 但降低概率同时加一点轻微的对比度扰动模拟不同雾浓度# 在训练命令里追加增强参数 hsv_v0.2 # 默认 0.4调小 mosaic0.5 # 默认 1.0减半 degrees0.0 # 道路场景不做旋转 translate0.1 scale0.3如果你有去雾算法如暗通道先验、AOD-Net可以离线生成一份“去雾版”图像作为额外训练样本让模型同时见到雾图和去雾图相当于一种数据层面的域增强。但注意去雾后的图要重新标注或确认原标注仍对齐否则坐标全错。4.3 显存不够时的三个降级方案雾天数据分辨率往往偏高1920×1080 常见直接 640 训练显存吃紧时按优先级降级一是batch从 16 降到 8 或 4配合accumulate梯度累积模拟大 batch二是imgsz降到 512但小目标召回会掉慎用三是换更小的模型yolov8n.pt精度损失约 3~5 个点但速度翻倍。三者不要同时用否则精度崩了你都不知道是哪个参数导致的。5. 避坑与排查雾天检测微调最容易翻车的 5 个点现象一训练 loss 正常下降但验证 mAP 始终在 0.3 以下。原因大概率是标签坐标系错位——VOC 是绝对像素YOLO 是归一化如果转换时忘了除以宽高框会全部挤在图像左上角。解决随机抽 5 张图用 OpenCV 把 YOLO 标签画出来和原图比对框位置不对就是转换 bug。现象二模型对近处目标检测正常远处小目标全漏。原因是雾天远处目标在 640 分辨率下只剩十几个像素加上雾的散射特征几乎消失。解决提高imgsz到 960 或 1280或在数据层面把远处目标区域裁剪出来做超分增强。另一个常被忽略的点是 anchor——YOLOv8 是 anchor-free但小目标仍依赖高分辨率特征图确保P3层stride 8没有被过度下采样。现象三验证集 mAP 很高实际视频推理时框闪烁严重。这是随机切分导致的“相似帧泄漏”训练集和验证集有近乎重复的帧。解决回到 3.2 节按场景分组重新划分验证集必须来自不同路段或不同时间段。现象四训练到一半 loss 突然变 NaN。雾天数据里如果有零面积框或坐标越界归一化后可能出现w0或负值导致除零。解决跑 2.2 节的体检脚本把所有zero_area和out_of_bound的框清理干净再训练。现象五类别不均衡导致 person 类几乎检不出。雾天数据 car 占 70%person 可能只有 5%模型倾向于全预测 car。解决在 yaml 里给稀有类别加权或用copy_paste增强把 person 实例复制到更多图上。YOLOv8 支持通过cls损失权重调整但更直接的办法是对 person 类样本做 3~5 倍过采样。6. 验证与进阶怎么确认你的雾天模型真的能用训练完拿到best.pt别只看 mAP 数字。我一般做三层验证。第一层按雾浓度分桶评估把验证集按图像对比度或能见度分成“薄雾/中雾/浓雾”三档分别算 mAP。如果浓雾档 mAP 只有薄雾的一半说明模型对浓雾泛化不足需要补浓雾样本。第二层可视化 FP/FN用yolo detect val导出混淆矩阵和 PR 曲线重点看 person 和 bicycle 的召回这两类漏检的代价最高。第三层拿一段真实雾天行车视频跑推理观察框的时序稳定性——单帧 mAP 再高框在帧间跳来跳去也没法用。进阶技巧上如果你要做多模态比如可见光红外VOC 格式可以扩展在Annotations同级加一个Infrared文件夹放红外图XML 里加modality字段标记。训练时用双分支 backbone 分别提特征再融合雾天可见光退化严重时红外能兜底。这条路成本高但天花板也高适合有红外采集条件的团队。最后说个我自己的习惯每次微调前先把预训练模型在雾天验证集上跑一遍 baseline记下数字。微调后如果提升不到 10 个点先怀疑数据而不是模型——十有八九是标签或划分出了问题。这个习惯帮我省了无数次瞎调参的时间。希望帮到你。本文还有配套的精品资源点击获取