news 2026/10/12 15:33:57

道路积水检测数据集:2699张VOC+YOLO双格式标注图

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
道路积水检测数据集:2699张VOC+YOLO双格式标注图

简介:面向道路积水检测场景的VOC+YOLO双格式标注数据集,适合目标检测算法训练、模型评估及毕业设计等用途;数据集中覆盖2699张道路积水图片的标注信息,类别统一为water,共3777个目标框,采用labelImg工具按矩形框规则绘制,标注准确且规范,可为YOLO系列、Faster R-CNN等模型提供直接可用的训练数据。压缩包共2000个文件,以VOC格式xml标注文件与YOLO格式txt标注文件为主,并附有使用前必读txt说明,整体大小约76.78MB,便于下载、解压与快速查阅;目前已有1075人学习浏览,受到了不少目标检测学习者的关注。这份数据集免去了自行采集图片和标注的繁琐过程,用户下载后即可按对应格式接入训练流程,特别适合在道路积水检测项目或算法对比实验中作为基础数据支撑;配套说明文档和CSDN原文链接可帮助理解标注规范与文件组织方式,对于目标检测入门者,还可基于此数据集实践数据划分、格式转换与模型调参等完整流程。

1. 道路积水检测数据集:2699 张 VOC+YOLO 双格式图,拿来就能训的积水识别底料

市政、高速、园区安防里,“路面积水”是典型的小目标、低对比度检测难题:水洼没有固定形状,反光还会骗过很多模型。做过一段时间道路场景目标检测的人应该都有同感,网上公开的积水图源太散,自己爬图标注一轮下来一周就没了。这份道路积水检测数据集解决的就是这个痛点——2699 张 jpg 全部带 Pascal VOC xml 和 YOLO txt 双格式标注,单类别 water,共 3777 个矩形框,标注工具是 labelImg,框得规整,拿来就能当 YOLOv5/YOLOv8 的 baseline 底料。适合两类人:一类是做智慧市政、汛期巡检项目需要快速出原型验证的算法工程师;另一类是刚接触目标检测、想找一份“格式齐全、不用清洗直接用”的数据集练手的学习者。它只承诺标注准确,不承诺模型精度,这点后面会专门说。

2. 目录与标签结构:三件套文件里,VOC 和 YOLO 存的是同一个积水框

2.1 解压后先看这四样:jpg、xml、txt 和必读说明

拿到压缩包解压之后,你看到的不是 images/labels 这种标准 YOLO 工程目录,而是一堆平铺的文件。这一点跟很多“下载即训练”的数据集不太一样,但不难处理。文件清单里主要有这样几类:

使用前必读.txt firc_water_0001.jpg firc_water_0001.xml firc_water_0001.txt ... firc_water_2699.jpg firc_water_2699.xml firc_water_2699.txt

每个firc_water_编号对应三件套:jpg 是原始图片,xml 是 Pascal VOC 格式标注,txt 是 YOLO 格式标注。我拆的这份一共 2699 张图,xml 和 txt 也正好各 2699 个,说明每张图都有对应标注,不是那种“一半图没标签”的残缺数据集,这是它比较实在的地方。

先打开使用必读.txt,里面写清楚了标注工具是 labelImg、标注规则是矩形框、类别只有 water。这些信息看起来简单,但实际很重要——比如“只有矩形框”意味着这份数据不能直接喂给分割模型,如果你想做积水分割,得自己把框转成 mask,这就是后话了。

提示:文件是平铺的,训练前需要自己按 images/labels 目录重组。别偷懒跳过这步,YOLO 工程目录不对,后面训练直接报错。

2.2 xml 的 bndbox 与 txt 的归一化坐标:3777 个框怎么一一对应

这一节是整个数据集的核心。很多人拿到双格式标注会困惑:xml 和 txt 是不是内容不一样?其实是同一批框的两种表达。VOC 格式存的是像素坐标,YOLO 格式存的是归一化坐标。

随便打开一个 xml 看结构:

<annotation> <filename>firc_water_0687.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>water</name> <bndbox> <xmin>412</xmin> <ymin>355</ymin> <xmax>648</xmax> <ymax>402</ymax> </bndbox> </object> </annotation>

