简介:这份积水图像分割检测数据集面向具备Python与CNN基础、从事图像分割或目标检测的开发者与研究者,可用于YOLOv8、YOLOv11等框架的训练与验证,帮助解决积水场景下像素级分割与目标定位的数据来源问题。资源包共1071个文件,压缩后约389.4MB,包含428张jpg原始积水图像、428个txt标注文件、214个json标注文件及1个yaml配置文件;其中jpg为待分割原图,txt对应YOLO格式标注,json提供结构化区域与类别信息,yaml用于数据集路径与类别配置,三种格式可灵活切换使用。目前已有519人学习下载。数据集按segment目录组织,img、json、seg子文件夹分别存放原图与两类标注,结构清晰,便于直接接入YOLO系列训练流程,也可用于自定义分割模型的标注解析与格式转换练习,适合作为积水检测课题的基线数据。
1. 积水图像分割检测数据集:从标注掩码到 YOLO11 训练的第一公里
城市内涝、地下车库进水、隧道积水,这些场景的监控画面里,水面的边界往往模糊、反光、和湿滑地面几乎同色。用目标检测框去圈积水,框里一半是水一半是墙,后处理根本没法用。真正能落地的是图像分割:给每个像素判定「是不是积水」,输出一张和原图等大的掩码。积水图像分割检测数据集就是为这件事准备的原料,它提供原图与对应的分割标注,可以直接喂给 YOLOv8-seg、YOLO11-seg 这类实例分割模型做训练。这篇文章面向已经会用 Python、想把手头积水数据跑通分割训练的人,从数据格式、环境配置、训练参数一路讲到踩坑排查。如果你手上只有一堆积水照片、还没想清楚标注怎么转成 YOLO 能吃的格式,或者跑起来 loss 不降、掩码全黑,那接下来的内容基本能对上你的问题。
2. 积水分割数据集长什么样:掩码格式、目录结构与选型判断
拿到一个分割数据集,第一件事不是急着训练,而是搞清楚它的标注到底以什么形式存在。积水分割的标注常见有三种:PNG 单通道掩码(像素值 0/1 或 0/255)、COCO 格式的 polygon JSON、以及 LabelMe 导出的 JSON。这三种决定了你后面转换脚本怎么写,也决定了能不能直接用 YOLO 官方的那套数据加载逻辑。
2.1 三种标注格式的差异与转换代价
PNG 掩码是最省事的。每张图对应一张同名的灰度图,白色区域是积水,黑色是背景。YOLO 的分割训练最终需要的是 polygon 坐标(归一化后的多边形点集),所以 PNG 掩码要先用轮廓提取转成多边形。这条路的好处是标注质量直观、可视化方便;坏处是如果掩码边缘有锯齿,提取出来的多边形点数会爆炸,一个目标几千个点,训练时显存和速度都受影响。
COCO polygon JSON 是另一种常见形态,annotations 里直接存 segmentation 数组。它和 YOLO 的差距主要在坐标系和归一化方式:COCO 用绝对像素坐标,YOLO 用相对图像宽高的 0~1 浮点数。转换时除以宽高即可,但要注意 COCO 的 polygon 可能是多段(一个目标多个不连通区域),YOLO 每行只接受一个多边形,多段需要拆成多行或者只保留最大轮廓。
LabelMe JSON 最灵活,支持多边形、矩形、圆形混合标注,但字段结构最杂。转 YOLO 时要遍历 shapes,按 label 过滤出积水类别,再把 points 归一化。
| 格式 | 存储方式 | 转 YOLO 难度 | 典型问题 |
|---|---|---|---|
| PNG 掩码 | 单通道灰度图 | 中,需轮廓提取 | 边缘锯齿导致点数过多 |
| COCO JSON | 单文件 polygon | 低,除以宽高 | 多段 polygon 需拆分 |
| LabelMe JSON | 每图一个 JSON | 中,字段需过滤 | 混入非积水标签 |
选型上,如果你是从零标注,我一般建议直接标 polygon 存 COCO 或 LabelMe,别走 PNG 掩码再反提取,那一步的精度损失和点数膨胀是纯亏。如果数据集已经给的是 PNG 掩码,那就老老实实写轮廓提取,后面用 approxPolyDP 做抽稀。
2.2 目录结构:YOLO 分割训练要求的布局
YOLO 系列(v8 和 11 通用)对分割数据集要求固定的目录布局,images 和 labels 平行,labels 里是 txt,每行格式为:
<class_id> <x1> <y1> <x2> <y2> ... <xn> <yn>坐标全部是相对图像宽高的 0~1 浮点数,类别 id 从 0 开始。一个能直接开训的结构是这样:
datasets/ water/ images/ train/ a001.jpg val/ a002.jpg labels/ train/ a001.txt val/ a002.txt注意 images 和 labels 下的子目录名必须一致(train/val),文件名(不含扩展名)必须一一对应。YOLO 加载时是按文件名去 labels 里找同名 txt,找不到就当作负样本跳过,不会报错——这也是很多人「训练正常但模型啥也学不到」的隐形原因。
2.3 用脚本把 PNG 掩码转成 YOLO 分割标签
假设你手上是 PNG 掩码,下面这段脚本做两件事:提取轮廓、抽稀、归一化、写 txt。依赖 opencv 和 numpy。
import cv2 import numpy as np import os from pathlib import Path def mask_to_yolo(mask_path, img_w, img_h, class_id=0, epsilon_ratio=0.002): # 读单通道掩码,二值化 mask = cv2.imread(str(mask_path), cv2.IMREAD_GRAYSCALE) _, binary = cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY) # 找外轮廓,只取最外层 contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) lines = [] for cnt in contours: # 面积太小的噪点直接丢,阈值按图像尺寸调 if cv2.contourArea(cnt) < 50: continue # 抽稀:epsilon 越大点越少,0.002 是经验起点 eps = epsilon_ratio * cv2.arcLength(cnt, True) approx = cv2.approxPolyDP(cnt, eps, True) # 少于 3 个点构不成多边形 if len(approx) < 3: continue pts = approx.reshape(-1, 2).astype(float) # 归一化到 0~1 pts[:, 0] /= img_w pts[:, 1] /= img_h coords = " ".join(f"{x:.6f} {y:.6f}" for x, y in pts) lines.append(f"{class_id} {coords}") return lines def convert_dir(mask_dir, img_dir, out_dir): os.makedirs(out_dir, exist_ok=True) for mask_path in Path(mask_dir).glob("*.png"): img_path = Path(img_dir) / f"{mask_path.stem}.jpg" if not img_path.exists(): continue img = cv2.imread(str(img_path)) h, w = img.shape[:2] lines = mask_to_yolo(mask_path, w, h) with open(Path(out_dir) / f"{mask_path.stem}.txt", "w") as f: f.write("\n".join(lines)) convert_dir("masks/train", "images/train", "labels/train")逻辑说明:findContours用RETR_EXTERNAL只取外轮廓,避免内孔产生多余多边形;approxPolyDP的 epsilon 是抽稀强度,按周长比例给,比固定像素值更适应不同尺寸目标;归一化用图像真实宽高,不是掩码宽高——两者通常一致,但如果掩码被缩放过分,这里就会错位。参数上,epsilon_ratio从 0.002 起调,点数还是太多就加到 0.005,边缘明显变方就降到 0.001。面积阈值 50 是过滤小噪点,积水场景里雨滴反光容易产生碎斑,这个值可以适当放大到 100。
2.4 转换后必须做的两步校验
转完不要直接开训,先做两件事。第一,用可视化脚本把 txt 画回图上,肉眼确认多边形贴合水面边界。第二,统计每张图的标签行数和点数分布,如果某张图有几百个多边形,基本是掩码噪点没清干净,回去调面积阈值。这两步花十分钟,能省掉后面几小时的无效训练。
3. YOLO11 分割训练环境配置与最小可跑命令
环境这块是新手翻车最集中的地方。YOLO11 和 YOLOv8 同属 ultralytics 库,安装方式一样,但 YOLO11 的预训练权重文件名不同(yolo11n-seg.pt 这种),别拿 v8 的权重去套 11 的配置。下面按 Windows 和 Linux 通用的方式讲,Windows 安装 yolo11 的坑单独在避坑章说。
3.1 用 conda 建环境并装 ultralytics
不要用系统 Python 直接 pip install,依赖冲突会让你怀疑人生。建一个干净环境:
conda create -n yolo11 python=3.10 -y conda activate yolo11 # 装 PyTorch,按你的 CUDA 版本选,这里以 CUDA 12.1 为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 # 装 ultralytics,它会带上 opencv、numpy 等依赖 pip install ultralytics逻辑说明:Python 3.10 是目前 ultralytics 兼容性最稳的版本,3.12 偶尔有包编译问题。PyTorch 一定要先装、且和 CUDA 版本对齐,装完用python -c "import torch; print(torch.cuda.is_available())"验证,输出 True 才算通。ultralytics 放最后装,让它自己解析剩余依赖。参数上,如果你没有 NVIDIA 显卡,把 torch 那行换成 CPU 版(去掉 index-url),训练会慢但能跑通流程。
3.2 写数据集配置文件 water.yaml
YOLO 训练靠一个 yaml 描述数据位置和类别。放在项目根目录:
path: ./datasets/water train: images/train val: images/val names: 0: water逻辑说明:path是数据集根目录,train和val是相对 path 的子路径,指向 images 下的目录,YOLO 会自动去同级的 labels 找标签。names是类别映射,积水单类就写 0: water,多类(比如积水、湿滑、反光)按顺序加。注意 yaml 里路径用正斜杠,Windows 下反斜杠会被当转义符。
3.3 一条命令启动 YOLO11 分割训练
最小可跑命令:
yolo segment train model=yolo11n-seg.pt data=water.yaml epochs=100 imgsz=640 batch=8 device=0逻辑说明:segment train是分割任务子命令,别写成 detect。model=yolo11n-seg.pt是 nano 版分割预训练权重,第一次跑会自动下载。data指向刚写的 yaml。epochs=100是迭代轮数,积水数据集通常几百到几千张,100 轮起步。imgsz=640是输入分辨率,分割任务对分辨率敏感,小目标多就上 1024。batch=8按显存调,8G 显存跑 640 大概能到 8~16。device=0指定第一块 GPU,CPU 训练去掉这个参数。
跑起来后终端会打印每轮的 box_loss、seg_loss、mask mAP。分割任务重点看 seg_loss 和 mask mAP50,box 指标只是辅助。如果 seg_loss 前几轮不降反升,先别慌,分割的收敛比检测慢,看 10 轮趋势。
3.4 训练参数里真正影响积水分割效果的几个
默认参数能跑,但积水场景有几个参数值得动。imgsz前面说了,水面边界细,分辨率低了掩码糊成一片。mosaic默认 1.0,做数据增强拼接四张图,对分割有好处但会让小目标更小,积水面积普遍不小,保持默认即可。copy_paste是分割专属增强,把目标抠出来贴到别的图上,积水场景可以开到 0.3 增加样本多样性。overlap_mask控制训练时掩码是否允许重叠,单类积水保持 True。学习率lr0默认 0.01,如果 loss 震荡厉害降到 0.001。
4. 从训练日志判断积水分割是否真的在学
训练跑起来不等于学对了。这一章讲怎么从日志和验证结果里读出模型状态,以及几个积水场景特有的判断方法。
4.1 看懂 seg_loss 和 mask mAP 的配合关系
YOLO 分割训练会同时输出分类损失、box 损失、seg 损失和对应的 mAP。判断模型是否在学,看两条曲线:seg_loss 应该整体下降并逐渐平缓,mask mAP50 应该整体上升。如果 seg_loss 降但 mask mAP 不涨,通常是过拟合或者验证集分布和训练集差太多。如果 seg_loss 一直高位震荡,检查标签是不是有问题——最常见的是多边形自交或者点数过少。
积水场景有个特点:水面边界本身模糊,标注时不同人画的边界能差十几个像素。这导致 mask mAP 的天花板不会太高,能到 0.7~0.8 就算不错,别拿检测任务的 0.9 去要求它。
4.2 用验证命令单独跑一遍并保存可视化结果
训练完或者训练中想单独验证:
yolo segment val model=runs/segment/train/weights/best.pt data=water.yaml imgsz=640 save_json=True plots=True逻辑说明:val子命令加载指定权重在验证集上跑。save_json=True会把预测结果存成 COCO 格式 json,方便后续算指标或对比。plots=True生成混淆矩阵、PR 曲线等图,存在 runs 目录下。跑完重点看runs/segment/val/下的 val_batch0_pred.jpg,那是模型预测掩码叠在原图上的可视化,一眼能看出漏检和误检。
4.3 积水分割特有的失败模式:反光、倒影、湿地面
普通分割任务错就是错,积水分割的错有规律。反光:水面反射天空或灯光,模型容易把亮斑判成非水。倒影:积水里的建筑倒影,颜色和真实建筑接近,模型可能把倒影区域也标成积水(其实那确实是水,但标注时可能没标)。湿地面:刚下过雨的地面是湿的但没积水,颜色和浅积水极像,这是误检重灾区。
针对这几种,验证时单独挑出对应样本看预测。如果反光误判多,训练时加亮度扰动的增强;湿地面误检多,说明数据集里负样本(湿地面无积水)不够,得补。
4.4 用预测命令快速看单张图效果
不想跑整个验证集,单张图快速看:
yolo segment predict model=runs/segment/train/weights/best.pt source=test_imgs/ imgsz=640 save=True conf=0.25逻辑说明:predict子命令对 source 目录下所有图做推理,save=True存可视化结果到 runs/segment/predict。conf=0.25是置信度阈值,积水分割建议从 0.25 起,太低会出一堆碎掩码,太高会漏掉边界模糊的水面。这个命令适合调阈值阶段反复跑。
5. 积水分割训练避坑:从环境到标签的 5 个血泪记录
这一章全是实际踩过的坑,每条按现象、原因、解决写。新手照着排查能省大量时间。
5.1 Windows 下装 ultralytics 报 DLL 加载失败
现象:pip install 成功,但 import ultralytics 或跑训练时报OSError: [WinError 126] 找不到指定的模块,通常指向 torch 的某个 dll。
原因:Windows 上 PyTorch 的 CUDA 运行时依赖没装全,或者 conda 环境里混了多个 torch 版本。另一个常见原因是缺 Visual C++ Redistributable。
解决:先确认只装了一个 torch(pip list | findstr torch),多了就卸干净重装。然后装最新的 VC++ 运行库。还不行就换 CPU 版 torch 先跑通流程,确认是 CUDA 依赖问题再针对性修。Windows 安装 yolo11 的坑九成出在 torch 和 CUDA 版本不匹配上。
5.2 训练正常但 mask mAP 一直是 0
现象:loss 在降,box mAP 有值,但 mask mAP 始终 0 或者极低。
原因:标签格式不对。最常见的是 txt 里坐标没归一化(还是像素值),或者每行开头少了 class_id,或者多边形点数少于 3。YOLO 对格式错误不报错,直接当无效标签跳过。
解决:随便打开一个 labels 下的 txt,确认每行是0 x1 y1 x2 y2 ...,坐标都在 0~1 之间。写个脚本统计所有 txt 的行数和坐标范围,超出 0~1 的就是没归一化。
5.3 显存爆了但 batch 已经调到 1
现象:CUDA out of memory,batch 降到 1 还是爆。
原因:imgsz 太大,或者某个样本的多边形点数极多导致单张图计算量暴涨。分割任务的显存占用和多边形总点数强相关,不是只看 batch。
解决:先把 imgsz 从 1024 降到 640 试。还爆就回去检查标签,把点数超过 500 的多边形用 approxPolyDP 再抽稀。另外cache=True会把整个数据集缓存到内存/显存,数据大时关掉。
5.4 验证集指标好但实际推理一塌糊涂
现象:val 的 mask mAP 0.8,拿真实监控图去预测,掩码乱七八糟。
原因:训练集和真实场景分布不一致。数据集里的积水图可能是特定角度、特定天气,真实监控是另一个视角。或者验证集本身和训练集同源,没有真正独立的测试集。
解决:从真实场景单独切一批图做测试,别用验证集的结果下结论。如果差距大,要么补真实场景数据进训练集,要么做域适应增强(色调、视角、亮度扰动)。
5.5 训练中断后想接着跑结果从头开始
现象:跑了 50 轮断了,重新执行训练命令,loss 从初始值开始,之前的白跑。
原因:没指定 resume。YOLO 默认每次训练新建一个 run 目录,不会自动续。
解决:训练时加resume=True并指向上次的 last.pt:yolo segment train resume=True model=runs/segment/train/weights/last.pt。注意 resume 要求其他参数和上次一致,改了 imgsz 或 data 会报错。养成习惯:长训练前确认 last.pt 会正常保存,默认每轮都存。
6. 把积水分割模型推到可用:阈值调优与掩码后处理技巧
训练出 best.pt 只是半成品,真正上线前还有两步:置信度阈值调优和掩码后处理。这两步做得好,同样的模型效果能上一个台阶。
6.1 按场景调 conf 和 iou 阈值
YOLO 分割推理有两个阈值:conf控制哪些检测保留,iou控制重叠掩码的合并(NMS)。积水场景的调法:
| 场景 | conf | iou | 理由 |
|---|---|---|---|
| 监控远景,积水面积大 | 0.3~0.4 | 0.5 | 高 conf 滤掉反光误检 |
| 近景,边界要求精细 | 0.15~0.25 | 0.6 | 低 conf 保住模糊边界 |
| 夜间/低照度 | 0.2 | 0.5 | 中间值,配合亮度增强 |
调的时候别凭感觉,写个循环跑不同阈值组合,统计 mask mAP 或直接看可视化。我一般固定 iou=0.5,只扫 conf 从 0.1 到 0.5,步长 0.05,挑误检和漏检平衡的那个点。
6.2 掩码后处理:去碎斑和填孔洞
模型输出的掩码常有小碎斑(几个像素的孤立区域)和孔洞(水面中间被误判成背景)。后处理两步:
import cv2 import numpy as np def clean_mask(mask, min_area=100, close_kernel=5): # mask 是 0/1 的 uint8 mask = mask.astype(np.uint8) # 形态学闭运算填小孔洞 kernel = np.ones((close_kernel, close_kernel), np.uint8) mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) # 连通域分析,去掉小面积碎斑 num, labels, stats, _ = cv2.connectedComponentsWithStats(mask, connectivity=8) cleaned = np.zeros_like(mask) for i in range(1, num): if stats[i, cv2.CC_STAT_AREA] >= min_area: cleaned[labels == i] = 1 return cleaned逻辑说明:闭运算先填内部小孔洞,kernel 大小按掩码分辨率调,640 输入用 5 就够。连通域分析按面积过滤,min_area是保留阈值,监控场景积水面积大,可以设到 200~500。参数上,close_kernel太大会把两个相邻积水区域粘一起,太小填不上孔洞,5 是 640 分辨率的稳妥值。
6.3 一个容易被忽略的验证习惯
我现在的习惯是:每次调完阈值或后处理,固定拿同一批 20 张「难样本」(反光、湿地面、夜间)跑一遍,把结果拼成对比图存下来。这样改动的效果是变好还是变差,一眼能看出来,不靠记忆。积水分割这活儿,模型指标只是参考,最终看的是那几张最难图上掩码贴不贴边。希望帮到你。
本文还有配套的精品资源,点击获取