多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

基于1782张YOLO+VOC数据集的微波炉热水器等电器检测实战

基于1782张YOLO+VOC数据集的微波炉热水器等电器检测实战 简介这是一份面向目标检测学习者的电器类数据集聚焦微波炉、热水器、水龙头、插座、锅炉五类常见家电与卫浴设备适合用于YOLO系列模型的训练、微调与算法验证也可作为课程设计或毕业项目的实战素材。压缩包共2000个文件包含1782张jpg图片、1782个VOC格式xml标注以及对应的YOLO格式txt标签另有说明文件整体约69.46MB目录分为图片、xml、txt三个文件夹结构清晰便于直接接入训练流程。数据集共标注1966个矩形框其中水龙头647、热水器557、微波炉553、插座172、锅炉37类别分布不均可用于考察模型对长尾类别的识别能力。图片分辨率清晰且未做增强标注准确合理能帮助读者省去繁琐的采集与标注环节快速搭建可复现的检测实验理解VOC与YOLO两种格式的转换与使用差异。目前已有54人学习下载。1. 微波炉热水器水龙头插座锅炉检测1782 张 YOLOVOC 数据集到底能干什么厨房和阳台的电器识别看起来是个小场景实际做起来比想象中麻烦。微波炉、热水器、水龙头、插座、锅炉这五类目标尺寸跨度大、遮挡严重、背景杂乱而且插座这种小目标在 640 分辨率下经常只剩十几个像素。我拿到这份 1782 张的 YOLOVOC 双格式数据集时第一反应不是直接开训而是先确认它到底覆盖了哪些真实工况——因为目标检测数据集的质量决定了后面所有调参工作的上限。这份数据集的核心价值在于它同时提供了 YOLO 格式的 txt 标注和 VOC 格式的 xml 标注意味着你可以在 YOLOv5/v8 和 Faster R-CNN/SSD 之间自由切换不用自己写格式转换脚本。1782 张的体量不算大但对于五类室内电器检测来说如果标注质量过关配合迁移学习和合理的数据增强mAP0.5 做到 0.85 以上是完全可行的。适合谁用做智能家居设备盘点、房屋租赁平台电器核验、保险定损辅助识别、以及想入门目标检测但苦于没有垂直领域数据集的工程师。这一章不展开技术细节只把结论说清楚这个数据集能让你在一天内跑通一个可用的电器检测基线但想真正落地你需要理解 VOC 和 YOLO 格式的差异、五类目标的尺度分布、以及小目标漏检的根因。后面几章按「格式转换 → 训练配置 → 避坑 → 进阶技巧」的顺序展开每一步都有可复现的命令和参数说明。2. 从 VOC 到 YOLO1782 张数据集的格式拆解与转换脚本2.1 先搞清楚 VOC 和 YOLO 标注的本质差异VOC 格式的 xml 文件里每个目标用bndbox记录xmin, ymin, xmax, ymax四个绝对像素坐标外加name标签名。YOLO 格式的 txt 文件里每行是class_id x_center y_center width height全部归一化到 0~1 之间。这个差异看起来简单但转换时有三个地方容易翻车坐标越界、类别映射错位、以及空标注文件处理。我一般会先写一个校验脚本统计每张图的标注数量、类别分布、以及边界框的宽高比。这一步能提前发现标注错误比如某个 xml 里xmax小于xmin或者类别名拼写不一致。下面这个脚本直接跑在数据集根目录输出一份统计报告。import os import xml.etree.ElementTree as ET from collections import Counter def audit_voc(voc_dir): class_counter Counter() box_sizes [] bad_files [] for xml_file in os.listdir(voc_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(voc_dir, xml_file)) root tree.getroot() for obj in root.findall(object): name obj.find(name).text class_counter[name] 1 bbox obj.find(bndbox) xmin int(bbox.find(xmin).text) ymin int(bbox.find(ymin).text) xmax int(bbox.find(xmax).text) ymax int(bbox.find(ymax).text) if xmax xmin or ymax ymin: bad_files.append(xml_file) continue box_sizes.append((xmax - xmin, ymax - ymin)) print(类别分布:, dict(class_counter)) print(异常文件数:, len(bad_files)) if box_sizes: widths [w for w, h in box_sizes] heights [h for w, h in box_sizes] print(f框宽范围: {min(widths)} ~ {max(widths)}, 均值 {sum(widths)/len(widths):.1f}) print(f框高范围: {min(heights)} ~ {max(heights)}, 均值 {sum(heights)/len(heights):.1f}) audit_voc(./VOC/Annotations)这段代码的逻辑很直接遍历所有 xml统计类别频次同时检查边界框的合法性。参数说明——voc_dir指向 VOC 格式的 Annotations 目录如果你的数据集把 xml 和图片混在一起需要先分离。跑完之后你会得到一份类别分布如果发现某个类别样本数少于 100后面训练时就要考虑过采样或者 focal loss。2.2 转换脚本从 xml 到 txt 的完整实现确认标注无误后下一步是转成 YOLO 格式。我习惯把图片和标签分目录存放结构如下images/train、images/val、labels/train、labels/val。转换脚本需要同时处理训练集和验证集的划分一般按 8:2 随机切分。import os import random import xml.etree.ElementTree as ET import shutil CLASSES [microwave, water_heater, faucet, socket, boiler] CLASS_MAP {name: idx for idx, name in enumerate(CLASSES)} def convert_bbox(size, box): dw 1.0 / size[0] dh 1.0 / size[1] x_center (box[0] box[1]) / 2.0 * dw y_center (box[2] box[3]) / 2.0 * dh w (box[1] - box[0]) * dw h (box[3] - box[2]) * dh return x_center, y_center, w, h def convert_voc_to_yolo(voc_img_dir, voc_ann_dir, out_img_dir, out_lbl_dir, split_ratio0.8): os.makedirs(out_img_dir, exist_okTrue) os.makedirs(out_lbl_dir, exist_okTrue) all_xml [f for f in os.listdir(voc_ann_dir) if f.endswith(.xml)] random.shuffle(all_xml) split_idx int(len(all_xml) * split_ratio) train_xml all_xml[:split_idx] val_xml all_xml[split_idx:] for phase, xml_list in [(train, train_xml), (val, val_xml)]: for xml_file in xml_list: tree ET.parse(os.path.join(voc_ann_dir, xml_file)) root tree.getroot() size root.find(size) w_img int(size.find(width).text) h_img int(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in CLASS_MAP: continue cls_id CLASS_MAP[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) xc, yc, bw, bh convert_bbox((w_img, h_img), (xmin, xmax, ymin, ymax)) lines.append(f{cls_id} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) img_name xml_file.replace(.xml, .jpg) src_img os.path.join(voc_img_dir, img_name) if not os.path.exists(src_img): continue shutil.copy(src_img, os.path.join(out_img_dir, phase, img_name)) with open(os.path.join(out_lbl_dir, phase, xml_file.replace(.xml, .txt)), w) as f: f.write(\n.join(lines)) convert_voc_to_yolo(./VOC/JPEGImages, ./VOC/Annotations, ./images, ./labels)逻辑说明convert_bbox把绝对坐标转成归一化中心点加宽高这是 YOLO 的标准格式。split_ratio0.8控制训练验证比例你可以改成 0.9 如果数据量特别少。注意CLASSES列表的顺序必须和后面训练时的data.yaml完全一致否则类别 ID 会错位这是血泪教训。转换完成后检查labels/train下的 txt 文件数量是否和images/train一致不一致说明有图片缺失。2.3 生成 data.yaml 和目录结构校验YOLOv5/v8 训练需要一个data.yaml描述文件里面写清楚路径和类别名。我一般会加一个校验步骤确保所有标签文件的类别 ID 都在 0~4 之间。path: ./dataset train: images/train val: images/val nc: 5 names: [microwave, water_heater, faucet, socket, boiler]参数说明nc是类别数必须和names长度一致。path是数据集根目录train和val是相对路径。如果你的图片是 png 格式需要在训练脚本里改img_formats参数或者统一转成 jpg。校验脚本很简单遍历所有 txt检查第一列是否在 0~4 之间同时检查每行是否有 5 个字段。提示转换完成后用labelImg或cv2随机抽 10 张图可视化标注框确认没有偏移。我遇到过 xml 里size字段和实际图片分辨率不一致的情况导致所有框偏移这种问题不可视化根本发现不了。3. YOLOv8 训练配置1782 张图怎么设参数才不浪费3.1 模型选型n/s/m 三个尺度的取舍1782 张图属于小数据集我一般不会直接上 YOLOv8x。参数量太大的模型在少量数据上容易过拟合验证集 loss 会先降后升。我的建议是先用yolov8n跑一个基线看 mAP0.5 能不能到 0.7如果能再换yolov8s微调通常能涨 3~5 个点yolov8m在 1782 张图上提升有限除非你做了大量数据增强。训练命令如下关键是imgsz和batch的配合。如果你用 8GB 显存的卡yolov8n可以跑imgsz640, batch16yolov8s建议batch8yolov8m只能batch4或者用梯度累积。yolo detect train \ modelyolov8s.pt \ data./dataset/data.yaml \ epochs150 \ imgsz640 \ batch8 \ lr00.01 \ lrf0.01 \ patience30 \ augmentTrue \ mosaic1.0 \ mixup0.1 \ copy_paste0.1 \ device0 \ project./runs \ nameappliance_v1参数说明lr0是初始学习率小数据集建议 0.01 而不是默认的 0.01 以上lrf是最终学习率因子控制余弦退火的下限patience30表示 30 个 epoch 没有提升就早停防止过拟合mosaic1.0开启马赛克增强对小数据集很关键mixup0.1和copy_paste0.1是额外增强copy_paste对插座这种小目标特别有用因为它会把目标复制到其他位置。3.2 小目标漏检的根因和三个必调参数插座和水龙头在 640 分辨率下经常只有 20~30 像素YOLOv8 的 P3 特征图 stride 是 8也就是说 640 输入下 P3 是 80x80每个格子对应 8 像素。如果目标小于 8 像素理论上就检测不到。实际中 20 像素的目标在 P3 上只有 2~3 个格子响应很容易被 NMS 滤掉。三个必调参数第一imgsz从 640 提到 960 或 1280小目标召回率会明显提升但显存占用翻倍第二boxloss 的权重YOLOv8 默认是 7.5可以提到 10.0 让模型更关注定位精度第三conf阈值在推理时从 0.25 降到 0.15先把召回拉上来再用 NMS 的iou阈值控制误检。from ultralytics import YOLO model YOLO(./runs/appliance_v1/weights/best.pt) results model.predict( source./test_images, imgsz960, conf0.15, iou0.5, max_det100, saveTrue, save_txtTrue )这段推理代码的关键是imgsz960和conf0.15的组合。max_det100防止一张图里检测框太多导致后处理变慢。save_txtTrue会输出 YOLO 格式的预测结果方便你后续做误检分析。3.3 数据增强策略哪些增强对电器检测有效不是所有增强都适合这个场景。mosaic和mixup是通用的但copy_paste对插座特别有效因为插座经常被遮挡复制粘贴可以模拟不同遮挡情况。hsv_h、hsv_s、hsv_v三个颜色增强参数建议保持默认的 0.015、0.7、0.4电器颜色相对固定过度颜色抖动反而有害。flipud垂直翻转要慎用因为热水器和锅炉通常不会倒置翻转后产生的样本不符合真实分布。我一般会做一个消融实验先跑一组只开mosaic的基线再跑一组加copy_paste的对比验证集 mAP。如果copy_paste带来 2 个点以上的提升就保留否则关掉减少训练时间。4. 避坑与排查1782 张电器数据集训练中最容易翻车的 5 个地方4.1 类别 ID 错位导致 mAP 全零现象训练 loss 正常下降但验证集 mAP 一直是 0。原因data.yaml里的names顺序和转换脚本里的CLASSES不一致比如转换时是[microwave, water_heater, faucet, socket, boiler]但 yaml 里写成了[boiler, socket, faucet, water_heater, microwave]。解决训练前用脚本检查标签文件第一列的最大值是否等于nc-1同时打印data.yaml的names和转换脚本的CLASSES做对比。4.2 图片和标签不匹配导致训练中断现象训练到某个 epoch 报FileNotFoundError或AssertionError。原因images/train里有图片但labels/train里没有对应的 txt或者反过来。解决写一个配对检查脚本遍历图片目录检查每个图片文件名对应的 txt 是否存在不存在的直接删除图片或补空标签。空标签文件在 YOLO 里是合法的表示这张图没有目标。4.3 验证集 loss 震荡不收敛现象训练 loss 持续下降但验证 loss 上下震荡mAP 波动超过 5 个点。原因验证集太小1782 张按 8:2 切分后验证集只有 356 张五类目标平均每类 70 张统计噪声大。解决改用 5 折交叉验证或者把split_ratio调到 0.9验证集只留 178 张但增加评估频率。另一个办法是固定随机种子确保每次验证集一致。4.4 插座小目标召回率低于 0.5现象微波炉和热水器的 AP 都在 0.9 以上但插座 AP 只有 0.4~0.5。原因插座在 640 分辨率下像素太少P3 特征图响应弱。解决把imgsz提到 960同时在data.yaml里加一个kpt_shape没用正确做法是在训练时加overlap_maskFalse并调整boxloss 权重。如果还不行考虑用切片推理把大图切成 4 块分别检测再合并。4.5 推理时误检锅炉为热水器现象锅炉和热水器外观相似模型经常混淆。原因两个类别的训练样本在视觉特征上重叠度高尤其是圆柱形锅炉和圆柱形热水器。解决在data.yaml里加names的别名没用需要在训练时增加这两个类别的区分度。我一般会收集 50 张误检样本手动标注后加入训练集做一轮 hard negative mining。另外推理时把iou阈值从 0.5 提到 0.6减少重叠框的合并错误。5. 进阶技巧用 1782 张图榨出更高精度的三个实操方法5.1 用预训练权重做分层微调YOLOv8 在 COCO 上预训练的权重已经学到了通用物体特征但电器检测需要更细粒度的纹理。我的做法是先冻结 backbone 训练 20 个 epoch让 head 适应新类别然后解冻所有层用更小的学习率 0.001 再训 100 个 epoch。这样比直接端到端训练收敛更快最终 mAP 通常高 2~3 个点。# 第一阶段冻结 backbone yolo detect train modelyolov8s.pt data./dataset/data.yaml epochs20 freeze10 lr00.01 # 第二阶段全量微调 yolo detect train model./runs/appliance_v1/weights/best.pt data./dataset/data.yaml epochs100 lr00.001freeze10表示冻结前 10 层YOLOv8s 的 backbone 大约 10 层。第二阶段加载第一阶段的最好权重学习率降到 0.001避免破坏已学到的特征。5.2 用 TTA 和模型集成提升推理精度测试时增强TTA对电器检测有效因为不同尺度的目标需要不同的感受野。YOLOv8 内置了 TTA只需要在推理时加augmentTrue。另外训练三个不同尺度的模型n/s/m推理时用加权框融合WBF合并结果mAP 能再涨 1~2 个点。from ultralytics import YOLO import numpy as np models [YOLO(./runs/v1/weights/best.pt), YOLO(./runs/v2/weights/best.pt)] all_boxes [] for model in models: results model.predict(source./test_images, imgsz960, conf0.15, augmentTrue) for r in results: boxes r.boxes.xyxy.cpu().numpy() scores r.boxes.conf.cpu().numpy() classes r.boxes.cls.cpu().numpy() all_boxes.append(np.column_stack([boxes, scores, classes])) # 这里用简单的 NMS 合并实际可以用 WBF merged np.vstack(all_boxes)这段代码只是演示思路实际部署时建议用ensemble-boxes库做 WBF效果比 NMS 好。参数说明augmentTrue会做水平翻转和多尺度推理推理时间增加约 3 倍但精度提升明显。5.3 用主动学习找漏检样本1782 张图不可能覆盖所有场景主动学习是性价比最高的提升方式。具体做法用当前模型推理一批未标注图片挑出置信度在 0.3~0.6 之间的样本这些是模型最不确定的。人工标注这 100~200 张加入训练集重新训练。我一般迭代 2~3 轮每轮加 100 张mAP 能涨 5 个点左右。注意主动学习的标注成本不低建议优先标注插座和锅炉这两个难类别的样本。微波炉和热水器已经够好了再加样本边际收益很小。最后说一个我自己的习惯每次训练完我都会把验证集里 mAP 最低的 20 张图单独拿出来看分析是标注问题还是模型问题。这个习惯帮我省了很多盲目调参的时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表