对应的 txt 文件里存的是五行中的一列格式:

0 0.276042 0.350463 0.122917 0.043519

第一列是类别 id,因为只有一个 water 类,所以全是 0。后面四位分别是归一化后的中心点 x、中心点 y、框宽 w、框高 h。这两个文件之间是精确换算关系:

  • x_center = (xmin + xmax) / 2 / width
  • y_center = (ymin + ymax) / 2 / height
  • w = (xmax - xmin) / width
  • h = (ymax - ymin) / height

xml 里 width=1920、height=1080,bndbox 四个值是像素角点;txt 里存的是小数,范围在 0~1 之间。这也就是摘要里说的“不包含分割路径的 txt 文件”——它指的是标签目录里没有 train.txt 那种图片清单文件,每张图对应的 label txt 是齐全的。容易混的人不少,把它理解成“标签文件有 2699 个,但训练列表要自己生成”就对了。

另外我算了一下,3777 个框分到 2699 张图里,平均每张约 1.4 个框。这说明相当一部分图只有一个框,少数图有两个以上。这种分布对训练本身是正常的,但新手容易误判成“标注不全”——不是漏标,是积水面在画面里本来就只占一小块区域。

表格整理一下两种格式的差异,后面章节会反复用到:

格式坐标含义数值范围典型文件
VOC xml左上角 xmin/ymin、右下角 xmax/ymax,像素坐标0~图片宽/高firc_water_0001.xml
YOLO txt中心点 cx/cy、框宽 w/框高 h,归一化0.0~1.0firc_water_0001.txt

3. XML 转 YOLO 与数据划分:写一个不碰手就生成 yaml 的小脚本

3.1 解析 xml 的四个坐标:读取顺序比你想的更讲究

虽然这份数据集已经同时给了 xml 和 txt,但你还是需要读懂 xml 解析逻辑。原因有两个:一是当你把 water 改成其他类别名、或者想把两三个数据集合并时,xml 是唯一的“母版”;二是排查标签问题时,你得手写脚本对比 xml 和 txt,这一步躲不开。

以下是从 VOC 解析出归一化框的常见做法:

import xml.etree.ElementTree as ET def parse_voc(xml_path): tree = ET.parse(xml_path) root = tree.getroot() size = root.find('size') width = int(size.find('width').text) height = int(size.find('height').text) boxes = [] for obj in root.iter('object'): name = obj.find('name').text bndbox = obj.find('bndbox') xmin = float(bndbox.find('xmin').text) ymin = float(bndbox.find('ymin').text) xmax = float(bndbox.find('xmax').text) ymax = float(bndbox.find('ymax').text) boxes.append({ 'name': name, 'x_center': (xmin + xmax) / 2 / width, 'y_center': (ymin + ymax) / 2 / height, 'w': (xmax - xmin) / width, 'h': (ymax - ymin) / height, }) return width, height, boxes xml_file = 'firc_water_0687.xml' w, h, boxes = parse_voc(xml_file) for b in boxes: print(b['name'], round(b['x_center'], 6), round(b['y_center'], 6), round(b['w'], 6), round(b['h'], 6))

这段代码的逻辑说明:先用size拿到图片原始宽高,这是归一化的分母;再用iter('object')遍历所有积水框,不写findall是为了兼容某些 xml 里 object 节点嵌套的写法。四个坐标分开找的原因是 labelImg 生成的 bndbox 子节点顺序稳定,但严谨起见别用list(bndbox)取下标,因为遇到带 rotated 属性的工具导出时顺序会变。参数说明:xmin/ymin 是左上角,xmax/ymax 是右下角,中心点必须取均值再除宽高,不能直接把 xmin 除宽高当成 cx,那是新手常踩的坑。

如果你发现 txt 和 xml 对不上,优先怀疑这个换算过程,特别是 width 和 height 取的是 xml 里的值而不是从 jpg 读出来的值。两者不一致时, txt 就是以 xml 为准生成的,后文避坑清单里会展开。

3.2 按 8:2 划分并生成 road_water.yaml:随机种子和划分逻辑

既然数据集没给训练列表,这一步就得自己来。我用的是常见的 8:2 划分,训练 2159 张、验证 540 张,随机种子固定下来保证每次复现结果一致。

import os, random, glob random.seed(42) xml_files = sorted(glob.glob('*.xml')) random.shuffle(xml_files) val_count = int(len(xml_files) * 0.2) val_list = xml_files[:val_count] train_list = xml_files[val_count:] def write_list(fname, files): with open(fname, 'w', encoding='utf-8') as f: for xml_path in files: img_path = xml_path.replace('.xml', '.jpg') f.write(os.path.abspath(img_path) + '\n') write_list('road_water_train.txt', train_list) write_list('road_water_val.txt', val_list) print(f'train={len(train_list)}, val={len(val_list)}')

这个脚本的意义在于:txt 里面的每一行是训练图片的绝对路径,YOLOv5 的 data.yaml 可以直接吃这种列表文件。固定random.seed(42)不是玄学,是保证你调模型时换参数不换划分,否则每次训练集都变,模型优劣没法对比。val 占比我按 20% 取值,如果你的积水图里某些路段反复出现,建议看第 5 章的哈希分桶方案,那种情况随机划分会出问题。

接着写 data.yaml:

train: /your/abs/path/road_water_train.txt val: /your/abs/path/road_water_val.txt nc: 1 names: ['water']

注意三点。第一,train/val 必须是绝对路径,相对路径在 YOLOv5 某些版本里会报AssertionError: train.txt not found,但报错信息不明显,容易排查半天。第二,nc是类别数量,这里必须写 1,不是写 3777,很多人看到框数写成类别数,训练直接崩溃。第三,names列表下标 0 对应 txt 第一列的 0,也就是 water,顺序不能换。

这类单类别数据集有一种常见误用:强行把names: ['water', 'background']凑成两类来“提升背景区分度”。如果图片里背景区域没有负样本标注,模型反而会在背景上乱画框,正确做法是依靠置信度阈值过滤背景,而不是加类别。

4. YOLOv5 训练 road-water:batch、img-size 和 epochs 怎么配合你的显卡

4.1 环境和数据集摆放:目录树错了,训练直接报错

数据集重组花五分钟,后续训练省两小时。我习惯按 YOLOv5 默认约定来组织目录,它会在图片目录同级自动找labels目录,不用你在 yaml 里写 label 路径。

road_water/ ├── images/ │ ├── train/ │ │ ├── firc_water_0001.jpg │ │ └── ... │ └── val/ │ ├── firc_water_1001.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── firc_water_0001.txt │ │ └── ... │ └── val/ │ ├── firc_water_1001.txt │ └── ... ├── road_water.yaml ├── road_water_train.txt └── road_water_val.txt

动手搬文件时,我一般直接用第 3 章生成的 train.txt 和 val.txt 来驱动移动脚本,避免手滑把同名不同后缀的文件挪串了。移动完跑一下find . -name "*.jpg" | wc -l,train 和 val 下的图片数量加起来必须等于 2699,少一张都说明划分列表有问题。labels 目录同理。

环境部分,YOLOv5 官方仓库克隆下来后,Python 3.8 以上、PyTorch 1.8 以上基本都能跑:

conda create -n yolo python=3.8 -y conda activate yolo pip install -r requirements.txt python train.py --data road_water.yaml \ --weights yolov5s.pt \ --img 640 --batch 16 --epochs 100 \ --cache

参数说明:--weights yolov5s.pt是拿 COCO 预训练权重做迁移学习,比随机初始化收敛快得多,尤其积水这种纹理特征不明显的目标;--cache会把图片提前缓存到内存,第一个 epoch 慢吞吞,后续明显提速,但内存不够时它会反过来拖垮训练,这个第 5 章会讲。

4.2 训练命令与参数取舍:8G 显存的推荐配置和判断依据

训练参数没有绝对标准,但可以按显存给一套不容易翻车的起步值。积水是小目标,--img别低于 640,低于这个值小水洼直接缩成一个点。下面是我在三种显存条件下的常用搭配:

