ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

室内积水检测数据集357张VOC+YOLO格式实战训练

室内积水检测数据集357张VOC+YOLO格式实战训练 简介面向目标检测任务室内房间积水检测数据集专门用于训练积水识别模型或验证各类检测算法适合计算机视觉初学者、算法工程师以及安防监控、室内巡检等场景的开发者使用。压缩包内共1073个文件包含357张jpg原图、357个Pascal VOC格式xml标注文件、357个YOLO格式txt标注文件以及少量辅助txt整体体积84.65MB文件组织规整便于按需取用。所有标注均使用labelImg人工框选完成对“jishui”类别绘制矩形框共378个标注框类别单一、定位准确符合数据集“准确且合理标注”的约定可直接用于YOLO系列、Faster R-CNN、SSD等主流检测框架的训练与评估。数据集不包含分割路径等额外内容图片与标注一一对应免去数据清洗与格式转换环节适合快速搭建积水检测实验也适合作为目标检测入门阶段的练习数据。目前已有216人学习下载对需要积累室内积水样本的开发者来说是一份可靠且可直接落地的数据基础。1. 357张积水数据能干什么先别嫌少先搞清任务边界拿到“室内房间内积水检测数据集357张VOCYOLO格式.zip”这套目标检测数据时多数人的第一反应是“才357张够干嘛”。这个反应很正常但放在积水检测这个具体任务上结论会反直觉室内积水的形态高度一致场景又受限在瓷砖、木地板、地漏周边几条路里357张如果标注干净、覆盖了多光照条件完全够训练出一个能用的初版预警模型——比如漏水巡查、水浸报警、物业巡检辅助这类对召回率敏感、对框的精细度不那么苛刻的任务。本文就用这套数据走一遍从格式校验、转换到训练和排错的完整落地流程适合刚接触目标检测的新手也适合想快速评估“小数据能不能撑起积水识别”的从业者。先别急着上大网络先把双格式数据集的底细摸清。2. 拆解VOC与YOLO双格式目录、标注语义与一个自检脚本2.1 VOC目录不是只有图片和标签其实是一整套组织约定解压后能看到两种并存的数据组织方式。VOC那一侧也就是Pascal VOC风格惯例是三个目录JPEGImages放原图Annotations放同名xmlImageSets/Main放划分好的train.txt、val.txt。357张规模不大常见划分是训练约280张、验证约70张留不留测试集看作者意图。xml里真正有用的信息集中在object节点每个目标有name类别名和bndbox四角坐标少数还带difficult标记表示这个目标很难辨认。用ET解析时最常踩的就是difficult1的目标处理策略转YOLO前决定留着还是丢否则后续训练会把难样本当成普通目标干扰损失曲线。VOC的坐标单位是绝对像素xmin/ymin/xmax/ymax对应图上真实位置。积水这个对象很特殊水迹边缘是渐变的标注员对“哪里算水”的把握会直接影响框的质量。有的框贴着墙根有的框只框住水渍最亮的核心区还有的把地砖反光也圈进来。这不是数据脏而是任务本身的标注不确定性后面训练评估时要当成固有噪声看。2.2 YOLO的txt标签怎么读五个数字代表什么YOLO那一侧每个xml对应一个同名txt放在labels目录里。每行五个字段class_id x_center y_center width height。前一个是类别索引从0开始对应你后续在data.yaml里names列表的顺序后四个都是归一化坐标用绝对像素除以图片宽高得到0到1之间的小数。比如一行0 0.5234 0.6712 0.3723 0.4581意思是这个积水框的中心点在图片水平52.34%、垂直67.12%处框宽占全图37.23%框高占45.81%。同一条标注在VOC里是四个像素绝对值在YOLO里是四个比例值语义等价但数值完全不同。正因为如此手动改txt最容易翻车有人把像素坐标直接写进txt有人忘了归一化有人把类别写成1但data.yaml里只有water一个类。这些问题训练时不一定报错但模型会学到些莫名其妙的东西所以动手训练前先做一次双格式一致性自检。2.3 用Python写一个标注自检脚本跑通之前先省半小时我每次拿到这种双格式数据集第一件事不是打开图片看而是跑一遍下面的脚本。它同时检查四件事图片能否被OpenCV正常解码、xml坐标是否越界、YOLO标签行数与VOC目标数是否一致、归一化坐标是否在合法区间。# check_labels.py import os import glob import cv2 import xml.etree.ElementTree as ET IMG_DIR JPEGImages # VOC侧图片目录 XML_DIR Annotations # VOC侧注解目录 TXT_DIR labels # YOLO侧标签目录 CLASSES [water] # 类别索引表顺序必须和data.yaml一致 def check_voc(xml_path, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() n 0 for obj in root.findall(object): box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) assert xmax xmin and ymax ymin, f{xml_path}: 框宽高为0 assert xmax img_w and ymax img_h, f{xml_path}: 标注超出图像边界 n 1 return n def check_yolo(txt_path, expected_n): with open(txt_path, r, encodingutf-8) as f: lines [line.strip().split() for line in f if line.strip()] assert len(lines) expected_n, f{txt_path}: YOLO行数与VOC目标数不一致 for parts in lines: assert len(parts) 5, f{txt_path}: 每行必须是5个字段 cls_id int(parts[0]) assert 0 cls_id len(CLASSES), f{txt_path}: 类别索引越界 x_c, y_c, w, h map(float, parts[1:]) assert 0.0 x_c 1.0 and 0.0 y_c 1.0, f{txt_path}: 中心点未归一化 assert 0.0 w 1.0 and 0.0 h 1.0, f{txt_path}: 宽高应为(0,1]之间的归一化值 assert 0.0 x_c - w / 2 and x_c w / 2 1.0, f{txt_path}: 框超出左右边界 def main(): xml_list sorted(glob.glob(os.path.join(XML_DIR, *.xml))) print(f发现 {len(xml_list)} 个VOC文件) for xml_path in xml_list: stem os.path.splitext(os.path.basename(xml_path))[0] img_path os.path.join(IMG_DIR, stem .jpg) txt_path os.path.join(TXT_DIR, stem .txt) img cv2.imread(img_path) assert img is not None, f{img_path}: 图片不存在或损坏 assert os.path.exists(txt_path), f{txt_path}: 缺少YOLO标签 img_h, img_w img.shape[:2] n check_voc(xml_path, img_w, img_h) check_yolo(txt_path, n) print(自检通过VOC与YOLO格式对齐) if __name__ __main__: main()脚本逻辑很直白先拿一张图的xml去查VOC目标数量再拿着这个数量去查YOLO的txt行数。只要行数对不上说明中间环节出过漏转或错转。注意img.shape[:2]取到的是高和宽别把顺序搞反。图片路径假设是jpg实际用png的图把stem .jpg改成stem .png就好。跑完没有断言错误才说明这套数据能安全喂给训练框架。提示自检脚本里CLASSES列表的顺序必须和之后data.yaml里names的顺序完全一致。索引错位是最隐蔽的坑出现时损失曲线看起来正常但预测结果永远张冠李戴。3. 从VOC切到YOLO的转换脚本归一化与三个易错细节3.1 转换脚本把xml的绝对坐标算成txt的归一化坐标虽然这套zip里两种格式都给全了但实际做项目时你仍可能需要自己转换比如把验证集重新切一份、把类别从中文名改成英文、或者你手里的数据只有VOC标注。下面这个脚本按YOLO的规范化要求把Annotations下的xml批量转成yolo_labels下的txt。# voc2yolo.py import os import glob import xml.etree.ElementTree as ET from PIL import Image CLASS_MAP {water: 0} # xml里的类名 - YOLO类别索引 def voc_xml_to_yolo_txt(xml_path, img_path, out_txt): img Image.open(img_path) img_w, img_h img.size tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in CLASS_MAP: continue cls_id CLASS_MAP[name] box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) xmin, xmax min(xmin, xmax), max(xmin, xmax) ymin, ymax min(ymin, ymax), max(ymax, ymax) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(out_txt, w, encodingutf-8) as f: f.write(\n.join(lines)) if __name__ __main__: os.makedirs(yolo_labels, exist_okTrue) for xml_path in sorted(glob.glob(Annotations/*.xml)): stem os.path.splitext(os.path.basename(xml_path))[0] img_path fJPEGImages/{stem}.jpg if not os.path.exists(img_path): print(f跳过缺失图片: {img_path}) continue voc_xml_to_yolo_txt(xml_path, img_path, fyolo_labels/{stem}.txt) print(转换完成建议接着跑上一章的自检脚本)转换的逻辑核心是用PIL读图拿真实宽高再逐个目标把四角坐标换算成中心点加宽高的归一化表示。之所以用Image.open而不是读xml里的size是因为有些标注工具的size字段和实际图片尺寸不一致一旦不一致整批归一化坐标全错。转换后别急着训练用2.3的自检脚本跑一遍新生成的yolo_labels确保没有漏行和越界。3.2 类别映射与classes文件的前后关系CLASS_MAP是转换时唯一的手工配置项。假设这套数据集只标注了water一个类映射表就一行如果后续你往数据里加了puddle、wet_area这些类就要在映射表里按索引顺序补。记住一个原则txt里的数字就是一切依赖names顺序的东西的同一口径。很多初学者在txt里写了1但data.yaml的names只写了[water]缓冲区索引直接越界。3.3 易错细节坐标裁边、difficult目标和积水的大宽高比第一个易错点是框越界。标注工具手滑时xmax偶尔会超过图片宽度几个像素直接换算后归一化框就超过1.0。YOLO数据加载器对边界检测很敏感轻则警告严重时loss直接变nan。处理方式有两种一种在转换阶段把坐标裁剪回图片范围内另一种把越过边界极少的框直接保留因为网上很多YOLO版本允许边界超差一个小容差。我习惯在转换时顺手保留原始语义但裁到[0,1]代价是框的面积损失零点几个百分点对积水检测这种边界本来就模糊的任务几乎无感。第二个易错点是difficult目标。VOC里的difficult1表示这个目标连人都不容易认。转换时如果不做过滤它会被当成普通正样本参与训练模型被迫学一个模糊不清的水渍典型表现是训练loss能降但验证时对该区域反复误报。常见做法是转换时直接跳过difficult目标保证训练标签干净。第三个易错点是积水目标的大宽高比。室内整片积水经常横跨半张图宽高比可能到2以上和通常目标检测数据里那些方方正正的物体差异明显。YOLO的anchor机制对极端宽高比不够友好训练时容易把大框拆成几个小框所以转换脚本里.6f的浮点精度别改成小数点后两位归一化值丢失精度会让框的边界抖动加剧对小图目标影响不大对大幅面积水是实打实的定位误差。4. 用YOLOv8跑通积水训练从data.yaml到第一条损失曲线4.1 环境怎么搭ultralytics的YOLOv8对0基础足够友好先用一句话说清选型理由357张的小数据扛不住大网络在YOLO系里选nano版起步是最稳的路线。ultralytics的YOLOv8把训练、验证、导出封得足够薄不需要自己手写dataloader和损失函数适合0基础把完整流程跑通。环境上pip install ultralytics装好依赖有NVIDIA显卡就把CUDA版PyTorch装对没有显卡用CPU也能训练只是357张图跑100个epoch要等上几个小时。别一上来就改网络结构先让基线跑起来后面再看需不需要加注意力、换损失。4.2 数据配置与最小训练脚本参数逐个说YOLOv8读取数据集靠一个yaml文件里面指定训练集、验证集路径和类别名。建议把图片和标签按下面的结构放好train和val两个目录下各自同时存在images和labels两个子目录这是ultralytics约定的默认查找方式。room-water-data/ ├── train/ │ ├── images/xxx.jpg │ └── labels/xxx.txt └── val/ ├── images/xxx.jpg └── labels/xxx.txt对应room_water.yaml内容如下# 积水检测数据集配置 path: /home/user/room-water-data # 数据集根目录改成你自己的绝对路径 train: train/images val: val/images names: 0: water然后训练脚本只需要这么短# train_water.py from ultralytics import YOLO model YOLO(yolov8n.pt) model.train( dataroom_water.yaml, epochs100, imgsz640, batch16, patience20, lr00.002, mosaic0.0, hsv_h0.02, hsv_s0.6, hsv_v0.6, projectruns/water_detect, nameexp_v8n, )逐个说参数。epochs100看着不长但有patience20兜底验证集mAP连续20轮不涨就早停不会傻跑到过拟合。batch16是16G显存常见的安全值显存小就调成8训练集只有280张左右时batch太大反而让每个epoch更新次数太少。lr00.002是小数据集的关键默认0.01在几百张图上极易震荡损失曲线像锯齿。mosaic0.0是我专门为积水任务关掉的增强mosaic会把四张图拼一起图中原本占大面积的整片积水被切成四块语义破坏比增强收益大。hsv_h/s/v则是对光照和反光做补偿室内积水在日光、暖光、LED冷光下颜色差别很大把饱和度与明度扰动调高等于免费扩充了这部分多样性。4.3 训练后的三个观察点别只看mAP一个数训练结束后先看runs/water_detect/exp_v8n/下生成的results.csv和曲线图。第一看train/box_loss和val/box_loss是不是同步下降如果训练loss一路走低、验证loss中途反弹就是过拟合的典型信号。第二看metrics/mAP50(M)和metrics/mAP50-95(M)对积水这种大目标mAP50更说明“框找没找到”mAP50-95则考验框和真实水迹贴合度小数据下后者数值难看很正常不要因此否定模型。第三把val_batch0_pred.jpg这种预测可视化图翻出来直接看模型把框画在了哪里。训练代码里的yolov8n.pt是官方预训练权重第一次运行会自动下载之后会基于它做迁移学习。357张图从零训练基本没有可行性迁移学习是小数据集能跑起来的根本原因——模型已经知道“纹理”“边缘”“区域”这些低级特征只需要在顶部分类头上适应积水形态。提示训练时不要开cacheTrue硬性全量缓存如果内存不够数据加载反而变慢。小数据集开workers4就够了数据量不大时多进程开销可能盖过加载收益。5. 小样本积水训练避坑盘点5个真实翻车现场5.1 loss为nan问题出在标签而不是网络结构现象训练第一个batch就出现nan或者跑了几十个batch后loss突然变成nan控制台伴随大量坐标越界警告。原因绝大多数情况是标签文件里有非法行。比如txt里出现了空行、某行只有3个字段、宽高被写成负数、归一化坐标跑到1.5或者图片路径与标签文件名没对齐dataloader随机采样时撞上了脏标签一反向传播梯度就爆掉。357张数据只要有一张坏的训练过程就可能在那张图上爆炸。解决跑一遍第2章的自检脚本把报错文件找出来。修完标签后重新训练。如果自检通过还出现nan再把batch调小或把imgsz从640降到512排除显存溢出导致的数值异常。5.2 epochs拉满导致过拟合验证mAP先升后崩现象训练到30轮时验证mAP接近0.650轮后不升反跌到80轮掉到0.45以下训练loss却还在稳步下降。验证集里的图明明和训练集很像但预测框开始乱飘同一个水洼画三四个重叠框。原因数据量太少网络在后期直接把训练样本“背”下来了泛化能力被过拟合吞掉。早停没生效是因为我把patience设太大给了它足够的“回光返照”空间。解决patience设为15到20让它连续十几轮没进步就停同时把lr0再调小到0.001配合余弦退火让学习率慢慢衰减。另一个有效手段是把mosaic0.0保留、hsv_v0.6继续调高用增强强迫模型别死记像素。5.3 大水渍漏检、小水渍反而不漏大目标的锚框问题现象验证集里墙角一大片积水漏检或者框只框住水迹中间最深的一小条反而桌面上一摊面积很小的水渍能被框对。粗细看mAP50数据还行拉出来逐张看图才发现漏的都是大目标。原因积水目标占图比例很大时YOLO的下采样特征图对整片水渍的编码不够充分而且水渍边缘到中心的灰度渐变让网络把“高亮核心”当成了完整目标。还有标注本身不够贴边xml里框的是整片泛光区域学习时正样本包含太多非水背景导致边界置信度一直被拉低。解决训练时关mosaic只是第一步更实用的是推理阶段用滑窗预测把大图切成几块分别送进模型再把结果合并小模型也能找回大目标。如果这个场景要长期做考虑走分割路线用mask标注代替bbox水渍这种非刚性目标回归边界更自然。5.4 白天效果尚可、夜间全漏光照单一化的幻觉现象白天窗户光下的积水识别得不错同一间房晚上开着暖色吸顶灯地面明明有积水模型一点反应都没有。更隐蔽的是阳光照在地砖上的反光区域会被误报成水。原因很直接训练集里绝大多数图都在日间或明亮的灯光下拍摄积水在暖光下呈黄褐色、反光强度也和白天完全不同模型学到的“水”其实是“带高光的偏冷色块”换光照就失效。解决把hsv_h从0.02加到0.05hsv_v从0.6加到0.8先让模型在颜色空间上见过更多变体然后专门从监控视频里抽夜间的几十张图补齐训练集哪怕手工标二三十张收益也比继续刷日间图大得多。验证集里单独留一个夜间子集训练后分开统计别让日间的高指标掩盖夜间失灵。5.5 类别索引错位模型学会了“永远输出第0类”现象训练完看混淆矩阵永远只有第0类一个格子预测时所有框都叫waterprecision还挺高。看起来任務成了但当你往数据里加第二个类别时新类完全不会出现。原因转换阶段CLASS_MAP顺序和data.yaml里names顺序没对齐或者有人手动改过classes.txt里的排序。YOLO对索引错位不报错它只会忠实地把“第1类的目标”全部当成“第0类”训练最终模型成了只会说一个词的复读机。解决用2.3的自检脚本核对全部txt的索引同时截一张预测图用model.names打印索引对应的类名逐个确认。增加一个类别后第一时间跑一次包含新旧类的验证确认混淆矩阵对角线完整。6. 进阶验证技巧用热力图确认模型没在学地砖反光6.1 先读曲线再谈效果别抱着best.pt就上线训练出的runs/water_detect/exp_v8n/里有一张F1_curve.png横轴是置信度阈值纵轴是F1分数。这张图的用途是替你找到“最优阈值”默认0.25的置信度不一定适合积水场景如果F1曲线峰值在0.4附近推理时就应该把conf阈值调上去降低地砖反光带来的误报。用model.predict(sourceIMG_0387.jpg, conf0.4)就能生效。这一步在样本少、类别只有一类时尤其重要因为单类检测的precision和recall平衡完全靠阈值调节。6.2 用热力图当“谎言检测器”看模型到底在看哪357张图训练出的模型最怕的不是精度低而是学了一堆不相关的捷径特征。用Grad-CAM对验证集里的积水图生成类别激活热力图叠加后如果高亮区域集中在积水本身模型是可靠的如果高亮集中在门框、踢脚线、地砖拼缝这类高频纹理位置说明模型在拿“垂直于地面的线条”冒充积水边缘。这个验证对积水任务非常关键因为水面反光会形成和地砖缝类似的边缘梯度模型极易混淆。发现热力图跑偏时不要急着调参数先去补那些“有积水但不带强反光边缘”的样本。6.3 后续扩充数据的最小可行路径小数据集的价值定位是“快速验证任务可行性”不是“一步到位商用”。357张图对应的最优扩增路线是先把当前模型在真实监控帧上跑一遍把漏检和误报的图抽出来人工复核补齐第二版训练集。这样每一张新图都是难例比随机采集更有价值。扩充到500张以上时再把模型从nano换成small重新评估一次mAP50的提升能否覆盖推理成本的增加。我现在的习惯是拿到任何小样本目标检测数据集永远先跑自检、再训基线、再看热力图三步做完才谈调参。以前我总想着用更多epoch、更大的网络对抗数据量少结果在积水这种边界模糊的任务上翻车多次后来发现老老实实把数据质量守住比什么先进损失函数都管用。希望这套流程能帮你在积水检测这个方向上少走点弯路。本文还有配套的精品资源点击获取
返回列表