
简介这是一份面向目标检测学习与室内积水场景识别任务的数据集包含357张已标注jpg图片配套Pascal VOC与YOLO两种格式标注文件类别为“jishui”共378个矩形框适合用于训练积水检测模型或作为YOLO、Faster R-CNN等算法实战数据。压缩包共1073个文件主体为357个jpg图片及对应的357个xml、357个txt标注另有个别txt文件整体大小84.65MB目录结构简洁可直接划分训练集。标注工具为labelImg画框规则统一边界框质量稳定。已有216人学习下载尤其适合具备一定深度学习基础、需要中小规模专用数据集进行模型验证或课程设计的学生及开发者。数据集中每张原始图片均提供VOC与YOLO双格式标签省去格式转换时间便于快速接入主流检测框架同时附带明确的标注统计与类别说明方便评估数据分布与训练效果。1. 室内积水检测357张图的小样本怎么做出能用的模型室内房间积水检测听起来是个冷门方向但它背后连着一条很实际的业务线物业巡检、机房漏水预警、仓库进水报警、老旧小区地下室防汛。这类场景的共同特点是“不能等人发现”得靠摄像头自动盯着地面有没有异常反光或水渍扩散。目标检测模型落在这种场景上本质是在做一件事——从正常地板纹理里把“不该出现的东西”框出来。积水不是固态目标它没有固定形状、没有纹理、还会反射天花板的灯光这让它在检测任务里比检测一个杯子、一辆车要麻烦得多。拿到手的这份标题指向的是357张室内房间积水数据的VOC和YOLO双格式样本集单从数量看属于典型的小样本数据集。357张图放到YOLOv8里跑连一个完整的训练轮次都喂不饱模型但这不意味着这个数据集没价值。小样本数据集的正确打开方式是先盘清标注质量再做格式适配然后用迁移学习加数据增强去榨干每一张图的潜力。这篇笔记就从数据盘点、格式转换、训练配置、踩坑记录到验证技巧把这条链路完整捋一遍。适合谁来读手里正好要做漏水检测、地面异常检测的算法工程师拿到一个小样本数据集不知道怎么下手的初学者还有想评估“357张能不能落地”这个问题的决策者。看完你应该能判断出这个数据集值不值得用于你的项目以及如果要用第一步该做什么。2. 数据资产盘点解析VOC与YOLO双格式的构成逻辑2.1 文件结构与标注格式的对应关系拿到压缩包别急着解压丢进训练脚本。第一步先做静态盘点搞清楚这个数据集长什么样。常见做法是先看目录结构VOC格式和YOLO格式通常是两套并行的文件夹也可能是一套源文件加一套转换后的文件。VOC格式的标准结构是Annotations目录放XML标注文件JPEGImages目录放原图。而YOLO格式的标准结构是images目录放图labels目录放TXT标注文件。标题既然写着“VOCYOLO格式”大概率是下面两种组织方式之一# 方式AVOC和YOLO各自独立成套 dataset/ ├── VOC/ │ ├── Annotations/ # 357个XML文件 │ └── JPEGImages/ # 357张JPG图片 └── YOLO/ ├── images/ # 357张JPG图片 └── labels/ # 357个TXT文件# 方式B共享图片标注文件分开放 dataset/ ├── images/ # 357张JPG图片 ├── Annotations/ # VOC格式XML标注 └── labels/ # YOLO格式TXT标注拿到手先用一条命令把目录树打出来比任何说明文档都直观find . -type d | sort # 同时统计各类文件数量确认357张图对应357个标注 ls VOC/Annotations | wc -l ls YOLO/labels | wc -l文件数量对不上要立刻排查。图片比标注多说明存在漏标样本标注比图片多可能是重复标注或冗余文件残留。这种数量校验看起来基础但恰恰是小样本数据集最要命的环节——357张图里如果有5张标注错乱就是丢掉1.4%的有效样本这比例在千级数据集里可以忽略在百级数据集里会直接影响训练结果。2.2 标注质量检查XML和TXT里到底存了什么格式对得上只是第一步标注内容对不对才是决定模型上限的关键。VOC格式的XML标注文件核心内容是object节点一个目标对应一个节点里面包含类别名和bndbox边界框坐标。看一眼内容就能判断标注粒度annotation folderJPEGImages/folder filenameroom_floor_023.jpg/filename size width1920/width height1080/height depth3/depth /size object namewater_accumulation/name bndbox xmin432/xmin ymin298/ymin xmax867/xmax ymax546/ymax /bndbox /object /annotationYOLO格式的TXT则是归一化坐标每行代表一个目标类别ID x_center y_center width height所有值都在0到1之间。两种格式的转换逻辑后面细讲静态检查阶段重点看三件事。第一类别名是否统一。XML里name字段如果出现water、Water、water_puddle混用说明标注过程没有严格执行类别规范这需要写脚本统一。第二边界框是否越界。YOLO格式的TXT文件里如果x_center width/2 1或者出现负数说明坐标值超出了图像边界训练时会被YOLO当噪声处理。第三真正小的目标有多少。积水的特点决定了框有可能很扁、很扁——覆盖在地面上的水迹往往是长条形。统计一下所有标注框的宽高比分布如果长宽比超过10:1的框占比很高训练时要注意anchor尺寸的适配。这个数据直接决定后续训练配置怎么调。import os import xml.etree.ElementTree as ET xml_dir VOC/Annotations aspect_ratios [] box_areas [] for xml_file in os.listdir(xml_dir): tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() for obj in root.findall(object): 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) w xmax - xmin h ymax - ymin aspect_ratios.append(w / h if h 0 else 0) box_areas.append(w * h) print(f平均宽高比: {sum(aspect_ratios)/len(aspect_ratios):.2f}) print(f最大宽高比: {max(aspect_ratios):.2f}) print(f框面积小于图像面积1% 的占比: {sum(1 for a in box_areas if a 1920*1080*0.01)/len(box_areas)*100:.1f}%)这段代码能快速评估积水样本的几何特性。平均宽高比偏离1越多说明积水检测越偏向“细长目标”这对anchor配置和损失函数里的box损失权重都有影响。框面积占比的统计则用于判断是否存在大量小目标如果小目标占比高推理阶段就需要调低置信度阈值并适当放大输入分辨率不然小片积水很容易被漏检。2.3 场景覆盖评估357张图分布在哪里静态检查的最后一步是看场景覆盖但这一步不能只看文件得配合可视化。一个实用技巧是随机抽样9到16张图拼成一张网格图人眼扫一遍就能对数据分布建立直觉。检查重点有几个方向光照条件是否多样——室内积水对光照极其敏感同一天的不同时段积水反光的强度和面积完全不同地面材质是否覆盖多种类型——瓷砖、木地板、水泥地、环氧地坪每种材质上积水的视觉特征都不一样拍摄视角是否统一——固定摄像头俯拍和手持设备平拍模型学到的特征完全不同负样本是否充足——没有积水的正常地面图片如果太少模型会倾向于把反光地板误判为积水。这些评估结果直接决定训练策略。如果发现场景单一中期训练时要把数据增强里的亮度扰动、对比度扰动、高斯噪声的强度调大用模拟多样化来弥补采集多样性的不足。提示静态盘点应该产出一份记录写清楚图片数量、标注数量、类别数量、坐标异常情况、场景覆盖结论。这份记录就是后续训练的决策依据比模型训练出来的mAP曲线更能说明数据底子。3. 从VOC到YOLO转换脚本与四个边界坑3.1 坐标归一化与路径映射的完整转换脚本VOC的XML坐标是像素绝对值YOLO的TXT坐标是相对图像的归一化值。转换公式不复杂x_center (xmin xmax) / 2 / width y_center (ymin ymax) / 2 / height box_width (xmax - xmin) / width box_height (ymax - ymin) / height分子是边界框中心点和宽高在像素空间的值分母是图像的宽高这样每个值都被压缩到0到1区间。看下面这段完整的转换脚本把VOC的XML逐行解析后写为YOLO格式的TXTimport os import xml.etree.ElementTree as ET from pathlib import Path def convert_voc_to_yolo(xml_path, output_dir, class_names): xml_path: VOC标注XML文件路径 output_dir: YOLO标签输出目录 class_names: 类别名到ID的映射字典如 {water_accumulation: 0} tree ET.parse(xml_path) root tree.getroot() # 从XML里读图像尺寸这是坐标归一化的分母 size root.find(size) img_width int(size.find(width).text) img_height int(size.find(height).text) yolo_lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_names: continue # 跳过未定义的类别 class_id class_names[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) # 核心转换公式注意所有坐标都用float计算 x_center (xmin xmax) / 2 / img_width y_center (ymin ymax) / 2 / img_height box_w (xmax - xmin) / img_width box_h (ymax - ymin) / img_height yolo_lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) # 输出TXT文件名与XML文件名保持一致只是扩展名不同 txt_name Path(xml_path).stem .txt output_path os.path.join(output_dir, txt_name) with open(output_path, w) as f: f.write(\n.join(yolo_lines)) class_names {water_accumulation: 0} # 单类别场景ID固定为0 for xml_file in os.listdir(VOC/Annotations): if xml_file.endswith(.xml): convert_voc_to_yolo( os.path.join(VOC/Annotations, xml_file), YOLO/labels, class_names )这段脚本的逻辑分四层先解析XML拿到图像尺寸再遍历所有object节点取出类别名和坐标然后套归一化公式生成YOLO格式的文本行最后写入TXT文件。单类别场景下class_names只有一个键如果是多类别标注直接往字典里追加映射关系就行。3.2 边界坑一坐标越界与标签偏移转换脚本跑完一定要做反向校验。常见做法是把YOLO的TXT坐标映射回像素值画在原图上肉眼对比与原标注的差异。这个步骤很多人会跳过然后训练出来的模型边界框歪得离谱还找不到原因。越界问题出在两个环节。一是标注人员在打框时手滑把边界框拖出了图像边缘XML里记录了超出宽高的坐标值二是脚本处理时没有做clip操作导致归一化后出现大于1或小于0的值。YOLO训练时遇到这种越界坐标不会报错而是当成无效目标直接忽略表现为某个类别训练了但recall始终起不来。处理办法是在转换时加一行clip操作# 归一化之前先把坐标限制在图像范围内 xmin max(0, min(xmin, img_width)) xmax max(0, min(xmax, img_width)) ymin max(0, min(ymin, img_height)) ymax max(0, min(ymax, img_height))对于边界框严重越界的情况比如框的中心点已经在图像外clip并不能修复语义错误这种情况应该直接从训练集剔除。3.3 边界坑二类别ID映射不一致VOC用的是字符串类别名YOLO用的是整数ID。转换脚本里class_names的字典定义了映射关系这个字典必须和训练时的data.yaml里的类别列表保持一致。一个典型的翻车场景是数据集提供的YOLO标签里water对应ID 0而你自己训练时写的data.yaml把water放在了ID 1的位置上模型训练输出全部错位。检测系统在推理时显示的框位置是对的但类别标签全错。解决方式是在训练前写一个校验脚本读取所有TXT标签统计出现的类别ID集合再和data.yaml里的类别配置对比import os label_dir YOLO/labels class_ids set() for txt_file in os.listdir(label_dir): with open(os.path.join(label_dir, txt_file), r) as f: for line in f: class_id int(line.strip().split()[0]) class_ids.add(class_id) print(f标签中的类别ID: {sorted(class_ids)}) # 输出结果应该和data.yaml里的类别索引完全一致3.4 边界坑三数据集划分不当导致的类别偏移357张图划分训练集、验证集、测试集时最容易犯的错误是随机划分打乱了场景分布。比如某个房间的积水图全部进了训练集验证集只剩下另一个房间的图结果验证集损失居高不下模型表现波动剧烈。小样本数据集应该用按场景或按文件夹划分的策略而不是按文件随机划分。如果数据集里图片文件名带房间号或拍摄位置信息优先根据这个信息做分组划分确保同一个场景的图片不会同时出现在训练集和验证集。YOLO训练时datasets目录的常见组织方式如下datasets/ ├── data.yaml # 数据集配置 ├── train/ │ ├── images/ # 训练图片 │ └── labels/ # 训练标签 └── val/ ├── images/ # 验证图片 └── labels/ # 验证标签建议划分比例训练集280张验证集50张测试集27张。如果觉得验证集太小可以只分训练和验证测试阶段直接用独立采集的实时画面来验证。3.5 边界坑四图像分辨率不统一积水检测对图像分辨率很敏感。小片积水在1080p图上可能只占20×20像素缩放到640×640后只剩12×12像素再经过网络下采样到检测头时特征已经基本消失。检查数据集里所有图片的尺寸分布from PIL import Image import os from collections import Counter img_dir VOC/JPEGImages sizes Counter() for img_file in os.listdir(img_dir): with Image.open(os.path.join(img_dir, img_file)) as img: sizes[img.size] 1 print(sizes) # 输出示例: Counter({(1920, 1080): 300, (1280, 720): 57})如果尺寸不统一训练前要统一做letterbox处理。YOLO在训练时内部已经做了resize但标注坐标是相对于原始图像的如果自己预处理时resize了图片而没有同步缩放标注就会造成标签偏移。这个坑隐蔽在“预处理”环节很多人排查半天最后发现是自己resize之后没改标签。4. 小样本训练的完整方案迁移学习与增强策略4.1 基于YOLOv8的迁移学习配置357张图从零训练一个检测模型没有任何意义收敛不了也泛化不了。正确做法是在COCO预训练权重基础上做迁移学习用预训练模型已经学到的底层特征——边缘、纹理、颜色分布——去适配积水检测这个具体任务。先用ultralytics这个库搭建最小可运行环境pip install ultralytics # 验证安装成功能正常输出版本号 yolo version装好之后准备data.yaml这是数据集配置的核心文件# 数据集根路径使用相对路径配合训练脚本的位置 path: ./datasets/room_water train: train/images val: val/images # 类别定义顺序必须与标签文件中的ID一致 names: 0: water_accumulation然后是训练命令yolo detect train \ modelyolov8n.pt \ dataroom_water.yaml \ epochs150 \ imgsz640 \ batch16 \ lr00.005 \ augmentTrue \ patience20命令参数的含义拆开说modelyolov8n.pt表示加载YOLOv8n的预训练权重n是nano版本模型体积最小、显存占用最少适合小数据集跑迁移学习data指向刚才创建的data.yamlepochs150对小数据集来说不算多配合patience20也就是连续20轮验证集损失没有下降就自动停止lr00.005比默认值0.01减半因为小数据集下学习率太大容易过拟合imgsz640是标准输入尺寸如果积水目标小而多可以试imgsz960提升小目标召回。迁移学习的关键直觉是预训练权重在COCO上已经学会了区分“地面”和“物体”积水检测需要的是在这个基础上微调让模型关注到“地面上的异常反射区域”。因此骨干网络层的学习率应该比检测头低这在YOLOv8的默认配置里已经做了分层处理但可以手动调整得更激进# 在ultralytics配置中关闭骨干网络的部分冻结训练 freeze: 10 # 冻结前10层只训练后面的层freeze10会让骨干网络的低层特征保持不动只训练高层语义特征和检测头。对小数据集来说冻结部分骨干层是有效的防过拟合手段。4.2 数据增强策略小样本的后悔药357张图跑150轮训练过拟合是必然的增强策略是唯一的后悔药。YOLOv8自带的增强管线覆盖了大部分基础操作但对积水检测要针对性调整参数。先看默认增强相关的配置yolo detect train \ modelyolov8n.pt \ dataroom_water.yaml \ epochs150 \ imgsz640 \ batch16 \ hsv_h0.015 \ hsv_s0.7 \ hsv_v0.4 \ degrees10 \ translate0.1 \ scale0.5 \ fliplr0.5这些参数的调整逻辑很明确积水的颜色特征并不关键关键是反光区域的亮度对比和几何形状所以hsv_h色相扰动给得很保守只让颜色在小范围内变化而hsv_s饱和度扰动给到0.7模拟不同环境光照下积水饱和度的差异。degrees10表示只允许10度以内的小角度旋转因为固定摄像头的场景不会出现大角度旋转给太多旋转反而会让模型学到不真实的视角。fliplr0.5水平翻转概率这个保留默认就行。有一个积水检测特化的增强策略值得单独提在训练时用随机亮度扰动模拟天气变化。室内积水检测也会受到室外光线透过窗户的影响场景里光照可能会有渐变from ultralytics.data.augment import v8_transforms # 自定义亮度扰动增强模拟自然光照变化 # 在ultralytics的配置中可以通过设置hsv_v实现 # v0.4表示亮度扰动幅度提升到默认值的2倍4.3 从357张到可用模型完整的训练与评估流程把整个流程串起来核心命令如下# 第一步检查GPU可用性 nvidia-smi # 第二步启动训练保持后台运行方便观察 nohup yolo detect train \ modelyolov8n.pt \ dataroom_water.yaml \ epochs150 \ imgsz640 \ batch16 \ lr00.005 \ freeze10 \ hsv_v0.4 \ degrees10 \ scale0.5 \ patience20 \ projectroom_water_runs \ nameexp_v1 \ train_log.txt 21 # 第三步实时查看训练日志 tail -f train_log.txt训练过程中的监控要点前10轮重点看loss下降曲线是否平滑如果loss剧烈震荡说明学习率过高需要下调lr020轮以后开始看验证集的mAP50和mAP50-95指标小样本数据集以mAP50为准因为积水检测的定位精度要求没有那么苛刻训练结束后查看confusion_matrix.png确认是否存在严重的类别混淆。推理验证yolo detect predict \ modelroom_water_runs/exp_v1/weights/best.pt \ sourcetest_images/ \ conf0.25 \ iou0.5 \ saveTrue \ projectroom_water_runs \ nameinferenceconf0.25的置信度阈值对积水检测来说比较合理。积水目标缺乏明确的形状特征模型给出的置信度普遍低于检测刚性物体阈值太高会把真目标滤掉。这里有个权衡——检出的每个框后续要有人工复核漏检比误检危害更大所以阈值倾向保守。5. 常见问题排查积水检测数据与训练的避坑记录5.1 标注框错位VOC转YOLO后偏差肉眼可见现象转换后的YOLO标签画回原图发现边界框偏移框住的不是积水主体而是旁边墙面。原因XML里的size节点记录的图像尺寸和JPEG图片的真实尺寸不一致。典型的产生路径是标注工具自动读取了缩略图尺寸标注完成后原图被替换成了新版本。转换脚本信任了XML里的数值归一化时用错了分母。解决转换前先校验XML里的size是否与图片实际尺寸一致from PIL import Image import xml.etree.ElementTree as ET import os xml_dir VOC/Annotations img_dir VOC/JPEGImages mismatch_count 0 for xml_file in os.listdir(xml_dir): tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() xml_w int(root.find(size/width).text) xml_h int(root.find(size/height).text) img_file xml_file.replace(.xml, .jpg) with Image.open(os.path.join(img_dir, img_file)) as img: real_w, real_h img.size if (xml_w, xml_h) ! (real_w, real_h): mismatch_count 1 print(f{xml_file}: XML{xml_w}x{xml_h}, 实际{real_w}x{real_h}) print(f尺寸不匹配的标注数: {mismatch_count})这个脚本在转换前跑一遍能避免后面所有的坐标错位问题。5.2 验证集损失不降357张里的分布不均现象训练时训练集loss持续下降验证集loss前20轮就停止下降甚至反弹。原因验证集里包含训练集从未见过的场景——比如训练集中全是瓷砖地面验证集中恰好有木质地板模型没见过这种材质就输出高置信度误判。或者验证集本身图片过少只有20张评估结果的方差太大一两张难样本就把mAP拉下去了。解决没有万能办法但可以做三步缓解。第一步检查数据集划分是否按场景分组如果图片文件名能看出房间或区域信息直接按文件名前缀划分。第二步缩减训练集把和验证集场景相似的图片剔除出训练集防止信息泄漏带来的虚高指标。第三步也是最实用的一步把验证集并入训练集重新划分确保验证集至少有30张图且覆盖所有场景类型。如果因为图片太少无法做到则改用K折交叉验证来评估模型真实水平而不是只跑一次固定划分。5.3 推理阶段误检严重把反光地板当积水现象训练指标mAP50有0.85但把模型放到真实场景里测试发现抛光地板的倒影、阳光光斑、潮湿脚印都被框成积水。原因数据集的负样本——也就是没有积水的正常地面图片——数量严重不足。模型没有见过足够多“看起来像积水但不是积水”的样本只能靠反光和对比度来做判断自然会把所有高反光区域都当作目标。解决最好的办法是补充采集纯负样本图加入训练集并标注为空标签。YOLO支持空标签训练labels目录下的TXT文件为空文件即可。如果暂时无法采集先把推理时的conf阈值调高到0.4到0.5用置信度换准确率。这种做法是应急手段长线还是要扩充硬负样本。5.4 小目标积水漏检缩小后特征消失现象模型对大片积水检出效果很好但对角落里一小滩水完全没反应。原因原始图是1080p的大图积水目标如果在原图只有30×30像素经过缩放和多次下采样后特征图上的信息量不足检测头无法给出有效的分类置信度。解决两个方向一是提高输入分辨率把imgsz从640提到960或1280这会增加显存占用需要同步降低batch大小二是测试时使用TTA或滑窗检测但最直接的还是调整数据标注策略——训练时用原图裁剪出局部区域作为输入让模型在训练时看到的目标相对尺寸更大。6. 落地进阶用迁移学习二次微调和场景验证技巧学会用别人的357张只是开始落地到自己的场景时还要再做两件事。第一件事是从“验收集”转为“生产集”。训练集和验证集只能证明模型在已有数据上work不能证明它在新场景里work。做法很简单拿自己的手机到实际部署位置拍20到30张真实画面要求覆盖不同时间段的光照变化和地面状态手动标注后替换掉原验证集重新跑yolo detect val看模型在新场景的mAP掉了多少。这个操作本质是把数据集迁移到目标域的过程也是判断这套3537张样本是否值得复用的验收标准。第二件事是二次微调策略。假设你拿到了自己的新数据比如50张现场真实积水图不要单独训练新模型而是把新数据和原357张合在一起微调。这里有个时间顺序的细节——先用原357张训练得到best.pt再用best.pt作为预训练权重放入自己的新数据继续训练而不是直接拿COCO权重训练。因为COCO里没有积水这样的目标中间多一步原数据域的过渡模型学到的特征会更贴近“地面异常检测”这个任务。用自己的数据二次微调时要适当降低学习率yolo detect train \ modelroom_water_runs/exp_v1/weights/best.pt \ datamy_room_water.yaml \ epochs50 \ imgsz640 \ batch8 \ lr00.002 \ lrf0.01 \ freeze5lr00.002降到初始训练的一半freeze5只冻结最底层的5层特征提取网络让高层特征和检测头更充分地适应新数据分布。训练完成后还有一个容易忽略的验证细节——在推理时把检测结果连同置信度一起输出成可视化的对比图或小视频。把一天中不同时段捕捉到的画面串起来观察模型在不同光照条件下的检出率波动这样比只看mAP曲线更能判断模型是否真的能投入生产。这357张数据集的真实价值不在于直接跑出一个高精度模型而在于它给后续迁移和微调提供了一个扎实的起点。最后说一个我自己的实践习惯训练记录里除了当次实验的曲线、指标、参数再补一句文字描述写清楚这批数据是从哪个渠道获取、做过哪些清洗步骤。几周后再回来分析模型问题时这句话能省下大量排查时间。希望帮到你。本文还有配套的精品资源点击获取