显存img-sizebatchepochs预期时间(单卡 3080 级别)
6 GB5128100约 1.5 小时
8 GB64016100约 1 小时
12 GB+64032150约 1 小时

这里有个判断依据:batch 增大、训练震荡幅度减小,但显存溢出直接 OOM 崩掉;img-size 降为 512 对小目标不友好,所以 6G 显存卡建议同时把模型换成yolov5n.pt而不是硬扛yolov5s。积水框平均每张只有 1.4 个,正样本密度低,epochs 低于 80 通常还没收敛到位,我一般设 100 起步看曲线再定。

跑完之后验证命令:

python val.py --data road_water.yaml \ --weights run/train/exp/weights/best.pt \ --img 640 --batch 16

val.py会输出 Precision、Recall、mAP@0.5、mAP@0.5:0.95 四组核心指标,还会在runs/val/exp/下生成混淆矩阵和可视化的预测标签图。这些文件是你判断“数据集质量行不行”的第一手证据。

4.3 训练日志解读:单类别任务盯 box_loss 和 mAP@0.5

积水检测只有一个类别,所以训练日志里最该盯的两个指标是box_loss和mAP@0.5。cls_loss在这种单类任务里参考价值有限,它衡量的是“是不是水”的分类置信度,如果这个值一直不降,大概率不是模型问题,而是标注框里混进了大量边缘反光区域。

训练过程中每一轮会打印一行,关键字段包括:

  • box_loss:框回归损失,代表预测框和标注框的位置偏差,应该整体下降但允许小幅震荡
  • obj_loss:目标置信度损失,积水样本少,这个值掉得慢是正常的
  • mAP@0.5:IoU 阈值 0.5 下的平均精度,积水这类低对比度目标,0.5 比 0.95 更贴近实际部署体验
  • val/box_loss:验证集上的回归损失,如果训练损失降了而验证损失反弹,说明过拟合,应提前早停

一个常见误读是看到mAP@0.95只有 0.3 就断言模型不行。对这个数据集的积水框来说,水洼边缘不平整,标注框本身就有一定的标注者主观性,mAP@0.95 卡得越死越低是正常的。部署时判断积水更依赖 mAP@0.5 和召回率,尤其“漏报一滩水”比“多报一处反光”后果更严重。所以我在这个场景下优先看 Recall,压过 0.9 再谈 Precision 优化。

最后一件事,训练结束后去runs/train/exp/里翻一下labels.jpg。它会把所有标注框按归一化坐标画在一张拼图上,一眼就能看出框有没有偏移、有没有跑到图片外面去。这个习惯救了我不止一次。

5. 避坑清单:坐标、类别 id、划分泄漏这五关,翻车现场复盘

5.1 现象:txt 第一列被写成了 1,训练完一个目标都检测不到

原因:VOC 的name是字符串,转 YOLO 时某些脚本用字典按字母排序生成类别映射,如果映射表里除了 water 还有其他历史类别,water 的 id 可能就不是 0。而 data.yaml 里names: ['water']只认 id=0,id=1 直接被视为越界或忽略。解决:解压后抽 3~5 个 txt 文件看第一列,必须全是 0。我一般批量检查:

grep -Rh '^[^0]' labels/ | head

如果输出为空说明所有标签首列都是 0,放心继续。

5.2 现象:自己写转换脚本时,框整体往右下偏移,mAP 起不来

原因:把xmin直接当成中心点归一化,公式写成了cx = xmin / width,漏了(xmin + xmax) / 2。YOLO 存的是中心点,不是左上角,这个至少能坑掉一半第一次手写转换的人。解决:用第 3 章的 parse 函数跑一遍已知 xml,把输出和原始 txt 逐行对比,如果前 4 位小数能对上,说明转换逻辑没问题;对不上就回去检查公式。这里没有玄学,就是数学。

5.3 现象:mAP 虚高到 0.95 以上,但视频实测频频漏检

