简介厨房积水检测数据集面向智能家居、商用厨房安全监测及清洁机器人等场景提供88张真实厨房环境的积水与水渍图像适用于需要训练foam泡沫与water积水两类目标检测模型的算法工程师和研究人员。数据集同时提供Pascal VOC格式的xml标注和YOLO格式的txt标注每张jpg对应一套标注文件不含分割路径文本类别共2类合计562个矩形框其中foam标注272个、water标注290个标注工具为labelImg画框规则一致。资源包共268个文件以jpg原图、xml标注文件和yolo格式txt标注文件为主体另含少量ini配置信息压缩后约18.23MB体量轻便便于下载、解压后直接开展模型训练或数据格式校验。目前已有161人学习下载数据由人工逐一标注并经过合理校验可直接作为YOLO、Faster R-CNN等目标检测算法的训练与验证数据尤其适合快速搭建厨房积水检测原型减少数据采集和标注格式转换的时间成本。1. 厨房积水检测数据集88张图能训练出什么先泼盆冷水做厨房积水检测很多人第一反应是“这场景简单不就一块地板嘛”。但真上手你会发现积水反光、地砖纹理、灯光色温、拖把留下的水痕全是让模型误判的坑。这个“厨房积水检测数据集VOCYOLO格式88张2类别”里88张图、2个类别意味着它不是给你训一个生产级模型的而是让你把“积水检测”这条链路跑通的最小样本。VOC格式负责标注与验证YOLO格式负责直接喂给训练脚本两者都齐省掉你写转换脚本的时间。这个标题适合谁适合两类人一类是刚接触目标检测、想用现成数据把YOLOv5/v8训练流程完整走一遍的初学者另一类是手里有智能家居、商用厨房监控项目想快速验证“积水检测”这个需求到底能不能用视觉方案落地、需要多少数据量级的工程师。至于指望88张图训出99%mAP的我劝你直接放弃这个数据集的正确用法是“跑通流程 摸清坑”不是“直接上线”。2. 两类标注分别是什么VOC管验证YOLO管训练别混着用2.1 VOC格式是“中间态”不是给你直接训YOLO的VOC格式的核心是每个图像对应一个同名XML文件里面用object标签框出目标name写类别名bndbox写xmin/ymin/xmax/ymax坐标。它的优点是结构清晰、适合用LabelImg这类工具二次检查标注质量很多开源评估脚本比如mAP计算工具也默认读VOC格式。这个数据集里VOC那份的真正价值在于你可以打开XML肉眼核对框有没有标歪、有没有漏标而不是上来就闷头训练。annotation folderimages/folder filenamekitchen_001.jpg/filename size width640/width height480/height depth3/depth /size object namewater/name bndbox xmin120/xmin ymin85/ymin xmax310/xmax ymax240/ymax /bndbox /object /annotation上面这段XML只示意了单类别单目标的结构。逻辑说明filename必须和图片文件名完全一致训练脚本靠这个字段把图和标注关联起来name就是类别名待会儿转YOLO格式时这个字符串会被映射成整数IDbndbox四个值必须是整数像素坐标且xmin不能大于xmax。参数说明如果同一张图里有多个目标就复制多个object块每个块一组坐标。2.2 YOLO格式是“训练态”归一化坐标最容易写错YOLO格式的标注文件是和图片同名的TXT文件每行五个数字class_id x_center y_center width height。这里的坐标全部是归一化后的即除以图片宽高值域在0到1之间。这是新手最容易翻车的地方——有人直接把VOC的像素坐标填进去模型训练直接loss爆炸或完全不收敛。import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, img_w, img_h, class_map): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_map: continue bndbox obj.find(bndbox) xmin int(bndbox.find(xmin).text) ymin int(bndbox.find(ymin).text) xmax int(bndbox.find(xmax).text) ymax int(bndbox.find(ymax).text) x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h lines.append(f{class_map[name]} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) return lines # 用法示意class_map {water: 0, person: 1} # 图片宽高必须从对应图片读取不能写死这段脚本逻辑说明先解析XML取坐标再换算成归一化的中心点坐标和宽高。注意img_w和img_h必须来自实际图片尺寸而不是标注里的size字段——虽然两者通常一致但有些标注工具会写错。参数说明class_map里的ID顺序就是你训练时的类别顺序YOLO的类别编号从0开始不是从1。如果你把这个顺序搞反了训练出来的模型预测结果会张冠李戴。2.3 两份标注的关系以VOC为基准YOLO可再生成拿到这个数据集后我建议的检查顺序是先看VOC的XML有没有问题再写脚本用XML重新生成YOLO的TXT而不是直接信任压缩包里给的TXT。因为TXT是纯文本肉眼很难检查坐标对不对XML有结构相对好验证。常见做法是写个批处理脚本遍历整个Annotations目录一次性重新生成labels目录顺便做一轮坐标合法性检查——比如宽度或高度小于0、中心点超出图片边界这类明显错误。import os from PIL import Image ann_dir Annotations img_dir JPEGImages label_dir labels os.makedirs(label_dir, exist_okTrue) for xml_name in os.listdir(ann_dir): if not xml_name.endswith(.xml): continue base xml_name.replace(.xml, ) img_path os.path.join(img_dir, base .jpg) img Image.open(img_path) w, h img.size lines voc_to_yolo(os.path.join(ann_dir, xml_name), w, h, {water: 0, person: 1}) with open(os.path.join(label_dir, base .txt), w) as f: f.write(\n.join(lines))这段代码的逻辑说明遍历XML、用真实图片尺寸做归一化、写出TXT。核心价值是保证YOLO标注不是“二手货”而是从VOC一手生成的这样至少格式层面不会有隐藏错误。参数说明如果你的图片不是jpg扩展名记得改base .jpg为实际扩展名否则图片打不开训练直接报错。3. 用88张图训YOLOv8从数据集划分到首次训练的最小命令3.1 数据集目录结构YOLO只认这个布局YOLOv5/v8的官方训练脚本对数据集目录有约定俗成的要求虽然可以用配置文件指定路径但最稳妥的做法是遵守它的默认结构。常见结构如下kitchen_water/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/图片和标注文件要严格同名、同目录层级。比如images/train/kitchen_001.jpg对应的标注必须是labels/train/kitchen_001.txt。这里有个细节images/train和labels/train下的文件名要一一对应缺一个标注文件训练时那张图会被跳过但不报错——这也是个隐蔽的坑你以为数据全用了实际可能丢了几张。检查方法是训练完看日志里的images计数和实际文件数是否一致。划分比例上88张图我建议按7:2:1切也就是训练集61张、验证集18张、测试集9张左右。因为总量太少验证集和测试集如果太大训练集不够学太小又评估不准。你也可以做简单的K折交叉验证来弥补数据量不足但对YOLO训练来说成本偏高不如先把单次流程跑通。3.2 数据配置YAML类别ID必须和标注对应YOLO训练需要的YAML文件核心就三部分路径、类别数、类别名。路径可以是绝对路径也可以是相对于YAML文件所在目录的相对路径——建议用相对路径加..的方式方便整个数据集目录挪动。# kitchen_water.yaml path: ../kitchen_water train: images/train val: images/val test: images/test nc: 2 names: [water, person]逻辑说明path是数据集根目录train/val/test是相对path的子目录路径nc必须和names列表长度一致。参数说明类别顺序[water, person]意味着标注里ID 0是积水、ID 1是人你和VOC转YOLO时的class_map必须完全一致——如果转标注时用的{water: 0, person: 1}但这里写成[person, water]那模型学出来的东西就是错的而且很难发现因为loss照样下降。3.3 训练命令与关键参数batch和imgsz决定显存训练命令本身不复杂真正的门道在参数选择上。对于一个88张的小数据集最重要的是防止过拟合模型不能太大、训练轮数不能太多、数据增强要克制。yolo detect train \ datakitchen_water.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch8 \ patience30 \ optimizerAdamW \ lr00.001 \ augmentFalse命令参数说明modelyolov8n.pt选nano版本参数量最小适合小数据集你选s或m纯属给自己找麻烦epochs100在小数据集上通常够了但建议配合patience30让它在验证集指标不再提升时自动早停augmentFalse这条值得单独说——YOLO默认开一堆增强比如随机翻转、颜色抖动、平移缩放在正常数据量下是好东西但在只有61张训练图的情况下增强过度会让模型学不到稳定特征。逻辑说明训练过程中模型会输出每个epoch的box_loss、cls_loss以及验证集的mAP50和mAP50-95。你要重点盯的是mAP50——它衡量的是预测框和真实框的IoU超过0.5就算中的情况。而mAP50-95更严格要求IoU从0.5到0.95逐步提高并求平均值。88张数据集的合理预期是mAP50在0.6到0.85之间波动mAP50-95大概率在0.4以下如果mAP50超过0.95基本可以断定过拟合了——模型把训练图背下来了。3.4 首次训练完的必做检查weights里到底存了什么训练完成后runs/detect/train/目录下会生成best.pt和last.pt。best.pt是验证集指标最好的权重last.pt是最后一轮epoch结束的权重。如果训练过程一直在过拟合last.pt可能比best.pt差很多。我的习惯是拿best.pt去跑验证集不要拿last.pt。验证命令很简单yolo detect val \ modelruns/detect/train/weights/best.pt \ datakitchen_water.yaml这条命令会重新计算验证集上的mAP指标并打印一个详细的按类别指标表。你要看每类的precision和recall——积水类别如果recall低说明漏检多如果precision低说明误检多。在积水检测这个场景里我宁可recall低一点也要保precision因为厨房积水报警如果三天两头误报用户直接把设备关了比漏报更致命。这是产品级需求和技术指标之间的权衡数据集本身不会告诉你但做项目的人必须自己定。4. 88张图的边界与过拟合风险为什么mAP高不等于能用4.1 类别不平衡是第一个暗雷“2类别”听起来简单但这2个类别在图片里的出现频率大概率严重不均。正常厨房场景里“人”出现的频率比“积水”高得多或者反过来采集时主要拍积水、人物出现少。如果你的数据划分时没注意类别分布训练集和验证集里某个类别的数量差异过大模型在那个类别上的评估结果就很不稳定。我拿到这种小数据集时会先统计每张图的类别出现次数。如果发现某个类别只在个位数图片里出现那这个类别的检测结果基本是玄学。解决方案有两个一是放弃训练这个类别改成单类别检测把精力集中在积水这个核心目标上二是手动把该类别的图片复制增强几份但这样做要小心模型对特定背景过拟合。在88张图的约束下我的建议是优先保主类别。4.2 积水检测的视觉特征陷阱纹理、反光、光影积水在RGB图像里的特征不像人、车那样有清晰的轮廓。它可能是半透明的、倒映着天花板灯光、也可能是拖地后残留的薄薄一层水膜。同一块积水白天和晚上的颜色值完全不同。模型真正学到的可能不是“水”而是“地砖在这个位置的颜色变化”。这意味着换个场景、换个地砖颜色模型可能直接失效。这是积水检测和通用目标检测最大的不同通用检测的类别有稳定的语义特征而积水是“材质 光影”的复合特征。所以训练集里的图片多样性比数量更重要——如果88张图全是同一个厨房、同一个灯光那模型换个房间基本不能用。这个数据集的价值是验证流程但真要落地你得自己扩展数据覆盖不同地砖、不同光照、不同积水形态。4.3 过拟合判断的三个硬指标训练小数据集必须盯着过拟合但“过拟合”在YOLO训练里不是看训练集loss降没降到底而是看三个信号。第一个信号是训练loss持续下降而验证loss先降后升这叫典型的泛化拐点。第二个信号是mAP50高达0.9以上但mAP50-95远低于它比如0.9对0.3说明模型学会了“猜中大概区域”但框不准。第三个信号是precision和recall极端不均衡比如precision 0.98但recall 0.4说明模型很保守只挑最有把握的框大量真实目标被漏掉。# 快速查看训练曲线关键指标 import pandas as pd df pd.read_csv(runs/detect/train/results.csv) df[[epoch, train/box_loss, val/box_loss, metrics/precision, metrics/recall, metrics/mAP50]].tail(10)这段代码逻辑说明results.csv是训练过程中自动记录的指标文件直接用pandas读取并看最后10行就能判断模型停在什么状态。参数说明如果val/box_loss在最后几轮开始回升说明过拟合已经发生你需要回退到best.pt而不是用最后一个权重。4.4 增强策略在小数据集上的调整方向如果你不关增强、用默认增强大干100轮小数据集会快速记住增强后的伪特征。但完全不增强也不行因为数据太少了。一个折中方案是只开轻度几何增强关掉颜色类增强——因为积水检测对颜色变化极其敏感你让模型看到“增强过的伪积水颜色”它学到的颜色边界就漂了。# 适合小数据集的增强配置片段 augment: true hsv_h: 0.0 hsv_s: 0.0 hsv_v: 0.0 degrees: 10 fliplr: 0.5 scale: 0.3参数说明hsv_h/s/v全设0关闭颜色扰动degrees设10度以内允许轻微旋转fliplr开水平翻转因为积水在镜像下仍然是积水scale控制在0.3防止缩放过狠导致目标太小。这套配置的核心思路是只做不影响积水语义的几何变换不做颜色扰动。你如果还是发现过拟合就把degrees和scale也调成0。5. 常见问题排查训练翻车的现象、原因与后悔药5.1 训练直接报错“Assertion num_images 0 failed”数据集一张图都没加载现象是训练刚开始终端抛出一个assert num_images 0 failed进程退出。原因几乎总是路径不对要么是YAML里的path写错了指向了空目录要么是train/val子目录名字和实际不一致比如实际叫images/train_images而你写的是images/train。还有一个常见原因图片目录有图但labels/train下对不上任何一个标注TXTYOLO会把这批图片视为“无标注”而跳过最终可用图片数为0。解决方法是先检查目录树再看YAML里的相对路径。我一般会直接在当前终端ls一遍数据集目录确认大小写和文件名不要相信记忆里上一版数据的目录结构。如果确认没问题就用python -c from ultralytics import YOLO; YOLO(yolov8n.pt).train(dataxxx.yaml, epochs1, batch1)跑一个单batch快速测试报错信息会明确告诉你哪张图缺失。5.2 loss在0.5附近震荡不上不下模型完全没学到东西现象是box_loss和cls_loss从第一轮到第100轮几乎不变验证mAP始终在0.1以下。原因通常有两个一是标注坐标严重错误比如YOLO TXT里的中心点坐标算成了像素值没有归一化或者宽高写成了0二是类别ID映射和names配置不一致模型把标签学成了错误的含义。排查办法是打开一张训练图的标注可视化确认不要只看数字。用OpenCV把TXT里的框画回原图上import cv2 img cv2.imread(images/train/kitchen_001.jpg) h, w img.shape[:2] with open(labels/train/kitchen_001.txt) as f: for line in f.readlines(): cls, xc, yc, bw, bh map(float, line.split()) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(check_annotation.jpg, img)这段脚本的逻辑说明把归一化坐标还原成像素坐标画出框人眼比对框和实际目标是否吻合。参数说明如果框画得偏移很大那必然是标注转换出了问题重点查img_w和img_h是不是和实际图片不一致如果框的位置对但很多目标没框那说明标注漏标严重这个数据集的标注质量就得打个问号。5.3 验证集mAP很高但实测摄像头画面全乱报现象是训练指标看起来完美mAP50有0.85但把best.pt接到摄像头流上对着真实厨房拍框乱飞、频繁误报。原因很直接训练集的88张图和实际部署时的光照、视角、地砖差异太大。这就是“小数据集 高指标”的经典陷阱——模型在训练分布内自洽但泛化为零。后悔药有两个方向。第一个是数据层面把部署场景真正的截图捞个几十张做半自动标注补进数据集重新训这比调参管用得多。第二个是推理层面对输出的置信度加阈值比如把conf从默认0.25提到0.6以及增加时间序列滤波——连续N帧都在同一位置检测到积水才触发告警靠滤波消除单帧误报。不要把精力浪费在调整YOLO的超参数上数据分布不对调什么都是白搭。5.4 显存不足CUDA out of memorybatch和imgsz怎么取舍现象是在一张消费级显卡比如8GB显存上训练batch8、imgsz640直接OOM。优先动的是batch不是imgsz。因为imgsz640是YOLOv8训练时推理尺寸的基础调小到416会影响模型对目标的感知尺度尤其积水这种目标是中小尺寸缩太多小目标就没了。而batch减小只是让每个step看到的图少一张训练结果差别不大。yolo detect train \ datakitchen_water.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch4参数说明batch4通常能把峰值显存压到6GB以下。如果4还不行检查是不是有其他进程占用了显存用nvidia-smi看看。另一个做法是开YOLO的梯度累积但ultralytics官方没有直接暴露这个参数需要改训练循环不推荐新手折腾。在数据集只有88张的情况下batch2也能训不用过于担心batch太小的噪声问题——你的数据集本来就小梯度噪声大是常态。5.5 训练完后发现数据划分漏了某个类别现象是训练正常完成但验证结果里少了一个类别没有报错。原因是在划分训练集和验证集时随机抽取某个类别出现次数极少恰好全被分到了训练集验证集里一张该类别的图都没有。解决方法是统计后按类别分层划分不要纯随机。手工用脚本先按类别分组再按比例抽样import os, random from collections import defaultdict ann_dir labels/train # 这里指全量标注目录 samples_by_class defaultdict(list) for txt_name in os.listdir(ann_dir): if not txt_name.endswith(.txt): continue with open(os.path.join(ann_dir, txt_name)) as f: classes {line.split()[0] for line in f} for c in classes: samples_by_class[c].append(txt_name) # 每个类别至少留20%进验证集 val_files set() for c, files in samples_by_class.items(): random.shuffle(files) keep int(len(files) * 0.2) val_files.update(files[:keep])这段代码的逻辑说明按文件里的类别做索引然后每类抽20%进验证集确保验证集不会缺类。参数说明keep比例可以根据你的需求改成0.2到0.3之间但小数据集不建议超过0.3否则训练集会被削得太薄。6. 用88张图做到“跑通能用”的进阶验证技巧当你把训练、验证流程走完别急着收工。做一套离线验证脚本用小视频片段代替单张图片评估在连续帧中的表现。因为积水检测的最终形态是视频告警单张图片的mAP说明不了时序稳定性。我的做法是录一段10秒的厨房视频按帧抽图跑一遍yolo detect predict然后统计误报出现的帧数和位置抖动。如果同一个小反光在连续几十帧里反复触发那就要么调高置信度要么加区域过滤。推理阶段有个实用参数配对conf0.5加iou0.5。conf设太低会放出大量误检框设太高又可能漏掉浅积水iou控制NMS时重叠框的合并力度在密集小目标场景下iou0.5是常用起点。你可以写个小脚本批量测试不同conf下的precision/recall曲线找出适合你部署场景的工作点而不是直接用训练时的默认0.25。from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.predict( sourcekitchen_video.mp4, conf0.5, iou0.5, saveTrue, streamTrue, ) # streamTrue 时 results 是生成器逐帧产出 for i, r in enumerate(results): if r.boxes is not None and len(r.boxes) 0: for box in r.boxes: cls int(box.cls[0]) score float(box.conf[0]) if cls 0 and score 0.6: print(fframe {i}: water detected, conf{score:.2f})参数说明streamTrue是关键它让推理按帧读取视频而不是一次性加载整个视频到内存处理长视频不爆内存。conf0.5设置置信度阈值iou0.5控制重叠框的抑制。这里加分值的条件score 0.6可以视作一个额外的输出过滤相当于在模型输出的基础上再加一道闸门——这对小数据集训练出的模型尤其重要因为它的置信度分布往往不够清晰高分框和低分框的界限模糊。最后给你一个我的习惯作为收尾拿到任何小数据集第一件事永远是手动检查标注质量第二件事是跑一个epochs1的最小训练确认数据管线没有错第三件事才是正经训练。这88张图的数据集最大的价值不是让你得到一个能商用的模型而是让你把VOC到YOLO的转换、训练参数的作用、过拟合的表现这些环节亲手走一遍——走完你就知道积水检测这个方向真正难的不是模型而是数据采集的多样性以及你对误报率的容忍线在哪里。希望这些踩坑经验帮到你。本文还有配套的精品资源点击获取