☰
厨房积水检测数据集实战:88张图训练YOLOv8的完整流程与避坑指南
2026/10/8 1:11:42 网站建设 项目流程

简介:厨房积水检测数据集面向智能家居、商用厨房安全监测及清洁机器人等场景,提供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张图能训练出什么,先泼盆冷水

做厨房积水检测,很多人第一反应是“这场景简单,不就一块地板嘛”。但真上手你会发现,积水反光、地砖纹理、灯光色温、拖把留下的水痕,全是让模型误判的坑。这个“厨房积水检测数据集VOC+YOLO格式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> <folder>images</folder> <filename>kitchen_001.jpg</filename> <size> <width>640</width> <height>480</height> <depth>3</depth> </size> <object> <name>water</name> <bndbox> <xmin>120</xmin> <ymin>85</ymin> <xmax>310</xmax> <ymax>240</ymax> </bndbox> </object> </annotation>

上面这段XML只示意了单类别单目标的结构。逻辑说明:<filename>必须和图片文件名完全一致,训练脚本靠这个字段把图和标注关联起来;<name>就是类别名,待会儿转YOLO格式时这个字符串会被映射成整数ID;<bndbox>四个值必须是整数像素坐标,且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_ok=True) 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 \ data=kitchen_water.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=8 \ patience=30 \ optimizer=AdamW \ lr0=0.001 \ augment=False

命令参数说明:model=yolov8n.pt选nano版本,参数量最小,适合小数据集,你选s或m纯属给自己找麻烦;epochs=100在小数据集上通常够了,但建议配合patience=30让它在验证集指标不再提升时自动早停;augment=False这条值得单独说——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 \ model=runs/detect/train/weights/best.pt \ data=kitchen_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下对不上任何一个标注TXT,YOLO会把这批图片视为“无标注”而跳过,最终可用图片数为0。

解决方法是先检查目录树,再看YAML里的相对路径。我一般会直接在当前终端ls一遍数据集目录确认大小写和文件名,不要相信记忆里上一版数据的目录结构。如果确认没问题,就用python -c "from ultralytics import YOLO; YOLO('yolov8n.pt').train(data='xxx.yaml', epochs=1, batch=1)"跑一个单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 memory),batch和imgsz怎么取舍

现象是在一张消费级显卡(比如8GB显存)上训练,batch=8、imgsz=640直接OOM。优先动的是batch,不是imgsz。因为imgsz=640是YOLOv8训练时推理尺寸的基础,调小到416会影响模型对目标的感知尺度,尤其积水这种目标是中小尺寸,缩太多小目标就没了。而batch减小只是让每个step看到的图少一张,训练结果差别不大。

yolo detect train \ data=kitchen_water.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=4

参数说明:batch=4通常能把峰值显存压到6GB以下。如果4还不行,检查是不是有其他进程占用了显存,用nvidia-smi看看。另一个做法是开YOLO的梯度累积,但ultralytics官方没有直接暴露这个参数,需要改训练循环,不推荐新手折腾。在数据集只有88张的情况下,batch=2也能训,不用过于担心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,然后统计误报出现的帧数和位置抖动。如果同一个小反光在连续几十帧里反复触发,那就要么调高置信度要么加区域过滤。

推理阶段有个实用参数配对:conf=0.5加iou=0.5。conf设太低会放出大量误检框,设太高又可能漏掉浅积水;iou控制NMS时重叠框的合并力度,在密集小目标场景下iou=0.5是常用起点。你可以写个小脚本批量测试不同conf下的precision/recall曲线,找出适合你部署场景的工作点,而不是直接用训练时的默认0.25。

from ultralytics import YOLO model = YOLO('runs/detect/train/weights/best.pt') results = model.predict( source='kitchen_video.mp4', conf=0.5, iou=0.5, save=True, stream=True, ) # stream=True 时 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(f"frame {i}: water detected, conf={score:.2f}")

参数说明:stream=True是关键,它让推理按帧读取视频而不是一次性加载整个视频到内存,处理长视频不爆内存。conf=0.5设置置信度阈值,iou=0.5控制重叠框的抑制。这里加分值的条件score > 0.6可以视作一个额外的输出过滤,相当于在模型输出的基础上再加一道闸门——这对小数据集训练出的模型尤其重要,因为它的置信度分布往往不够清晰,高分框和低分框的界限模糊。

最后给你一个我的习惯作为收尾:拿到任何小数据集,第一件事永远是手动检查标注质量,第二件事是跑一个epochs=1的最小训练确认数据管线没有错,第三件事才是正经训练。这88张图的数据集,最大的价值不是让你得到一个能商用的模型,而是让你把VOC到YOLO的转换、训练参数的作用、过拟合的表现这些环节亲手走一遍——走完你就知道,积水检测这个方向真正难的不是模型,而是数据采集的多样性,以及你对误报率的容忍线在哪里。希望这些踩坑经验帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询