原因:数据集划分泄。这份数据集的图是从道路场景来的,虽然 2699 张编号不连续有随机性,但如果你的划分方式恰好把同一路段、同一时段、不同编号的连续帧同时放进了 train 和 val,验证集相当于“开卷考试”,mAP 虚高,视频里遇到新路段就现原形。解决:不按随机种子洗牌,改成按文件名哈希分桶。文件名是firc_water_编号,对编号做哈希后取余 5,余数 0 的进验证集,其余进训练集,相同编号区间不会扎堆进同一侧。这类“数据泄漏”是目标检测里最难排查的坑之一,因为损失曲线看起来完全正常。

5.4 现象:训练不报错,但可视化时框明显错位,水洼框到了路面上方

原因:xml 里记录的 width/height 和 jpg 实际尺寸不一致。常见于数据集作者处理过图片尺寸但 xml 没同步更新,或标注时用了 EXIF 旋转后的尺寸导致宽高互换。解决:训练前强制做一次一致性校验,把每个 xml 的 size 和对应 jpg 的真实 shape 比对:

import cv2, glob, xml.etree.ElementTree as ET for xml_file in glob.glob('*.xml'): tree = ET.parse(xml_file) root = tree.getroot() size = root.find('size') w, h = int(size.find('width').text), int(size.find('height').text) img = cv2.imread(xml_file[:-4] + '.jpg') ih, iw = img.shape[:2] if (w, h) != (iw, ih): print(f'{xml_file}: xml={w}x{h}, jpg={iw}x{ih}')

这段代码的作用是把“宽高不一致”的图揪出来。这类文件一般只有个位数,直接删掉对应三件套即可,不用纠结修复,2699 张里少几张不影响整体。注意xml_file[:-4] + '.jpg'依赖文件名严格对应,如果原文件有大小写不一致,用os.path.splitext处理更稳。

5.5 现象:加了--cache后训练中途被杀进程,或者机房机器直接卡死

原因:--cache默认把图片以内存对象形式缓存,2699 张 1080p 图大约要吃 3~5 GB 内存,在你本地 16G 内存的机器上问题不大,但如果同时开多个训练任务或者用的远程容器内存受限,OOM Killer 会直接杀掉进程。解决:显存够、内存不够的机器去掉--cache,换--workers 0来规避 DataLoader 多进程崩溃;确定要吃缓存的话,先把--cache改成--cache ram试试水位,不行就放弃。这个坑在 6G 显存的旧卡上尤其常见,属于典型的环境性问题。

6. 验证与进阶:labels.jpg 质检到积水面积估算的落地闭环

6.1 先跑一遍可视化,再谈训练结果

训练收到best.pt后,我第一件事不是看 mAP 表,而是打开runs/train/exp/下的labels.jpg和labels_correlogram.jpg。前者把所有训练框叠在一张画布上,后者展示框宽高分布。积水场景里有个规律:真实的积水框宽高比集中在 1:2 到 2:1 之间,如果 labels_correlogram 里出现大量细长条,大概率是标注框把道路边缘的排水沟阴影也框进去了。这类图只需要肉眼扫一遍,比任何指标都直观。

6.2 从检测框到面积占比:一个可直接抄的粗粒度估算脚本

很多积水场景的实际诉求不是“哪里有水”,而是“积水面积多大”。检测框是矩形,水洼是不规则形状,但部署侧可以先拿框面积占整图比例做粗粒度估算,再按需接分割模型精修。下面是一个基于已有 label 的快速估算模板:

import cv2, glob labels = sorted(glob.glob('labels/val/*.txt')) img_dir = 'images/val/' calib_m2_per_pixel = None # 标定值,来自相机内参或现场实测 for lb in labels: img = cv2.imread(img_dir + lb.split('/')[-1][:-4] + '.jpg') h, w = img.shape[:2] for line in open(lb): parts = line.strip().split() if len(parts) < 5: continue _, cx, cy, bw, bh = map(float, parts) x1, y1 = int((cx - bw / 2) * w), int((cy - bh / 2) * h) x2, y2 = int((cx + bw / 2) * w), int((cy + bh / 2) * h) pixel_area = (x2 - x1) * (y2 - y1) img_area = w * h ratio = pixel_area / img_area print(f'{lb.split("/")[-1]}: 框面积占比 {ratio:.3%}')

