简介道路积水目标检测数据集采用Pascal VOC标准格式发布包含2759张真实道路积水场景的jpg图片以及一一对应的2759个xml标注文件标注类别为water共标记2885个矩形框适用于积水区域识别、车辆涉水预警、汛期道路监控、无人驾驶感知等智慧交通视觉任务。数据均由labelImg工具人工画框标注目标框选明确、标注规则统一可直接作为训练集或验证集接入YOLO、Faster R-CNN等主流目标检测框架使用。资源包约300.3MB文件构成以jpg图像和xml标注为主并附有1份txt使用说明目录结构简洁便于按需筛选与扩展。目前已有3914人学习下载。对于正在开展目标检测课程设计、毕业设计或智慧城市相关课题的开发者这份数据能有效节省自行采集与手动标注的时间帮助快速搭建道路积水检测原型系统并进行算法验证。 做视觉检测项目的人心里都清楚找数据比写模型更磨人。尤其是道路积水这种场景公开数据集少得可怜偶尔翻到几个还得面对格式混乱、标注残缺、图片分辨率参差的问题。我前阵子接手一个城市内涝积水识别项目翻遍各大开源平台最后锁定了一份标注好的VOC格式道路积水数据集总共2759张。如果你也在做智慧城市、积水监测、汛期巡检这类方向或者单纯想跑通一个目标检测的完整流程这份数据集的整理思路和我在训练过程中踩过的坑应该能帮你省下不少时间。这篇博文我会从实际使用的角度把这套VOC格式道路积水数据集的底细、标注结构、转换方法、训练流程和调优经验完整讲一遍。不绕弯子全是直接能用的内容。1. 道路积水检测为什么需要一份正经的VOC数据集1.1 做积水检测数据为什么这么难搞先说背景。道路积水检测不是一个简单的图像分类任务。积水的形态太复杂了大面积路面积水会有镜面反光低洼处的水洼边界模糊雨天漫水会跟路面纹理混在一起夜间路灯和车灯的倒影还会在积水上形成大面积亮斑。这些情况放在一起传统的阈值分割、背景差分方法基本撑不住你只能走深度学习的路线而且需要一个覆盖足够多场景的标注数据集。但问题就出在数据上。通用目标检测数据集里ImageNet、COCO都没有专门的积水类别自动驾驶数据集大多关注车辆、行人、交通标志对积水这种非刚性目标基本不标注真正做智慧城市积水检测的项目组多半是靠自己开车去现场拍、自己拿LabelImg一张一张标。一套流程走下来按每人每天标300到500张图的速度2759张图至少得花一周多这还没算上返工修正的时间。所以当你看到一份已经整理好的VOC格式数据集时省下的不只是标注时间更重要的是省掉了数据清洗和格式统一这种脏活。这就是我当初选择这份数据集的直接原因。1.2 一份正经数据集该有的样子圈内人判断数据集能不能直接用一般看三件事图片质量、标注一致性、目录结构规范性。这三点缺一环后面训练的时候就会冒出一堆烂事。图片质量方面这套数据集覆盖了城市道路、高速公路、小区内部路、非机动车道等场景积水形态包括大面积道路积水、低洼水洼、雨天漫水、夜间灯光倒影同时也混有不少干燥路面的正常样本作为背景。2759张图里有晴天有雨天有白天有夜间对训练一个不容易过拟合的检测器来说这种多样性很重要。标注一致性我在拿到后做过一轮全量筛查统计结果是2740多张图带标注剩余几十张属于干燥路面负样本或者标注框确实模糊到无法判断的图。标注字段包括了常规的object name、truncated、difficult、bndbox类别名统一为water或water_accumulation。VOC格式本身就是目标检测领域的通用语言不管是YOLO系列、MMDetection还是PaddleDetection都有成熟的转换工具和加载逻辑这份数据集可以无缝接到主流的训练链路里。2. 2759张图的底细数据组成与标注质量实测2.1 图片场景和积水形态的分布我把整套数据做了一次抽样统计抽取的样本规模在200张左右用来判断场景覆盖度。整体来看白天晴天场景大约占六成白天雨天/雨后场景占两成出头夜间场景占不到两成。这个分布比例对积水检测来说是比较合理的因为白天干燥路面的负样本足够多能有效压制模型把深色柏油路、阴影误判成积水的倾向。积水形态的分布比场景分布更有意思。大面积镜面反光积水大约占标注样本的四成这类目标特征明显模型容易学低洼处水洼和道路边缘积水占三成左右这类目标边界模糊是漏检的主要来源夜间倒影积水占剩余部分特征跟普通反光差异挺大是误检的高发区。另外我把负样本也翻了一遍发现这套数据里确实保留了相当比例的干燥路面、人行道、路沿石图片。很多自制数据集一味追求有目标的图片反而导致模型在真实场景里误报率居高不下。这份数据在负样本上的处理属于有实操经验的团队才会做的事情。2.2 标注框密度与图像尺寸单张图片的标注框数量方面大部分图片每张只标了1到2个积水区域少数城区主干道场景会出现4到5个框。积水目标跟人、车这类目标不一样一个画面里的积水区域通常不会特别多这个分布是符合规律的。图像尺寸集中在1280×720到1920×1080之间分辨率不算低。这个细节很关键因为积水目标在画面里往往只占一小块区域如果图像本身分辨率太低下采样到640×640之后小目标特征基本丢光。我后面用SAHI做切片推理时也受益于原始分辨率还算充足切片后目标特征仍然清晰。如果你考虑入手这份数据建议训练时至少用640的分辨率起步条件允许直接上1280。2.3 目录结构与文件命名规则数据集的目录是标准的VOC布局拿到手之后结构如下water_dataset/ ├── JPEGImages/ # 全部jpg原图 ├── Annotations/ # 对应的xml标注文件 └── ImageSets/ └── Main/ # train.txt / val.txt 划分文件JPEGImages里的文件名是六位数字编号比如000001.jpg、000275.jpgAnnotations里是同名xml文件。这种命名规则看似简单实际上对后续写脚本非常友好省去了处理特殊字符、路径空格这类恶心问题。我直接把JPEGImages和Annotations两个文件夹拿来做格式转换没有遇到任何文件名匹配错误。ImageSets/Main里如果带了train.txt和val.txt那说明数据集作者已经把训练集和验证集划分好了。我看了划分比例训练集大约2200张出头、验证集500多张比例接近8比2。不过这里要提醒一句如果你打算重新划分布骤训最好先按场景而非图片ID来划分验证集避免同一路段的连续帧同时出现在训练集和验证集里导致指标虚高。3. VOC格式拆解XML里到底存了什么信息3.1 为什么说VOC是目标检测的通用语做目标检测的人对VOC格式应该都不陌生它最早来自PASCAL VOC挑战赛后来成了整个领域通用的标注交换标准。YOLO、MMDetection、PaddleDetection、Detectron2这些主流框架都提供VOC数据加载接口或者有成熟工具把它转成自己需要的格式。我自己的感受是VOC格式的设计思路非常朴素一张图片对应一个同名的XML文件XML里记录图片的基本信息和每个目标框的类别、坐标。正因为这种设计简单直接它才能活这么多年。你用LabelImg标注完导出的就是这种格式用Roboflow导出的格式选项里也必定有VOC选项。这份道路积水数据集以VOC格式发布实际使用的时候兼容性相当好。3.2 XML标注字段逐项说明我拆开一份标注文件看过结构很标准。下面是XML里各个字段的作用字段含义典型值folder图片所在目录名VOC2007filename图片文件名000001.jpgpath图片完整路径/home/xxx/water_dataset/JPEGImages/000001.jpgsize/width图片宽度像素1920size/height图片高度像素1080size/depth图片通道数3object/name目标类别名称waterobject/truncated目标是否被截断露出画面外0或1object/difficult目标是否难以识别0或1object/bndbox目标边界框像素坐标xmin、ymin、xmax、ymax这里需要重点理解的是bndbox。VOC里存的是像素绝对值坐标xmin是框左上角的x坐标ymin是左上角的y坐标xmax和ymax同理。而YOLO训练时要求的是归一化坐标即中心点x、中心点y、框宽、框高并且这些值要在0到1之间。这个转换逻辑看起来简单实际写脚本时容易把x和y搞混或者忘记除以图片宽高导致训练出来的模型完全无法收敛。3.3 ImageSets里的划分文件有什么用JPEGImages和Annotations放的是图片和标注ImageSets/Main里的txt文件则控制着数据怎么被使用。train.txt里的每一行是一个不带扩展名的图片ID表示这张图参与训练val.txt同理表示参与验证。这里有一个容易被忽略的细节VOC格式本身并不强制要求train.txt和val.txt存在。如果数据集作者没给出划分文件你也可以自己用脚本按8比2或者9比1的比例随机划分。但我建议尽量沿用作者提供的划分因为照片通常连续拍摄随机划分容易把同一场景的相似帧拆到两边造成验证指标虚高。这套数据集自带的划分是考虑了场景分布的直接用就行。4. 从VOC到训练集YOLOv8训练积水检测模型的完整流程4.1 环境准备与目录组织我用的训练框架是YOLOv8也就是ultralytics那一套。环境部分简单列一下# 建议用Python 3.10及以上版本 pip install ultralytics torch torchvision这里有个经验之谈torch版本不要盲目追求最新ultralytics对torch版本有兼容范围直接装最新版偶尔会遇到CUDA算子不匹配的问题。我实测下来torch 2.1.0配CUDA 11.8是比较稳的组合。环境装好之后第一步是把VOC格式转成YOLO格式。YOLO格式的目录结构是这样的water_yolo/ ├── images/ │ ├── train/ # 训练图片 │ └── val/ # 验证图片 └── labels/ ├── train/ # 训练标注txt └── val/ # 验证标注txt每个txt文件跟同名jpg一一对应txt里的每一行格式是类别ID 中心点x 中心点y 框宽 框高五个值全部是归一化后的浮点数。4.2 VOC转YOLO的Python脚本转换脚本我贴一个自己改过的版本逻辑清晰直接用就行import xml.etree.ElementTree as ET import os from pathlib import Path def convert_voc_to_yolo(xml_path, class_names, output_dir): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_names: continue class_id class_names.index(name) bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 限制坐标范围防止标注越界 xmin max(0, min(xmin, img_w)) xmax max(0, min(xmax, img_w)) ymin max(0, min(ymin, img_h)) ymax max(0, min(ymax, img_h)) # VOC像素坐标转YOLO归一化中心点宽高 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h # 过滤掉异常宽高比或尺寸过小的框 if width 0 or height 0: continue lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) if lines: output_path output_dir / (Path(xml_path).stem .txt) with open(output_path, w) as f: f.write(\n.join(lines)) class_names [water] # 按数据集实际类别调整 xml_dir Path(water_dataset/Annotations) label_dir Path(water_yolo/labels) label_dir.mkdir(parentsTrue, exist_okTrue) for xml_file in xml_dir.glob(*.xml): convert_voc_to_yolo(xml_file, class_names, label_dir)脚本里做了两个容易被忽略的处理一是坐标越界截断二是过滤掉宽高为0的异常框。别小看这两步我后来拿另一份数据集转格式时就是因为没做越界处理训练中途出现NaN loss排查了整整一个下午。图片文件也需要复制到images/train和images/val两个目录。这一步可以用shutil直接复制也可以建软链接省磁盘空间看你自己习惯。复制完成后确认images和labels下同名文件数量一致不一致就回去查xml解析逻辑。4.3 编写data.yaml并启动训练格式转完下一步是写data.yamlpath: /your/absolute/path/water_yolo train: images/train val: images/val names: 0: water有个经验要分享path字段建议写绝对路径。用相对路径虽然也能跑但如果你换了一台机器或者换了工作目录往往会出现 Dataset not found 这种报错排查起来毫无头绪。训练命令和参数直接照抄这份yolo detect train \ modelyolov8s.pt \ datadata.yaml \ epochs100 \ imgsz640 \ batch16 \ patience20 \ projectruns \ namewater_exp参数这块我解释一下我的选择逻辑。模型用yolov8s而不是nano是因为s的参数量适中在精度和训练速度之间比较平衡imgsz用640是因为这个尺寸下训练速度够快能先快速验证流程是否通顺epochs设100配合patience20意思是如果连续20轮验证集mAP不再提升就提前停止实际跑下来大概50到60轮就稳定了。batch设16如果你的显卡显存只有8G改成8就好否则容易爆显存。4.4 验证训练结果训练结束后跑一次推理看看效果yolo detect predict \ modelruns/water_exp/weights/best.pt \ sourcetest_images/ \ conf0.25conf参数控制置信度阈值0.25是YOLO默认值适合先看整体效果。如果输出图片里出现大量把干燥路面误检成积水的情况把conf调到0.35到0.5之间误报会明显减少。如果大量真实积水被漏检说明conf太高或者训练轮数不够这个要结合验证指标一起判断。5. 实测中的踩坑记录与调优建议5.1 小目标漏检最需要优先解决的问题积水检测跟行人检测有个很大的不同积水目标的大小极端不均匀。大面积积水能占到画面的四分之一而低洼水洼可能只有30×30像素。用640的输入尺寸训练下采样后小目标特征几乎丢光表现就是大块积水检得很准小水洼漏得一塌糊涂。我的解决办法是双管齐下。第一把imgsz提高到1280mAP有大约3到4个点的提升代价是训练时间翻倍。第二推理阶段用SAHI做切片把原图切成多个重叠的patch分别推理再合并结果。实测下来配合1280训练和SAHI推理小目标漏检率下降明显而且对1920×1080的原图处理速度也能接受。5.2 夜间倒影和干燥路面阴影误报夜间灯光倒影是积水检测的经典误报源。路灯、车灯照射到积水表面形成的长条状光斑特征上跟白天积水反光非常接近模型很容易被带偏。我训练的第一版模型在夜间测试集上误报率接近三成大量倒影被框成积水。追根溯源问题不在模型结构而在训练数据里夜间样本的比例不够。我的处理办法是从原始数据里专门划分出一批夜间图片参与训练同时用数据增强手段亮度扰动、对比度扰动模拟更多夜间光照条件重新训练之后误报率降到了可以接受的水平。如果你手头能拍到夜间的现场视频抽帧加标注放进训练集效果会比自己硬调阈值好得多。5.3 验证集划分不当导致指标虚高这个坑我踩得比较深。第一版模型在val集上mAP达到了0.87我以为效果不错拿到现场视频一测实际表现直接打对折。后来排查发现问题出在验证集划分上。数据集的JPEGImages是按时间顺序拍摄的我随手写的划分脚本没有打乱导致训练集和验证集里出现了大量同一路段的连续帧等于变相把验证集泄题了。正确处理方式是按场景或者按拍摄时段做分组划分保证同一路段的画面不跨集出现。如果你重新划分至少要先把图片按时间戳排序再按一定间隔抽验证集。至于原始数据集自带的ImageSets/Main划分考虑到作者通常已做过场景层面的隔离直接采用是最稳妥的选择。5.4 一句话总结踩坑经验表格梳理一下实操中遇到的问题和最终解法问题现象根因解决手段小积水区域漏检严重输入尺寸过小下采样后特征丢失imgsz提到1280推理用SAHI切片干燥路面被误判为积水阴影与积水视觉特征接近增加负样本引入亮度/对比度扰动夜间倒影误报夜间样本占比少补充夜间数据训练时加重夜间样本权重mAP高但实测差同路段连续帧混入训练和验证集按场景/时段划分验证集训练时loss出现NaN标注坐标越界或出现异常框转换脚本中做坐标截断和尺寸过滤5.5 关于预训练权重和迁移学习的一些建议最后聊一下训练策略。拿到这份数据集没必要从零初始化训练。yolov8s.pt是在COCO上预训练过的权重用它做起点相当于模型已经知道物体边界是什么概念你只需要教会它积水长什么样。我用预训练权重训练50轮左右就能达到相对稳定的水平如果从零开始可能需要80轮以上还不一定收敛。另外一点是想办法利用数据集里的负样本。如果训练时只喂带积水标注的图片模型没见过足够多的干燥路面推理时就容易把阴影、深色柏油路误判成积水。我把负样本单独抽出来作为背景图混入训练流程误报率下降了接近一半。这份数据集中负样本占比不算高按我的实测从所有图片里抽样打乱作为背景补充效果也不错。从实际项目角度来看这套VOC格式道路积水数据集确实能省掉大量前期准备时间。我现在自己再跑积水检测项目基本固定用YOLOv8加这套数据做迁移学习然后再拿现场拍摄的少量图片做微调。如果你后面准备做更大规模的应用还可以用这份数据做预训练在自采数据上继续微调让模型适应目标区域的特殊路面和光照条件。整个链路跑通之后后面做什么都顺。本文还有配套的精品资源点击获取
