厨房积水检测:88张VOC+YOLO数据集训练YOLO全流程解析

📅 发布时间:2026/9/11 17:11:47
厨房积水检测:88张VOC+YOLO数据集训练YOLO全流程解析
简介此数据集面向厨房场景下积水与泡沫检测任务为需要训练YOLO、Faster R-CNN等目标检测模型的开发者与算法工程师提供高质量标注样本。数据集共268个文件包含88张JPG原始图像、88个Pascal VOC格式XML标注文件、90个TXT文件其中88个为YOLO格式标注及2个系统配置文件打包为7z格式约18.23MB并可直接接入YOLO系训练流程。标注类别为foam与water两类分别包含272个与290个真实框总框数562个全部由labelImg工具按矩形框规则逐张绘制确保标注准确一致。当前已有161人浏览学习该数据集可用于厨房安全监测、智能家居、清洁机器人视觉等场景下的模型预训练与算法验证是学习目标检测数据标注与模型训练的理想基础数据。1. 88张图片的厨房积水检测数据集到底能不能训练YOLO88张图、2个类别、VOC和YOLO格式双份标注、一个7z压缩包——这套厨房积水检测数据集第一眼看上去确实有点单薄。但正是这种小数据集最适合把YOLO目标检测流程完整跑通。拿到手之后你会面对一连串真实工程问题7z怎么在Linux下解压、VOC的XML怎么对应YOLO的txt、88张图怎么划分训练集和验证集、训练参数该设多少、过拟合了怎么办。这些问题不经历一遍换个大数据集也照样踩坑。它不能和COCO这种量级的数据集比精度但它的价值在于验证“厨房积水这个场景到底能不能用视觉检测做”以及“VOCYOLO两种格式并存时工具链怎么切换”。适合正在做智慧厨房、漏水报警、边缘视觉方案的人拿它练习数据准备和模型微调流程。下面不评价标注质量只讲拿到手之后怎么最大化利用。2. VOC和YOLO两种标注格式怎么共存从7z解压到标签内容压缩包名字里同时写了VOC和YOLO格式意味着同一批图片给了两套标注VOC用XML存矩形框YOLO用txt存归一化坐标。很多数据集的发布者习惯只给一种给两种是为了让使用者少做一步格式转换。但代价是目录结构往往比较混乱你得先搞清楚两种格式的对应关系再决定直接使用哪一套。2.1 在Linux下解压7z压缩文件并校验完整性拿到“厨房积水检测数据集VOCYOLO格式88张2类别.7z”这样的文件我通常不急着解压。第一步是校验第二步才是解压。在Linux下如果没有安装7z命令先装p7zipsudo apt install p7zip-full然后用下面四行完成校验和解压sha256sum 厨房积水检测数据集VOCYOLO格式88张2类别.7z 7z t 厨房积水检测数据集VOCYOLO格式88张2类别.7z 7z l 厨房积水检测数据集VOCYOLO格式88张2类别.7z 7z x 厨房积水检测数据集VOCYOLO格式88张2类别.7z -o./kitchen_watersha256sum计算整个压缩文件的SHA256哈希值适合与发布者公开的哈希比对。7z t是测试压缩包完整性利用7z内置的CRC机制检查文件是否在传输中损坏。7z l只列目录内容不解压可以提前观察内部有没有单独的根目录避免解压后文件散落一地。7z x解压到./kitchen_water-o参数指定输出目录注意-o后面不能有空格。这套流程做下来基本能排除“下到残缺文件、解压后打不开”的最常见故障。提示如果发布者没公开SHA256至少运行7z t。它能检测压缩流是否完整但无法识别压缩包本身是否被更换过。安全要求高的环境中仍建议以发布渠道公开的哈希为准。如果是在Windows上做数据整理用7-Zip图形界面同样能完成测试和解压命令换成C:\Program Files\7-Zip\7z.exe参数一致。不过后续训练命令仍然建议在Linux下跑避免路径分隔符带来的麻烦。解压后先看目录整体结构。比较常见的是Annotations、JPEGImages、labels、classes.txt和ImageSets/Main共存。JPEGImages放原图Annotations放VOC的XMLlabels放YOLO的txtclasses.txt列出类别名一行一个。这种组织方式意味着你不需要再去找额外脚本转格式。2.2 VOC标注的XML文件里有什么关键信息随便打开一个Annotations下的XML文件内容大概长这样annotation folderJPEGImages/folder filenamekitchen_001.jpg/filename path/home/user/kitchen/JPEGImages/kitchen_001.jpg/path source databaseUnknown/database /source size width1280/width height720/height depth3/depth /size segmented0/segmented object namewater/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin100/xmin ymin200/ymin xmax340/xmax ymax410/ymax /bndbox /object /annotationsize里是图像真实宽度、高度和通道数后续YOLO归一化计算依赖它。object代表一个目标框name是类别名这里的值必须能在classes.txt里找到。bndbox是目标的左上角和右下角绝对像素坐标四个值都是整数。truncated表示目标是否在图像边缘被截断difficult表示该目标是否难以辨认这两个标记在转换YOLO格式时通常被丢弃但它们能帮你了解标注质量——如果difficult1的目标很多训练时模型容易学得迷茫。很多标注工具例如labelImg保存时就是这种格式。如果你怀疑某个框歪了用labelImg打开图检查比直接看XML直观得多。这个习惯在“yolo标注训练工具”的讨论里经常出现也是排查小数据集问题时要做的第一步。2.3 YOLO格式的txt怎么和图片对应同样一张kitchen_001.jpg在labels目录下的kitchen_001.txt内容可能是0 0.462890625 0.423611111 0.1875 0.291666667 1 0.135416667 0.568055556 0.060546875 0.125每一行代表一个目标框。第一个数字是类别ID从0开始顺序由classes.txt决定。后面四个数字是该框的中心点x、中心点y、宽度w、高度h全部相对于图片宽高做了归一化。计算关系是x_center (xmin xmax) / 2 / widthy_center (ymin ymax) / 2 / heightw (xmax - xmin) / widthh (ymax - ymin) / height。假如classes.txt内容是water和stain那么water对应ID0stain对应ID1。如果两者顺序打乱例如把stain放第一行那么所有txt中的0就会从water变成stain模型学到的语义也随之错乱。所以拿到数据集后第一步不是训练而是确认classes.txt的顺序和labels中实际出现的ID一致。为了彻底搞清楚转换关系可以自己写一个VOC转YOLO的脚本来验证import xml.etree.ElementTree as ET classes [water, stain] def convert(xml_path): tree ET.parse(xml_path) root tree.getroot() width int(root.find(size/width).text) height int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text box obj.find(bndbox) x1 int(box.find(xmin).text) y1 int(box.find(ymin).text) x2 int(box.find(xmax).text) y2 int(box.find(ymax).text) cls_id classes.index(name) x_c (x1 x2) / 2 / width y_c (y1 y2) / 2 / height w (x2 - x1) / width h (y2 - y1) / height lines.append(f{cls_id} {x_c:.6f} {y_c:.6f} {w:.6f} {h:.6f}) return \n.join(lines)这段代码的核心是classes.index(name)它把XML里的类别字符串映射成YOLO的数字ID。如果数据集自带YOLO格式你不必运行这段脚本但当你发现某个txt的坐标超出0到1的范围时可以用这个逻辑反向检查。xml.etree.ElementTree是Python标准库不需要额外安装。2.4 两种格式共存时的目录组织建议YOLO训练代码通常只认两个目录images和labels且同名图片和同名txt必须分别在对应目录下。VOC的XML在这个流程里不参与训练所以我习惯把压缩包整理成下面的结构kitchen_water/ ├── images/ │ ├── kitchen_001.jpg │ └── ... ├── labels/ │ ├── kitchen_001.txt │ └── ... ├── classes.txt └── voc_backup/ └── Annotations/整理命令如下cd kitchen_water mkdir -p images labels voc_backup cp JPEGImages/*.jpg images/ cp labels/*.txt labels/ cp -r Annotations voc_backup/把Annotations整体挪到voc_backup下既保留VOC备份又不干扰YOLO训练时的文件扫描。ImageSets/Main里的trainval.txt如果存在可以保留但后续划分以你自己的脚本为准。这样整理好后面写data.yaml和训练命令都会省事很多。3. 把VOCYOLO格式数据集喂给YOLO训练的最小流程88张图的分量决定了你不能按大数据集的常规路子走。划分数据集是第一关这里的决策会影响后面所有指标。先明确一个原则验证集的图片必须和训练集来自不同时间段或不同光照条件否则模型只是背住了场景而不是学会了判断积水。3.1 随机划分但固定随机种子我一般按8:2划分也就是70张训练、18张验证。88张再切测试集意义不大把样本数进一步压小验证集上的mAP波动会非常大。下面这段Python脚本生成YOLO训练所需的train.txt和val.txtimport os import random root kitchen_water names [] for f in sorted(os.listdir(f{root}/images)): if f.endswith((.jpg, .jpeg, .png)): names.append(os.path.splitext(f)[0]) random.seed(42) random.shuffle(names) val_count int(len(names) * 0.2) val_names set(names[:val_count]) for split in [train, val]: with open(f{root}/{split}.txt, w) as out: for name in names: if (split train) ! (name in val_names): out.write(f{root}/images/{name}.jpg\n)脚本逻辑很简单先收集所有图片名固定随机种子后打乱顺序取前18个名字作为验证集其余作为训练集。最后按图片名写路径列表。random.seed(42)这行去掉每次运行划分结果都会变实验就不可复现。验证集样本数偏少是客观事实所以训练时更依赖早停和交叉验证的手感。3.2 编写data.yaml路径和类别名一个都不能错Ultralytics YOLO系列都通过data.yaml描述数据集。最小可用配置path: /绝对路径/kitchen_water train: train.txt val: val.txt names: 0: water 1: stainpath是数据集根目录后续train、val中的相对路径都基于它。train和val这里写成txt文件因为txt里是明确的图片路径能防止模型把验证集图片又当成训练集。如果你把train直接写成images那么训练时整个目录都会被扫进去validation用同一个目录mAP会虚高到离谱这是新手最容易踩的坑。names的顺序必须和classes.txt一致也和你labels里的ID一致。这里只有两个类别不容易出错但仍要对着classes.txt核对一遍尤其是如果classes.txt里是stain和water而你写成了water和stain那整个训练就白跑了。3.3 训练命令与关键参数用YOLOv8自带命令行工具训练最小命令是yolo train datadata.yaml modelyolov8n.pt epochs200 imgsz640 batch16 device0modelyolov8n.pt会下载COCO预训练权重不要换成.yaml后者表示从零训练。小数据集上用yolov8nnano基本足够两个类别的检测难度不大模型容量太大反而更容易过拟合。常用参数如下表参数常用值作用epochs200-300总训练轮数小数据集要更多轮次收敛imgsz640模型输入边长原图是1280x720时先压到640batch16 或 8每批图片数显存不够就减半device0 / cpu指定GPU没用GPU就填cpupatience50验证集指标连续多少轮无改善即早停lr00.01 → 0.001初始学习率小数据集建议调低freeze10冻结骨干网络层数用于迁移学习CPU训练不是不行88张图每轮可能只要几分钟但300轮也要好几个小时。有条件尽量用GPU。没有GPU时命令改成yolo train datadata.yaml modelyolov8n.pt epochs200 imgsz640 batch8 devicecpupatience50这个参数很关键它代表验证集指标连续50轮没有改善就自动停止。小数据集很容易在80轮后开始过拟合不早停的话后面80轮都在浪费时间。加上它yolo train datadata.yaml modelyolov8n.pt epochs300 imgsz640 batch16 device0 patience503.4 训练日志怎么看loss、mAP和过拟合信号训练过程中控制台会实时打印指标结束后所有结果在runs/detect/train/下。可以用下面的命令直接看摘要head -5 runs/detect/train/results.csvresults.csv里每一列是epoch、train/box_loss、train/cls_loss、train/dfl_loss、metrics/precision、metrics/recall、metrics/mAP50、metrics/mAP50-95等。核心关注三条train/box_loss持续下降val/box_loss不再下降甚至反弹说明过拟合开始metrics/mAP50超过0.5就说明有可用价值。两个类别的积水检测在88张图上mAP50做到0.7以上已经算不错。如果训练结束后train/box_loss很高始终压不下去先检查学习率。YOLOv8默认lr00.01小数据集经常要用更低的初始学习率yolo train datadata.yaml modelyolov8n.pt epochs300 lr00.001lr0是初始学习率后面会按调度策略下降。改成0.001后如果loss下降明显变慢可以折中为0.005。不要同时把batch和lr0都调得过低否则训练进程会变得异常慢。4. 只有88张图怎么防止过拟合增强、迁移学习与损失函数小数据集训练几乎必然面对过拟合。积水和墙面反光、地砖纹路在视觉上高度相似模型很容易记住训练集里的特定纹理而不是“水”这个概念。这一章要解决的就是这件事。4.1 数据增强参数怎么调YOLOv8默认开启一套增强策略包括马赛克、随机翻转、缩放、色域变化等。对积水检测来说无差别套用默认参数不一定合适。比如垂直翻转真实监控摄像头画面里积水不会倒过来出现flipud0更合理。水平翻转则保留因为摄像头安装位置不同积水可能在画面左侧也可能在右侧。训练命令中通过参数覆盖默认值yolo train datadata.yaml modelyolov8n.pt epochs300 batch16 imgsz640 \ mosaic0.5 fliplr0.5 flipud0.0 scale0.3 hsv_h0.01 hsv_s0.5mosaic0.5把马赛克增强概率从1.0降到0.5。马赛克会放大或缩小目标可能让积水区域看起来像普通色块过强的马赛克会让小数据集训练更难收敛。scale0.3限制随机缩放比例避免积水目标变得过小。hsv_h0.01和hsv_s0.5控制色调和饱和度变化厨房灯光偏暖或偏冷时微调色相有助于泛化。这些参数没有标准答案你可以跑两组对比一组mosaic0.5一组mosaic0.0看验证集mAP哪个更好。4.2 迁移学习不要从零训练88张图从零训练YOLO结果通常是loss降不下来因为模型没有任何视觉先验。正确做法是加载COCO预训练权重。modelyolov8n.pt已经是迁移学习的入口它下载的是在COCO上训练好的权重。在此基础上继续训练模型会保留对边缘、纹理、物体边界的基本理解。真正需要动脑的是微调策略。常见做法是分两阶段。第一步冻结骨干网络只训练检测头。在YOLOv8的命令行里可以这样做yolo train datadata.yaml modelyolov8n.pt epochs100 freeze10freeze10表示冻结模型前10层这些层属于骨干网络负责提取通用视觉特征。冻结后反向传播不会更新它们所以预训练特征不会被小数据集的噪声破坏。这一阶段跑完后得到runs/detect/train/weights/last.pt再解冻全部层用更小学习率微调yolo train datadata.yaml modelruns/detect/train/weights/last.pt epochs200 freeze0 lr00.0005注意这里的model换成了第一步产生的last.pt而不是原始的yolov8n.pt。如果你直接拿COCO权重做全量微调开头学习率太大照样会破坏预训练特征。一般全量微调的学习率比初始学习率低一个数量级这就是为什么要单独设置lr00.0005。4.3 YOLO损失函数构成与类别不平衡的处理YOLOv8的损失函数由三部分组成box_loss使用CIoU计算框的位置误差cls_loss使用BCE计算分类误差dfl_loss是分布焦点损失专门优化边框的定位分布。理解这一点对判断训练状态很有帮助。如果cls_loss高说明模型分不清“积水”和另一个类别如果box_loss高说明框的位置不稳定或标注本身有噪声。两个类别的样本量不平衡时小类别的梯度会被大类别的BCE损失淹没。以这个数据集为例很可能“积水”框有几百个“反光”或“水渍”只有几十个。常见做法不是改损失函数而是从数据层面做均衡。我一般会写一个脚本统计每个类别的框数量cd kitchen_water/labels cat *.txt | awk {print $1} | sort | uniq -c这一行命令统计所有txt里每个类别ID出现的总次数。如果发现类别1只有类别0的三分之一就需要增强少数类别。最简单的操作是复制含有少数类别的图片到训练集中并做水平翻转。下面这段脚本实现了批量复制和坐标变换import os from PIL import Image rare_cls 1 rare_imgs [] for name in os.listdir(labels): with open(flabels/{name}) as f: for line in f: if line.startswith(f{rare_cls} ): rare_imgs.append(name.replace(.txt, )) break for base in rare_imgs: src_img fimages/{base}.jpg src_lbl flabels/{base}.txt new_base base _flip img Image.open(src_img).transpose(Image.FLIP_LEFT_RIGHT) img.save(fimages/{new_base}.jpg) new_lines [] with open(src_lbl) as f: for line in f: c, x, y, w, h map(float, line.split()) new_lines.append(f{int(c)} {1.0 - x:.6f} {y:.6f} {w:.6f} {h:.6f}\n) with open(flabels/{new_base}.txt, w) as f: f.writelines(new_lines)这一段脚本先找出所有包含类别1的样本对它们做水平翻转然后把YOLO标注里的x_center变成1.0 - x_center。翻转后的图片和原图构成新的训练样本与原始图片一起参与下一轮训练。注意水平翻转后类别顺序不变宽度和高度的归一化值也不变。提示增强后要重新划分训练集和验证集确保原图和翻转图只出现在同一个集合里否则验证集指标会虚高。把本章前面的划分脚本重新跑一遍即可。5. 把积水检测模型部署到厨房监控的验证技巧训练收敛后验证才是真正决定能不能用的环节。直接拿单张测试图预测太片面厨房监控视频里的反光、水滴、倒影会让模型疯狂误报。以下技巧是用这个数据集训练出的模型落地前必须做的。5.1 导出ONNX并做单图推理部署到边缘盒子之前先导出成ONNX格式方便用ONNX Runtime或者TensorRT推理yolo export modelruns/detect/train/weights/best.pt formatonnx yolo predict modelbest.onnx sourcetest.jpg conf0.25 iou0.45conf0.25是置信度阈值低于它的检测框会被丢弃。如果积水的目标比较小可以降到0.15但随之而来的是更多的误报。iou0.45是NMS合并重叠框的阈值值越小越容易合并掉相邻框。对积水这种边缘模糊的目标建议先保留0.45不要随便改。5.2 用视频流验证误报单帧预测无法判断积水是真实存在还是短暂反光。常见做法是连续检测多帧同一个位置在连续3帧以上都被检测到才触发报警。下面是核心判断逻辑的伪代码实际运行需要补充视频流读取和模型加载部分hits {} for frame in stream: result model(frame, conf0.2)[0] for box in result.boxes.xyxy.cpu().numpy(): key (int(box[0] // 32), int(box[1] // 32), int(box[2] // 32), int(box[3] // 32)) hits[key] hits.get(key, 0) 1 if hits[key] 3: alert() break代码的核心是按框的左上角和右下角除以32做格子化对齐把位置相近的框合并成同一个统计键。同一个位置累计命中3次才触发告警短时间出现又消失的反光会被过滤掉。实际工程中这个格子化逻辑可以改成IoU匹配效果更准确。5.3 用混淆矩阵检查两个类别的分离度训练结束后runs/detect/train/下会生成confusion_matrix.png这是排查误报的宝藏。打开看两个类别是否互相混淆如果“积水”被频繁预测成“反光”说明类别定义本身有重叠要么合并类别要么补充标注如果背景被预测成其中一个类别说明conf阈值需要提高。两个类别的数据集只有88张出现混淆是完全正常的关键是要知道混淆在哪里才知道下一步该补哪些图片。另一个实用技巧是检查labels和图片是否一一对应。用下面命令快速列出没有标签的图片for f in images/*.jpg; do name$(basename $f .jpg) [ -f labels/$name.txt ] || echo missing label: $name done这个检查能揪出压缩包整理时遗漏的标注避免模型在训练时把无标签图片当成背景误伤一批正常样本。做完这些再回到训练阶段补充数据和调整阈值厨房积水的检测精度会一步步稳定下来。本文还有配套的精品资源点击获取