逻辑说明:读取归一化标签还原像素坐标,框像素面积除以全图面积得到占比。为什么用占比而不是绝对面积?因为视觉上“占画面 10% 的积水”远比“5000 平方厘米”直观,而且占比可以直接作为后续规则引擎的阈值。如果要换算真实平方米,需要相机标定得到m2_per_pixel,典型做法是现场放一个已知尺寸的标定板,用检测框像素数除以真实面积得到系数,把这个系数填进calib_m2_per_pixel做乘法即可。参数说明:这套脚本不做过滤,直接遍历所有框,适合离线统计;在线推理时把cx, cy, bw, bh换成模型输出即可。

更进一步,如果你想做精细积水面积而不是粗粒度估算,有两个方向:一是把矩形框作为种子区域,在框内用颜色饱和度或灰度方差做二次分割,反光区域和水面本身的纹理差异足够分开;二是把这份数据转成分割标签后用 YOLOv8-seg 或 SAHI 切片推理训练,能明显提升小水洼的检出。联网部署时如果用 TensorRT 做 INT8 量化,注意积水场景的反光变化大,量化校准集要选包含阴影和夜间反光样本的图,否则精度掉得比一般目标更狠。

那次之后我养成了一个习惯:任何数据集拿到手,第一步永远是跑 labels 可视化并抽查十个 txt 的类别 id,而不是直接进训练。检查这三分钟换来的是一整轮训练不白跑。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 15:33:54

CEC测试集实战指南:进化算法压力测试与Python封装

简介&#xff1a;本资源是面向智能优化算法研究者与进阶学习者的CEC系列单目标测试函数标准集&#xff0c;涵盖CEC2010、2013、2014、2015及2017共五个年度的完整基准问题实现&#xff0c;为进化算法、群体智能算法等新方法的性能验证提供权威评测环境。资源包含516个文件&…

作者头像 李华
网站建设 2026/10/12 15:33:50

LOL英雄检测数据集:3000张VOC+YOLO双格式标注,开箱即训YOLOv8/v10

简介&#xff1a;本资源是面向计算机视觉初学者与目标检测实践者的LOL英雄联盟角色检测专用数据集&#xff0c;适用于YOLO、Faster R-CNN等主流检测模型的训练与验证。数据集共3000张游戏场景截图&#xff0c;涵盖6类关键目标&#xff1a;己方小兵、己方防御塔、敌方小兵、敌方…

作者头像 李华
网站建设 2026/10/12 15:33:35

Oracle PL/SQL 账号批处理:用 TaoToken 统一 Key 打通存储过程调用链

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/12 15:33:19

3块钱2小时训出64M参数中文大语言模型:MiniMind实操复盘

很多人在刚接触大语言模型时&#xff0c;第一反应都是&#xff1a;训练LLM是高门槛的事&#xff0c;没有几块A100、几百GB数据、几十万预算根本玩不转。但事实是&#xff0c;随着开源生态和训练技术的成熟&#xff0c;个人开发者用极低成本训练一个小模型&#xff0c;已经不是什…

作者头像 李华
网站建设 2026/10/12 15:31:49

Excel导入实战:从解析到校验的完整链路与excelimportor 0.0.4应用

简介&#xff1a;Excelimportor 0.0.4 是一款面向前端开发者的开源 Chrome 扩展&#xff0c;用于将 Excel 表格数据快速导入网页&#xff0c;并兼容含 iframe 嵌套结构和 select 下拉控件的复杂页面&#xff0c;省去手写解析与元素匹配代码的繁琐过程。压缩包共 15 个文件&…

作者头像 李华
网站建设 2026/10/12 15:31:45

浏览器播放RTSP监控流:html5_rtsp_player部署与避坑指南

简介&#xff1a;这是一套面向 Web 前端的 HTML5 RTSP 播放器完整源码包&#xff0c;目标是在浏览器中不依赖插件直接播放 RTSP 视频流&#xff0c;适用于网络监控、视频会议等实时流媒体场景。压缩包共 69 个文件&#xff0c;以 57 个 JavaScript 文件为主&#xff0c;覆盖播放…

作者头像 李华