隔离开关状态识别:50张VOC+YOLO数据集训练YOLOv8全流程
简介面向电力设备巡检与隔离开关状态识别任务的小型标注数据集适合目标检测初学者做模型训练、算法对比或数据增广练习。包内包含50张jpg原图每张均配Pascal VOC格式xml与YOLO格式txt标注类别为close和open共50个标注框close 42框、open 8框由labelImg按矩形框标注可直接接入常见检测框架。txt文件为不带分割路径的YOLO标注xml文件则记录图片尺寸、目标类别与矩形框坐标两种格式一一对应便于复核和转换。资源共152个文件52个txt文件、50个xml文件、50个jpg图片压缩包13.79MB体量轻、结构简单适合快速下载与离线实验。目前已有350人学习下载作为电力视觉项目起步样本集或教学示例都很合适。需注意数据集仅保证标注准确合理不对训练模型精度作任何承诺使用时应结合实际场景评估效果。1. 这份 50 张 2 类别的隔离开关状态识别检测数据集到底能拿来做什么变电站运维里有个很实在的需求用摄像头代替人工去确认隔离开关的“合闸 / 分闸”状态。你拿到的这份“电力场景隔离开关状态识别检测数据集VOCYOLO格式50张2类别.7z”就是干这件事的最小样本50 张电力场景照片两个类别同时给了 VOCXML和 YOLOtxt两套标注。它不是一个能直接训练出生产模型的弹药库而是一个把“格式理解、转换、训练、验证”整条链路跑通的样板。反直觉的结论是直接拿它硬训会翻车必须在预训练权重基础上关掉一大半数据增强才有可能榨出能用的精度。适合谁刚入坑 YOLO 想练流程的人或者手头有自己现场照片、想照着这套格式做数据集的人。50 张图少归少但用来学“怎么让 YOLO 吃下电力场景数据”已经足够。2. 解压、盘点与格式对齐先把 VOCYOLO 两套标注统一成一套能训的2.1 先解压不要急着开工拿到 .7z 压缩包第一步不是打开文件夹看图片而是把它解压到一个固定工作目录并且确认解压完整。Windows 上常见的压缩工具是 7-Zip传到 Linux 服务器上训练时一般用 p7zip 的命令行工具处理。mkdir -p switch_dataset 7z x 电力场景隔离开关状态识别检测数据集VOCYOLO格式50张2类别.7z -oswitch_dataset find switch_dataset -type f | wc -l7z x表示保留压缩包内目录结构完整解压-o指定输出目录注意-o后面紧跟路径、不加空格。最后一行find统计文件总数用于和压缩包里预期的文件数做核对。50 张图、每张至少 1 个 XML 标注和 1 个 txt 标注再加上可能的划分文件总数应该在 150 到 200 之间。如果差很多说明解压过程出问题比如磁盘写满、压缩包损坏。实际解压时如果遇到中文文件名乱码多半是压缩包在 Windows 下用 GBK 编码打包Linux 端按 UTF-8 解出来就花了。这种情况不要硬掰直接在 Windows 或 macOS 图形端解压后重新用 zip 或 tar 打包再传到服务器反而更快。2.2 盘点目录VOC 和 YOLO 两套标注先对齐解压完先做一次盘点。常见布局是VOC/下放着Annotations、JPEGImages、ImageSets/MainYOLO/下放着images和labels。但实际压缩包可能把两套格式混在一个目录里所以盘点逻辑要按文件类型去扫而不是按固定目录名去猜。from pathlib import Path root Path(switch_dataset) xml_files sorted(root.rglob(*.xml)) img_files sorted( list(root.rglob(*.jpg)) list(root.rglob(*.jpeg)) list(root.rglob(*.png)) ) # 只统计 labels 目录下的 txt过滤掉 ImageSets 里的划分文件 label_dir root / YOLO / labels if not label_dir.exists(): label_dir root / labels txt_files sorted(label_dir.rglob(*.txt)) if label_dir.exists() else [] xml_names {p.stem for p in xml_files} img_names {p.stem for p in img_files} txt_names {p.stem for p in txt_files} print(f图片数量: {len(img_names)}) print(fVOC标注数量: {len(xml_names)}) print(fYOLO标注数量: {len(txt_names)}) print(有图无VOC标注:, img_names - xml_names) print(有图无YOLO标注:, img_names - txt_names) print(两套标注文件名不一致:, (xml_names ^ txt_names) img_names)这段代码的价值在于把“两套标注是否对齐”变成可量化的检查项。文件名我统一用Path.stem取前缀不带扩展名这样.xml、.txt、.jpg只要前缀相同就能对应上。运行结果里比较理想的是三个数量都等于 50差集为空。如果出现“有图无 VOC 标注”或者“两套标注文件名不一致”说明压缩包本身有缺失后面训练时 YOLO 会自动丢弃没有标注的图片直接导致有效训练样本减少而这个过程通常不会报错。2.3 用一份脚本把 VOC 的 XML 转成 YOLO 的 txt不管压缩包里给的 YOLO 标注是否完整我拿到手都会自己跑一遍转换脚本。原因很简单YOLO 训练只需要 txt而 txt 里写的是归一化坐标任何一步换算出错都直接影响模型学到的框用自己的脚本重转一遍等于把坐标计算过程重新核对一次。import cv2 from pathlib import Path import xml.etree.ElementTree as ET # 类名到 ID 的映射以数据集中实际类名为准 class_map {close: 0, open: 1} def voc_to_yolo(xml_path, img_dir, out_dir): tree ET.parse(xml_path) root tree.getroot() # 不信任 XML 里的 filename 字段按前缀逐一尝试图片扩展名 img_path None for ext in (.jpg, .jpeg, .png, .bmp): candidate img_dir / (xml_path.stem ext) if candidate.exists(): img_path candidate break if img_path is None: print(f找不到图片: {xml_path.stem}) return 0 img cv2.imread(str(img_path)) if img is None: print(f图片读取失败: {img_path}) return 0 h, w img.shape[:2] lines [] for obj in root.findall(object): cls obj.find(name).text if cls not in class_map: print(f未知类名 {cls}跳过) continue bnd obj.find(bndbox) x1 float(bnd.find(xmin).text) y1 float(bnd.find(ymin).text) x2 float(bnd.find(xmax).text) y2 float(bnd.find(ymax).text) # VOC 允许框越界YOLO 不允许先裁剪到图像范围内 x1 max(0, min(w, x1)); y1 max(0, min(h, y1)) x2 max(0, min(w, x2)); y2 max(0, min(h, y2)) if x2 x1 or y2 y1: print(f无效框被跳过: {xml_path.stem}) continue # 转归一化中心坐标和宽高 cx ((x1 x2) / 2) / w cy ((y1 y2) / 2) / h bw (x2 - x1) / w bh (y2 - y1) / h lines.append(f{class_map[cls]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) if lines: out_path out_dir / (xml_path.stem .txt) out_path.write_text(\n.join(lines)) return len(lines)坐标换算的逻辑是VOC 的bndbox给的是左上角和右下角的像素坐标(x1,y1,x2,y2)YOLO 要求的是class x_center y_center width height其中中心坐标和宽高都要除以图像宽高做归一化。脚本里先用cv2.imread拿真实图像尺寸而不是用 XML 里size字段的值因为 XML 的尺寸字段偶尔会和实际图片不一致。cx ((x1 x2) / 2) / w这一步就是把像素中心点换算成 0 到 1 之间的比例x1 max(0, min(w, x1))是裁剪越界坐标标注时手滑把框画到图像外面很常见不裁剪会得到大于 1 的宽高比训练时直接报错或产生 NaN 损失。转换完务必检查生成的 txt 行数如果某个文件被跳过了打印信息里会直接告诉你原因。2.4 划分 train/val 并可视化抽查50 张图做训练验证集不能太多。我一般按 8:2 划分也就是 40 张训练、10 张验证不再单独留测试集。用小数据集时验证集只是用来监控过拟合没必要再切一块测试集出来浪费样本。import random import shutil from pathlib import Path img_dir Path(switch_dataset/YOLO/images) label_dir Path(switch_dataset/YOLO/labels) imgs sorted(img_dir.glob(*.jpg)) random.Random(42).shuffle(imgs) val_imgs imgs[:10] train_imgs imgs[10:] for split, split_imgs in [(train, train_imgs), (val, val_imgs)]: (img_dir / split).mkdir(parentsTrue, exist_okTrue) (label_dir / split).mkdir(parentsTrue, exist_okTrue) for img in split_imgs: shutil.move(str(img), str(img_dir / split / img.name)) label label_dir / (img.stem .txt) if label.exists(): shutil.move(str(label), str(label_dir / split / label.name))固定random.seed(42)是为了保证每次划分结果一致方便复现。划分后images/train、images/val、labels/train、labels/val四个目录各就各位YOLO 训练时直接按目录读数据。这里有个最容易忽略的点移动图片的同时必须移动同名 txt只移图片不移标注训练时那个目录里就会混入“有图无标注”的脏数据。划分之后一定做一次可视化抽查。50 张图里的每一张都值得看一遍这是小数据集仅有的好处——人工能检查完。import cv2 names {0: close, 1: open} def draw_yolo(img_path, label_path, out_path): img cv2.imread(str(img_path)) h, w img.shape[:2] for line in label_path.read_text().splitlines(): cid, cx, cy, bw, bh line.split() cid int(cid); cx float(cx); cy float(cy) bw float(bw); bh float(bh) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, names[cid], (x1, max(0, y1 - 8)), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2) cv2.imwrite(str(out_path), img) for label in sorted((Path(switch_dataset/YOLO/labels/val)).glob(*.txt)): draw_yolo( Path(switch_dataset/YOLO/images/val) / (label.stem .jpg), label, Path(preview) / (label.stem .jpg), )画框用的是 txt 里的归一化坐标重新乘回宽高得到像素坐标这一步能验证转换脚本没有把坐标算错位。抽查时重点看两类问题第一框是不是紧紧包住隔离开关的触头部分有没有框到绝缘子串第二close和open的标签对不对类别标反是这种小数据集里最常见的翻车点。3. 用 YOLOv8 在这 50 张图上训练最小可复现配置与必调参数3.1 data.yaml 怎么写目录结构、类别 ID 与 names训练前先把数据配置文件写好。YOLOv8 的 data.yaml 就是告诉训练器“数据在哪、类别有几类、名字是什么”的清单。对这份 50 张的数据集配置文件很简单path: /home/yourname/switch_dataset/YOLO train: images/train val: images/val names: 0: close 1: openpath写 YOLO 目录的绝对路径train和val可以是相对path的目录名也可以是 txt 文件路径这里直接用上一步划分好的目录最省事。names里的 0 和 1 必须和 labels 里 txt 的第一列整数严格对应。这一步最容易踩的坑是压缩包里给的 YOLO txt 里close可能是 0 也可能是 1如果不先打开 txt 看一眼就照抄网上的模板类别就会整体对调。写 yaml 前随便打开一个labels/val下的 txt看第一列是什么再回来写names。3.2 选预训练权重与训练命令50 张图从头训练一个 YOLO 模型没有任何意义必须加载预训练权重。模型规格我选yolov8n.ptnano 版参数量最小对极小数据集来说是最不容易过拟合的选择显存 8G 以上的机器也可以换yolov8s.pt但对 50 张图来说 nano 已经足够省下来的时间都花在调参上。yolo detect train \ data/home/yourname/switch_dataset/YOLO/switch.yaml \ modelyolov8n.pt \ epochs200 \ imgsz640 \ batch16 \ mosaic0.0 \ mixup0.0 \ close_mosaic0 \ lr00.001 \ optimizerAdamW \ patience30 \ seed42这条命令里真正决定成败的不是epochs而是mosaic0.0和mixup0.0。YOLOv8 默认开启 mosaic 增强训练时会随机把四张图拼成一张这对大规模数据集是神器但对只有 50 张、目标又是隔离开关这种细长结构的数据集拼接会把触头截断、把绝缘子串拼到另一张图上模型学到的是碎片而不是完整的开关状态。close_mosaic0是让训练从第一个 epoch 起就不启用 mosaic而不是训练后期才关闭。lr0从默认的 0.01 降到 0.001因为预训练权重的特征已经很好微调阶段学习率太大容易把之前学到的通用特征冲掉。patience30表示验证集指标连续 30 个 epoch 不提升就自动停防止 200 个 epoch 白跑。3.3 关掉 mosaic 之后的增强方案哪些保留哪些必须改数据增强在 50 张图上是双刃剑。增强太猛模型在增强后的失真图上学不到真实特征增强太弱50 张图又不够泛化。我按 YOLOv8 默认参数逐项说明留还是改。参数默认值建议值原因mosaic1.00.0拼图会截断细长目标破坏隔离开关整体结构mixup0.00.0默认关闭不要打开小数据集里只会让框混乱fliplr0.50.5水平翻转不改变开关的开合语义保留flipud0.00.1上下翻转同样不改变语义但会让绝缘子方向变化轻微开启增强泛化hsv_h0.0150.03模拟不同光照色偏电力场景早晚色温差异大hsv_s0.70.7保留能模拟阴天低饱和度和晴天高饱和度degrees0.05.0隔离开关安装有角度偏差5 度以内的旋转足够覆盖translate0.10.1保留模拟相机位置偏移表格里这几个参数是硬经验。hsv_h我习惯调高到 0.03因为变电站的摄像头画面经常偏色黄昏和夜间的白平衡都不稳定degrees不要超过 5隔离开关是刚性设备超过 5 度的旋转增强出来的样本在真实场景中几乎不存在反而让模型学到不真实的姿态。这套增强策略的核心思路是只在“真实可能出现的扰动”范围内做增强不做破坏性增强。yolo detect train \ data/home/yourname/switch_dataset/YOLO/switch.yaml \ modelyolov8n.pt \ epochs200 imgsz640 batch16 \ mosaic0.0 mixup0.0 close_mosaic0 \ flipud0.1 degrees5.0 hsv_h0.03 \ lr00.001 optimizerAdamW \ patience30 seed423.4 训练过程中盯什么loss 曲线和验证指标训练启动后不要干等。yolo detect train会在runs/detect/train/下实时写入日志和训练曲线用下面的命令可以随时看训练状态tail -f runs/detect/train/train_args.yaml实际要看的是两部分命令行的 loss 输出和验证集指标。YOLOv8 的 loss 拆成box_loss、cls_loss、dfl_loss三项box_loss负责框的位置回归cls_loss负责类别判断。对隔离开关状态识别来说cls_loss比box_loss更重要——框偏几个像素问题不大开合状态判错就是事故。正常走势是三个 loss 都稳步下降验证集 mAP50 缓慢上升并最终稳定如果cls_loss降不下去回到第 2 章重新检查类别标签多数时候是标错了。如果曲线骤降后 loss 变成 NaN直接进下一章看 BN 崩溃的处理。4. 避坑50 张数据集训练与 .7z 处理里的 5 个翻车现场4.1 7z 解压报错密码正确却解不出来或文件名全乱码现象在 Linux 上用7z x解压明明密码是对的却一直报 CRC 错误或文件名变成一堆乱码。原因压缩包是在 Windows 上用 7-Zip 打的中文文件名按 GBK 编码存储Linux 端默认 UTF-8 解码就会错乱部分压缩包还带了 Windows 的 Unicode 扩展头p7zip 版本太旧解析失败。解决先升 p7zip 到最新版再解压时加上-p参数显式传密码如果乱码依旧最快的办法是把压缩包拷贝到 Windows 或 macOS 上用图形界面解压再重新打成 zip 传到服务器。不要花半小时研究 Linux 端转码这不是技术问题是压缩工具跨平台兼容问题。4.2 两套标注对不上有图无标注训练集悄悄缩水现象训练启动日志里显示的图片数量不是预期的 40 张而是 32 张或者更少程序不报错。原因压缩包里的 YOLO labels 目录本身有缺失几张图的 txt 没打包进去或者文件名前缀有细微差异比如IMG_001和img_001。YOLO 训练时会静默跳过没有对应 txt 的图片不生成任何警告。解决训练前一定要跑第 2 章的盘点脚本把img_names - txt_names的结果打出来。如果找到缺失就从 VOC 的 XML 重新转一份 txt 补进去这也是我在第 2 章坚持自己重转一次标注的另一个原因。4.3 类别 ID 错位把“闭合”学成了“断开”现象训练 loss 正常下降验证 mAP 也有 0.8 以上但拿到现场一测闭合状态的开关被识别成断开并且置信度很高。原因data.yaml 里names的顺序和 txt 里的类别 ID 对不上。比如 txt 第一列 0 代表open而 yaml 里写0: close模型从头到尾都在用相反的标签训练最后学到的就是一个镜像分类器。解决训练前随机打开一个 labels txt把第一列数字和应用代码里names[0]的结果人工对齐图像可视化时把类别名打印在框上人眼扫一遍 val 的预测结果比看任何指标都直接。4.4 训练到一半 loss 变 NaNBN 崩溃现象前几十个 epoch 一切正常某个 epoch 开始cls_loss跳成nan验证 mAP 直接归零重启训练后同样位置再次翻车。原因BatchNorm 层的统计量在小 batch 下不稳定。50 张图的验证集只有 10 张如果 batch 设成 4 或更小BN 层的均值方差估计在每一步都剧烈震荡加上 mosaic 开启时梯度方向混乱最后数值溢出成 NaN。解决先把 batch 提到 16显存不够就降 imgsz 到 480同时确保mosaic0.0再把lr0降到 0.0005 重试。这里一个血泪经验是出现 NaN 不要只想着调学习率先检查 batch 大小BN 层在小 batch 上崩溃是这类小数据集的头号杀手。4.5 训练集 99%换现场照片全废背景过拟合现象train loss 降到很低验证集 mAP 也很高但把手机拍的现场照片喂进去要么漏检、要么乱框。原因50 张图大概率来自同一个变电站同一台相机背景、角度、光照高度一致模型学到的是“这个背景里有开关”而不是“开关长什么样”。解决先在数据增强里把hsv_h、degrees、translate调到我前面写的值用增强去打破背景一致性然后最实际的做法是补拍数据——这份数据集的价值是帮你把流程跑通真正要投产至少再拍 200 张不同角度、不同背景、不同光照的照片按 VOC 格式标注后并入训练集。只靠这 50 张图就认为自己有了一个能上线的检测模型是最危险的误判。5. 验证用 mAP、混淆矩阵和时序投票确认它能上现场训练结束不是终点对 50 张图训练出来的模型验证必须比训练更严格。第一步先用验证集跑一次标准评估yolo detect val \ model/home/yourname/switch_dataset/runs/detect/train/weights/best.pt \ data/home/yourname/switch_dataset/YOLO/switch.yaml \ conf0.25跑完重点看两个输出mAP50 和混淆矩阵。mAP50 能到 0.9 以上说明模型在这 10 张验证图上没有明显问题盯着混淆矩阵时注意YOLO 的混淆矩阵里会多出一个背景类bg所有被模型漏检的目标都会归到 bg所以矩阵各格数字之和大概率不等于图片总数也不等于标注总数这是正常的不要当成 bug。真正要警惕的是“close 预测成 open”这类非对角线数字偏高一旦出现回到第 4 章查类别 ID。验证集只是及格线我习惯再做一步“按时间序列验证”。隔离开关的状态判断本质上是一个时序问题单帧画面的置信度再高也可能因为遮挡、运动模糊产生误判。做法是让模型连续处理同一摄像头拍摄的若干帧在跟踪到同一个开关设备的前提下做投票# 每 5 帧对同一隔离开关的状态做一次投票至少 3 帧判定闭合才算闭合 for i in range(0, len(frame_preds), 5): window frame_preds[i:i5] close_votes sum(1 for pred in window if pred[class_id] 0 and pred[confidence] 0.5) final_state close if close_votes 3 else open这段逻辑在工程上叫“状态防抖”。单帧输出会闪烁5 帧窗口取多数能过滤掉偶发的误检代价是状态切换响应慢了一点点但对隔离开关这种动作频率极低、状态持续很久的设备来说响应慢一两秒完全可以接受换来的是误报率明显下降。跑完这一套这个 50 张数据集训练的模型才能算“验证过”而不是“训练过”。我现在的习惯是任何小数据集训出来的模型先当它不可靠跑完标准评估、混淆矩阵、时序投票这三步再谈能不能拿到现场。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →