ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

道路积水检测数据集:VOC转YOLO与YOLOv8训练避坑指南

道路积水检测数据集:VOC转YOLO与YOLOv8训练避坑指南 简介面向道路积水检测、自动驾驶视觉感知等目标检测应用这是一份整理规范的标注数据集同时提供VOC与YOLO两种主流格式覆盖2699张道路积水图片类别只有water目标框共计3777个。压缩包内共2000个文件主体为1999个xml标注文件和1个txt说明文档整包约76.78MB体量适中便于下载与迁移。所有标注均由labelImg工具按矩形框规则逐张完成xml与txt一一对应边界框贴合积水区域且经过统一校对可直接接入YOLO系列、Faster R-CNN等常见检测框架无需额外转换。该数据集只包含water一个类别排除其他目标干扰特别适合做二分类检测模型的基准测试文件结构简明便于批量加载与检查。目前已有1068人浏览学习适合刚接触目标检测的初学者及智慧城市、水利监测等团队快速获取高质量数据省去采集与人工标注的时间将更多精力放到模型调优和积水识别的效果验证上提升项目研发效率。1. 道路积水检测2699张图的数据集能撑起什么样的落地模型雨季城市内涝年年有道路积水检测这两年已经从论文题目变成市政、交警和自动驾驶公司都在做的落地需求。这份“道路积水检测数据集VOCYOLO格式2699张1类别.7z”是典型的单类别目标检测数据集2699张真实道路场景图统一标注积水区域同时给出VOC和YOLO两种格式。它适合两类人一类是刚接触YOLO系列、想用干净数据跑通训练到部署全流程的新手另一类是做积水监测选型、需要快速评估单类别检测效果的工程师。2699张图不算大但对积水这种目标形态集中、背景变化有限的单类别任务只要标注质量和数据划分处理得当足够训练出一个能落地的模型。2. VOC与YOLO两种标注格式并存目录结构先看懂再动手拿到一个压缩包别急着解压就跑先弄清楚里面的目录组织。这类同时提供VOC与YOLO两种格式的数据集通常解压后能看到两个并列的根目录或者一个根目录下挂着两种子目录。以这个2699张1类别的道路积水检测数据集为例解压后应该能看到VOC风格和YOLO风格两套并列目录前者是给人复查用的后者是给训练框架直接读取用的。2.1 打开压缩包先看什么VOC格式的目录规范VOC格式源自PASCAL VOC挑战赛目录约定非常固定。打开压缩包后找这三个子目录JPEGImages/存放全部2699张原始图片命名一般是000001.jpg这种六位流水号或者带拍摄场景前缀的编号。Annotations/每张图片对应的XML标注文件文件名与图片文件名一一对应。ImageSets/Main/存放训练集和验证集划分的txt文档每行一个不带扩展名的图片文件名。XML标注文件里最关键的是object节点。积水是单类别所以每个XML里可能有多个object但name字段永远都是同一个类名比如water。每个object下面是一个bndbox四个坐标值xmin、ymin、xmax、ymax都是像素坐标对应图片里的绝对位置。拿到数据集后我一般先写一个统计脚本把每张图的object数量和类名都扫一遍确认类别只有一种。有些标注人员会用water和ponding两个名字标同一个东西这种情况在所谓的“1类别”数据集里经常出现不检查直接转格式后面积水检测模型训练出来会莫名其妙少掉一半正样本。统计脚本很短import os import xml.etree.ElementTree as ET ann_dir Annotations class_counter {} empty_files [] for f in os.listdir(ann_dir): if not f.endswith(.xml): continue tree ET.parse(os.path.join(ann_dir, f)) names [obj.find(name).text for obj in tree.iter(object)] if len(names) 0: empty_files.append(f) for name in names: class_counter[name] class_counter.get(name, 0) 1 print(class distribution:, class_counter) print(empty annotations:, len(empty_files), empty_files[:10])class_counter输出如果出现两个不同的键说明这份数据集的类名不纯净需要先归并类名empty_files里的图没有任何标注训练时要排除掉。这两类脏数据在公开数据集里很少见但在自制数据集里几乎是必现问题。提示VOC坐标是闭区间一张图宽640时xmax最大是639而不是640。转换到YOLO格式前要把这个边界问题处理好否则个别框的坐标会写成1.0以上训练时被YOLO框架忽略掉。2.2 YOLO格式的txt标注长什么样归一化坐标是怎么算的YOLO系列的标注格式和VOC完全不同。每个txt文件对应一张图片文件名与图片名一致拓展名变成.txt里面每行代表一个目标框格式是class_id x_center y_center width height五个值全部是归一化数值取值范围0到1。具体算法x_center (xmin xmax) / 2 / image_width y_center (ymin ymax) / 2 / image_height width (xmax - xmin) / image_width height (ymax - ymin) / image_height举个例子一张1920x1080的路口图XML里某个积水框的bndbox是xmin952, ymin401, xmax1423, ymax622那么换算到txt里就是0 0.6185 0.4736 0.2453 0.20460是类别id因为只有积水一个类别0.6185是框中心点的x方向归一化位置计算过程是(9521423)/2/1920后面三个数同理。这个换算过程必须自己手算一遍而不是靠感觉因为坐标差一个像素在归一化后看着都是0.0005级别的差异光盯屏幕根本发现不了。这个手算验证的过程用来检查别人提供的labels是否可靠比任何工具都有效。YOLO框架读取txt文件的目录一般叫labels/图片目录叫images/训练时按数据集根目录下的data.yaml里指定的路径去找对应文件。特别提醒一点网上有些教程会把VOC的ImageSets/Main里的划分文件直接复制成YOLO的train.txt但两份文件内容形式不同一个是裸文件名一个是带路径的完整条目直接copy过去会在训练时报找不到图片后面第4节会给正确做法。2.3 为什么这个数据集要同时给两种格式训练框架的适配路径同时给VOC和YOLO格式根本原因是训练框架的兼容性差异。YOLOv5、YOLOv8、YOLOv11这些ultralytics系框架原生吃YOLO格式的txt标注而很多做语义分割、做mmrotate检测、或者想用MMDetection系列框架的工程师习惯从VOC格式起步。mmrotate训练DOTA数据集那一套流程里VOC格式也是常见的中间格式方便后续做旋转框适配。所以数据集制作者直接把两种格式都生成好省去使用者在两个格式之间来回倒腾的时间。两种格式的核心差异可以看这张表对比项VOC格式YOLO格式标注载体每张图一个XML文件每张图一个txt文件坐标形式像素坐标系绝对坐标归一化坐标0到1类别标识name字符串class_id整数方便复查文本编辑器直接打开直观数值不直观需反算原生适配框架MMDetection等ultralytics YOLO系列对只跑YOLO的读者来说VOC格式部分可以暂时不看但别删掉。调试标注异常时回查XML比直接看txt要直观得多——txt里只有一个归一化数值看不出原始的xmin是多少XML是像素坐标一眼就能发现框的位置到底有没有画错。3. 把VOC转成YOLO的完整脚本2699张图五分钟处理完虽然这份道路积水数据集已经给了YOLO格式的txt但实际工作中拿到手的数据集经常只有VOC标注或者txt标注是别人用错坐标转换出来的。学会自己转格式比依赖现成文件更可靠——尤其是从网盘或数据集站点分享来的压缩包labels目录里偶尔会混进一套和JPEGImages目录对不上的脏txt。下面给一个我常用的转换脚本可以一次性处理整个Annotations目录对2699张图级别的数据集跑完只需要几秒钟。3.1 转换脚本从XML到txt的逐字段映射import os import xml.etree.ElementTree as ET # 配置三个路径标注目录、图片目录、输出目录 annotations_dir /data/water_dataset/Annotations images_dir /data/water_dataset/JPEGImages output_dir /data/water_dataset/labels os.makedirs(output_dir, exist_okTrue) # 类别映射表VOC的name字段 - YOLO的class_id # 这份数据集只有water一个类id固定为0 class_mapping { water: 0, ponding: 0, # 部分标注文件可能用了同义词归到同一类 } for xml_file in os.listdir(annotations_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(annotations_dir, xml_file)) root tree.getroot() # 读取图片尺寸yolo归一化必须用真实宽高 size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) filename root.find(filename).text # 取出图片后缀后面写txt时用同名文件 stem os.path.splitext(xml_file)[0] out_lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_mapping: # 排查脏数据的关键点遇到未知类直接打印 print(f[unexpected class] {xml_file} - {name}) continue class_id class_mapping[name] box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 边界裁剪坐标围到合法范围 xmin max(0, min(xmin, img_w - 1)) xmax max(0, min(xmax, img_w - 1)) ymin max(0, min(ymin, img_h - 1)) ymax max(0, min(ymax, img_h - 1)) # 过滤掉退化成点的框 if xmax xmin or ymax ymin: print(f[degenerate box] {xml_file}) continue # 归一化计算 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h out_lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) with open(os.path.join(output_dir, stem .txt), w) as f: f.write(\n.join(out_lines) \n) print(conversion done, check unexpected-class lines above)脚本逻辑分四步先解析XML拿图片宽高再遍历每个object取类别和框坐标归一化后拼成YOLO格式的一行最后写入同名txt。class_mapping里我故意把ponding也映射到0因为真实数据集中经常混用同义词不归并的话同一类别被拆成两个class_id训练时类别数配1也没用模型会学成两个没意义的类。unexpected class打印输出不能省转换完看一眼终端有未知类别出现就停下来改映射表别直接训练。3.2 类别映射表与文件命名最容易出错的隐式约定YOLO格式对文件名有严格的对应要求。图片是road_0234.jpg标注就必须是road_0234.txt多一个字符或少一个扩展名训练时这张图就变成无标注样本被YOLO当作背景图处理。我踩过的坑是把road_0234.jpg和road_0234.jpg.txt混在一起ultralytics不会报错但数据加载后正样本数量直接缩水。建议转换完成后跑一遍文件名比对以下代码可以快速找出缺标注的图片import os images_dir /data/water_dataset/JPEGImages labels_dir /data/water_dataset/labels image_files set(os.path.splitext(f)[0] for f in os.listdir(images_dir) if f.endswith((.jpg, .jpeg, .png))) label_files set(os.path.splitext(f)[0] for f in os.listdir(labels_dir) if f.endswith(.txt)) missing image_files - label_files extra label_files - image_files print(fimages: {len(image_files)}, labels: {len(label_files)}) print(fmissing labels: {len(missing)}) for m in list(missing)[:10]: print( , m)跑完看到missing labels: 0才是安全的。如果发现有几张图没有txt说明这份数据集的标注不完整要么回去补XML要么直接把这批图从训练列表里删掉不要带着无标注的背景图进积水检测模型的训练集。3.3 转换后验证用Python脚本检查每张图的标注是否越界归一化之后的值理论上都在0到1之间但由于标注框可能画到图片边界外标注人员手滑、或者图像resize过转换后会出现x_center 1或者x_center width/2 1的脏数据。YOLO训练时遇到这种框通常不报错而是直接丢弃导致有效正样本减少。我一般在训练前跑一次边界校验import os labels_dir /data/water_dataset/labels bad_files [] for txt_file in os.listdir(labels_dir): if not txt_file.endswith(.txt): continue with open(os.path.join(labels_dir, txt_file)) as f: for line in f: parts line.strip().split() if len(parts) ! 5: bad_files.append((txt_file, field count)) continue cls, xc, yc, w, h parts[:] xc, yc, w, h float(xc), float(yc), float(w), float(h) if not (0 xc 1 and 0 yc 1): bad_files.append((txt_file, center out of range)) elif not (0 w 1 and 0 h 1): bad_files.append((txt_file, size out of range)) elif xc w / 2 1 or yc h / 2 1: bad_files.append((txt_file, box exceed right/bottom edge)) elif xc - w / 2 0 or yc - h / 2 0: bad_files.append((txt_file, box exceed left/top edge)) if bad_files: print(ffound {len(bad_files)} bad annotations:) for f, reason in bad_files[:20]: print(f {f}: {reason}) else: print(all annotations pass bounds check)这个脚本检查三类问题五个字段缺一不可、中心坐标越界、框超出图片边界。cls这一列虽然在校验脚本里没有实际参与运算但保留它才能保证字段数量校验有效。遇到box exceed right/bottom edge的情况不要改txt回XML里看原始框是不是画错了否则越改越乱。数据集制作时如果做过数据增强比如随机裁剪后再导出这类问题特别常见。3.4 为什么我不推荐直接用别人的转换工具网上有大量现成的VOC转YOLO脚本很多是基于标注框读取工具包做的。工具很快但加载时会把XML里的所有object都读进来类别过滤做得很粗糙。积水数据集里偶尔混着一两张带car或者person标注的图整批转换出来的labels文件里class_id就会错位。所以对这种单类别小数据集我更推荐用上面的精简脚本自己跑一遍逻辑透明坏数据直接打印出来处理完心里有数。4. 用YOLOv8在积水数据集上训练最小可复现的命令与参数格式整理完进入训练环节。对2699张单类别数据YOLOv8是稳妥的选择——训练代码成熟、显存占用可控、自带增强策略不需要自己写训练循环。下面从数据集配置和训练命令两个层面讲怎么落地。4.1 用模型预训练权重训练还是从零开始新手最容易犯的错是用随机权重从头训练。YOLO系列在COCO上预训练过骨干网络已经学会识别纹理、边缘、形状这些通用模式。积水边缘在图像上表现为路面区域的反光、色差和边缘轮廓这些特征与COCO里的水体、地面类目标共享低层特征。直接用预训练权重做迁移学习收敛速度和最终精度都远好于从零训练。YOLOv8的预训练模型分n/s/m/l/x五个型号。对2699张积水图我建议按训练机器的资源来选模型参数量级建议显存适用场景yolov8n最小4GB可跑快速验证流程是否能走通yolov8s较小6GB常规分辨率推荐yolov8m中等8GB追求mAP提升显存充足时推荐训练前用yolov8n.pt把训练脚本、数据加载、损失计算这些环节全部跑通确认没有问题后再升级到s或m这是目标检测项目里值得养成的习惯。直接上大模型一旦数据路径或标注格式有问题排查成本会成倍增加。4.2 单类别检测的训练配置classes1的关键参数用ultralytics框架训练时数据集目录建议整理成这样的结构water_dataset/ ├── data.yaml ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/data.yaml里最关键的是类别数、类别名和路径。积水是单类别配置写为# 道路积水检测数据集配置 path: /data/water_dataset # 数据集根目录 train: images/train # 训练图片目录 val: images/val # 验证图片目录 nc: 1 # 类别数只有积水一类 names: 0: water # 类别名与class_id对应nc填成别的数字比如错误地填成2是隐患最大的一个参数。有些开发者因为数据集里偶然出现了一张带其他目标的标注图就把nc改成2结果模型把一个类别学成两个mAP上去了但实际检测效果很差。解决办法就是严格用转换脚本里的class_mapping控制类别保证整个数据集的class_id范围只有0。names列表的写法也容易出错。names: {0: water}和names: [water]效果一致但如果写成names: [water, other]同时nc: 1训练时会因为列表长度与nc不一致直接报错。这类错误在ultralytics的不同版本里提示并不统一有的版本报AssertionError有的版本只在日志里打一行警告然后自动忽略多出来的名字让人摸不着头脑。4.3 跑通训练命令、日志怎么看、权重怎么选配置完成执行训练命令yolo train \ modelyolov8s.pt \ data/data/water_dataset/data.yaml \ epochs100 \ imgsz640 \ batch16 \ patience15 \ project/data/water_dataset/runs \ namewater_train_v1逐一说明关键参数modelyolov8s.pt加载预训练权重让ultralytics按默认路径自动下载即可。不要用网上别人二次蒸馏过的所谓“专用预训练模型”那种往往改了输出头的维度训练时会报维度不匹配。epochs100对2699张图不算多YOLO的默认增强会让每个epoch实际看到的数据变化很大100个epoch能让收敛曲线平稳下来。imgsz640平衡精度和速度的经典分辨率。积水框在原始图中占比往往不大如果发现漏检远处的小面积积水可以提到800或960但显存和训练时间会明显上升。batch16根据显存调整够用就行。单类别数据batch16和batch32的最终精度差异很小多出来的时间不值得。patience15连续15个epoch验证集指标没提升就早停。这个值设太小时模型可能还在平台期就被停掉设太大又浪费时间10到20之间都算合理。project和name日志和权重统一输出到/data/water_dataset/runs/water_train_v1/方便多轮实验对比。训练中重点关注两行日志Box_Loss和cls_loss。积水框形状比较规则Box_Loss收敛到0.02以下cls_loss收敛到0.5以下模型就基本可用了。训练结束后在runs/water_train_v1/weights/目录下会产生两个权重文件best.pt验证集mAP最高的权重是默认部署选择。last.pt最后一个epoch的权重如果patience设太大导致提前保存用last.pt续训比从best.pt继续更合理。模型验证用以下命令yolo val \ model/data/water_dataset/runs/water_train_v1/weights/best.pt \ data/data/water_dataset/data.yaml \ conf0.25conf0.25是决定预测框是否保留的置信度阈值。验证时把阈值调低到0.1再看一遍会看到很多被压掉的正确检测框这是判断模型真实能力的一个习惯操作。5. 道路积水数据集避坑5条从标注到训练的踩坑记录做积水检测项目的人不算多但数据集和训练里踩的坑高度相似。下面五条是从标注整理到训练调参的真实翻车场景每一条都按“现象 → 原因 → 解决”来写。5.1 现象训练mAP很高但实际预测积水区域偏移验证集mAP达到0.8以上看起来很不错但把这个权重接到实景视频里发现预测框经常比实际积水范围大一圈边界来回抖动。这是积水检测项目落地时最典型的翻车现象。原因在于标注边界不统一。积水没有清晰轮廓标注人员有的把整个反光区域框进去有的只框深色水域中心部分边界框IoU天然就存在0.1到0.2的抖动。mAP计算时预测框只要与GT有50%重叠就算正样本边界不一致对mAP的影响很小但对后续输出积水面积统计影响很大。解决办法是复标但不必全部重标。统计每张图标注框面积的中位数把标注框面积偏离中位数2倍以上的图单独挑出来复查。这类图通常是把路灯倒影、油污路面误标成积水的。5.2 现象7z解压报错提示密码错误但确认密码确实正确从网盘下载的数据集是.7z压缩包解压时提示密码不对但下载页面标注的密码明明就是正确的。Windows下在7-Zip图形界面里尤其常见。原因是7z压缩时的编码格式与系统当前使用的编码不一致。压缩包内文件名如果包含中文或特殊字符在Windows默认的GBK环境下解压文件名编码映射失败7-Zip会报“数据错误”或“密码错误”。解决方法是先用7z t测试压缩包完整性确认文件没有损坏7z t water_dataset.7z -pwater_2024测试通过的话在7-Zip设置里勾选“用UTF-8传递文件名”大部分情况下就能正常解压。Linux环境下则先安装p7zip再解压sudo apt install p7zip-full 7z x water_dataset.7z -o/data/water_dataset -pwater_2024-o指定解压目录-p后面紧跟密码注意-o与目录之间不能有空格。如果密码真的忘记了7z没有找回机制只能重新下载原始分享文件这一点下载前就应该确认清楚先看分享页有没有补充说明再决定要不要为整包密码赌一把。5.3 现象YOLO格式标注出现0.5 0.5 1.0 1.0整张图一个框打开labels目录检查某个txt文件发现每一行的数值几乎都相同比如0 0.5 0.5 1.0 1.0。这代表这张图的积水目标被标注成了整图范围的一个大框几乎没有任何定位价值。原因通常是数据集生成时标注工具导出了默认框或者某次批量处理时坐标归一化用错了分母——把图片宽高写成了目标框的宽高导致所有坐标都被放大到整个图像范围。这类图分布在训练集和验证集中时会显著拖低验证集mAP让人误以为模型没有收敛。解决办法是写一个统计脚本把宽高占比超过0.8的框全部列出核实这些图是否真的是大面积积水。如果是保留少数几张可以接受如果大部分图都是这种整框标注说明这份数据集的标注质量不可靠优先用标注工具重新标明显有问题的图比直接训练更省时间。5.4 现象白天雨天图效果很好夜间积水几乎完全漏检用训练好的模型检测夜间视频发现夜间积水的召回率不足白天的一半。排查时先看数据集里夜间图像的比例典型情况下2699张图里夜间可能只有个位数这样的比例让模型完全没法学到夜间泛光条件下的积水形态。原因很简单数据集采集时只抓了白天道路画面但部署地每天的早晚高峰恰恰是雨天积水最容易出问题的时间段。解决办法不是简单加几张夜间图而是在数据准备阶段就把夜间、逆光、路灯反光这三类难例各补50张以上补充后重新划分训练验证集。这个操作对积水检测模型夜间泛化能力的提升比换任何预训练权重都明显。5.5 现象模型对路面缝隙和阴影产生误检验证集图片恰好全部来自同一条路训练时验证集mAP高得出奇但换个路段摄像头立刻出现大量误检。原因是数据划分时没有做路段隔离同一道路的白天、晚上、雨天图片同时出现在训练集和验证集里模型记住的场景特征被验证分数掩盖了。解决方法是按采集时间和地点做分组划分。把相同地点、相邻时段的图分到同一组再对组做随机划分确保验证集和训练集来自不同的道路或不同时间段。这样训练出的模型泛化能力才接近真实部署条件。对这个2699张的数据集如果采集地点少于三个不要期待模型能很好地泛化到全新路段部署前必须补充目标路段的少量现场图做微调。6. 验证模型的“干活能力”两级推理与真实视频测试模型训练完最后一步是拿到真实视频流里验证。很多工程师只看验证集mAP这个习惯要改——单类别积水检测的mAP很容易超过0.9但这只说明模型在测试集上的表现。真机视频里积水区域随镜头角度、光照和车速变化极大模型能否在160ms内输出稳定的框比0.01的mAP提升重要得多。NDCG是信息检索里衡量排序质量的指标核心思想是排越靠前的正确答案权重越高。积水检测部署评估也是同一个逻辑多个候选框按置信度排序排最前面的框是否命中真实积水比整体mAP更能反映现场可用性。先把best.pt部署到一段没进过训练集的雨天路口视频上from ultralytics import YOLO model YOLO(/data/water_dataset/runs/water_train_v1/weights/best.pt) results model.predict( source/data/test_rain_intersection.mp4, conf0.25, iou0.5, saveTrue, save_txtTrue, project/data/water_dataset/deploy_test )跑完看输出帧的前50帧重点检查置信度最高的前三个框是否都落在真实积水区域。如果排名靠前的框经常落在阴影或破损路面把conf从0.25提高到0.4过滤低置信度噪声框同时用iou0.5抑制同一个积水区域上重叠的多个框。如果漏检的是远处小面积积水这种小目标有个两级推理技巧很好用先用imgsz640跑全图把置信度落在0.1到0.4之间的可疑区域截图再对这块区域用imgsz960做二次检测。这个技巧能在不显著增加耗时的前提下把小积水区域的召回率提升10%左右代价只是推理代码比单次推理多十几行裁剪逻辑。我的习惯是训练好后先不急着调参找一段真实环境视频连续看50帧有漏检就记录坐标和时间段回头统计这些样本属于哪种场景统一补进数据集。这比反复调conf和iou有效得多也有助于判断这份YOLO格式的2699张积水数据到底值不值得继续投入补充标注。希望这一整套从解压数据集到部署验证的流程能帮你在道路积水检测这个方向少走弯路。本文还有配套的精品资源点击获取
返回列表