简介:变电站继电保护控制柜屏幕检测图像数据集面向电力系统智能化与计算机视觉研究人员,也适合算法工程师与设备运维团队,提供725张标注图像,用于训练和评测屏幕检测与识别模型。压缩包共1450个文件,包含725张JPG原图与725套PASCAL VOC格式XML标签,标签中提供了对象名称、边界框坐标以及difficult、occluded、truncated等属性,可直接用于YOLO、Faster R-CNN、Mask R-CNN等常见检测与分割框架的训练输入。资源包大小为32.29MB,便于快速下载和部署,图像来自多个不同编号设备的实测画面,有助于提升模型的泛化能力。目前已有1301人学习,既适合刚接触VOC标注与目标检测的初学者对照练习,也可支撑数据增强、迁移学习、区域提议与边界框回归等完整实验链路,甚至可以结合同一批标注扩展语义分割分析,为变电站远程监控、故障诊断和自动化巡检提供扎实的数据基础。
1. 变电站屏幕检测数据集:700张VOC图像能解决什么问题
变电站继电保护控制柜的屏幕,是设备状态的“门面”——保护装置的实时采样电流、电压、开关位置、告警信息和动作次数,全部显示在这块液晶屏上。传统做法是巡检人员走到柜前,拍照、肉眼读、抄录回填,夜间或者告警信息高频刷新时最容易抄错。近几年电力巡检的视觉方案里,“屏幕检测”成了图像数据集里的独立分支:先定位屏幕在画面中的位置,再把屏显内容切给下游的OCR或分类模块识别。这套数据就是干这个用的:700张图像、VOC标签,规模不大,但恰恰是工业小样本项目里最有代表性的资产。适合算法工程师、巡检机器人方案商,以及刚接手变电站智能化改造项目想快速验证“AI读表”可行性的团队。
2. 拆透这套VOC图像数据集:目录结构、标注内容与使用边界
2.1 VOC标签不是一种文件格式,而是一套目录约定
很多人一看到“VOC标签”就以为它等同于某个XML文件,实际拿到手后会先懵一会儿。PASCAL VOC的惯例是目录结构本身参与了数据组织,常见的布局是 JPEGImages 放原图、Annotations 放XML标注、ImageSets/Main 放划分好的训练与验证列表。
VOC/ ├── Annotations │ ├── IMG_0001.xml │ ├── IMG_0002.xml │ └── ... ├── JPEGImages │ ├── IMG_0001.jpg │ ├── IMG_0002.jpg │ └── ... └── ImageSets └── Main ├── train.txt ├── val.txt └── test.txt划分文件里存的是不带后缀的图像基名,一行一个,训练时按名字去 Annotations 和 JPEGImages 里找对应文件。这套约定的好处是标准化:不管标注工具是LabelImg还是Label Studio,导出成VOC格式后目录骨架都差不多,换人、换环境接手成本低。
真正要读的是Annotations里的XML。一个典型的屏幕检测标注文件包含图像尺寸和每个目标的类别、外接框坐标:
<annotation> <filename>IMG_0001.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>screen</name> <bndbox> <xmin>640</xmin> <ymin>320</ymin> <xmax>1280</xmax> <ymax>720</ymax> </bndbox> </object> </annotation>bndbox里四个值单位是像素,xmin/ymin是框左上角,xmax/ymax是右下角。这个坐标系就是后面转训练格式的基准。拿到数据第一件事不是急着训练,而是抽十张XML,把坐标换算回原图看一遍框位置准不准,这个习惯能省掉后面大量排错时间。
2.2 屏幕检测数据集的标注对象与类别设计
屏幕检测看起来是“找屏幕”,但工业数据集里往往不止标一类框。常见的标注思路分三层:屏幕外框本身、屏幕内的状态元素、屏显字符区域。
- screen:屏幕面板发光区域的边界,通常取屏幕内边框,避开外壳和柜体。
- alarm_light:告警指示灯、运行指示灯等亮起状态,只标亮起的位置,不标熄灭的。
- digit:屏显数字字段,比如电流值、电压值、动作次数。
- state_text:汉字或字母状态栏,例如“合位”“分位”“运行”。
类别命名没有硬性强制,但设计上要遵循两条原则。第一,类别之间边界要清晰,screen是区域级目标,digit是字符级目标,两者天然不重叠。第二,单类别标注数量要够用,700张里每张图平均一到两个对象时,分成四类是没有问题的,但分配到某个小类别只剩几十个框,训练就会明显吃力。
这套数据集如果你的应用只需要屏幕定位,那就只用screen这一类框,其他类别忽略不读。如果要做到“读状态”,就需要把alarm_light、digit这样的细粒度目标加进来。我的经验是:先明确下游任务再决定用哪些类别,不要一股脑全训,类别多了小样本数据会更稀疏。
2.3 700张小样本的数据定位与边界条件
700张图像对通用视觉数据集来说不值一提,但对工业场景一个明确任务来说,属于典型的小样本但够用的量级。关键前提是场景高度受控:设备就在柜子里,机位、角度、光照范围固定,目标类型少、结构规则。这不是自动驾驶那种开放世界,模型不需要理解“任意场景下找屏幕”,只需要在“这个变电站的柜面里找屏幕”。
按7:2:1划分,700张大约得到490张训练、140张验证、70张测试。配合320像素以上的输入尺度和预训练权重,训练出的检测器在同类站点上能跑到一个可交付的程度。需要注意边界条件:如果换站型、换屏幕尺寸、柜面玻璃反光情况完全不同,这套数据就得补充现场样本再微调,指望一个模型吃遍所有变电站不现实。这也是工业图像数据集和学术数据集本质的区别——它的价值是“现场性”而不是“规模大”。
3. 把VOC转成YOLO格式:转换脚本与四个边界坑
3.1 先读懂XML再写脚本
训练检测模型前,绝大多数人会先把VOC转成YOLO的txt格式。原因很简单:YOLO链路训练读的是归一化的中心点坐标,每行一个目标,格式是“类别编号 cx cy w h”。转换本身不难,难在转换之前你要想清楚几个细节。
VOC里的坐标是整数像素,框定义为左上和右下两个点;YOLO需要的是归一化到0~1之间的浮点数中心点加宽高。换算公式是固定的:
- cx = (xmin + xmax) / 2 / 图像宽度
- cy = (ymin + ymax) / 2 / 图像高度
- w = (xmax - xmin) / 图像宽度
- h = (ymax - ymin) / 图像高度
图像宽高用XML里size节点的值,不要自己去读图片再取尺寸,否则遇到图像被旋转过但XML未同步更新的情况时,转出来的框会错位。
3.2 转换脚本与参数说明
下面这个脚本是我处理VOC数据集时的惯用写法,只读取XML里的目标,按类别映射表输出YOLO格式的txt文件。
import os import xml.etree.ElementTree as ET voc_dir = "./VOC" # 数据集根目录 yolo_dir = "./yolo_labels" # 输出目录 cls_dict = {"screen": 0, "alarm_light": 1, "digit": 2} os.makedirs(yolo_dir, exist_ok=True) for xml_name in os.listdir(os.path.join(voc_dir, "Annotations")): if not xml_name.endswith(".xml"): continue xml_path = os.path.join(voc_dir, "Annotations", xml_name) tree = ET.parse(xml_path) root = tree.getroot() img_name = os.path.splitext(xml_name)[0] + ".jpg" size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) lines = [] for obj in root.iter("object"): cls_name = obj.find("name").text.strip() if cls_name not in cls_dict: print(f"未知类别, 跳过: {cls_name}") continue 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) # 过滤异常框: 坐标顺序颠倒或宽高为负 if xmax <= xmin or ymax <= ymin: print(f"跳过异常框: {xml_name}, bbox=({xmin},{ymin},{xmax},{ymax})") continue cx = (xmin + xmax) / 2.0 / img_w cy = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_dict[cls_name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") txt_name = os.path.splitext(xml_name)[0] + ".txt" with open(os.path.join(yolo_dir, txt_name), "w") as f: f.write("\n".join(lines))逻辑说明:脚本以Annotations目录里的xml文件为主循环,用XML文件名去找对应图像,而不是读取XML里的filename字段。这样能避开文件名不一致的问题。类别映射表cls_dict里的数字就是YOLO训练时的类别索引,后续配置yaml文件时names顺序必须与之一致,否则训练出来类别全错位。
参数怎么调:img_w和img_h必须与真实图像尺寸一致,如果XML里的size和图像实际尺寸不一致,优先以图像实际尺寸为准,或者在预处理阶段统一重命名改制。wxh坐标输出保留六位小数,精度足够YOLO训练使用,不需要更多位数。
四个边界坑:
- 坑一是类别带空格或大小写不统一,“Screen”和“screen”会被当成两个类别,转换前先统计一遍XML里所有name值。
- 坑二是图像实际后缀是png但xml里filename写的jpg,按基名匹配可以绕开这个问题。
- 坑三是目标框贴边且坐标计算出负值或大于1,YOLO训练时容忍度低,最直观的表现是loss降不下去。
- 坑四是单张图像里同一类别出现多次时,lines列表里会有多行对应,别用一次性覆盖写文件。
3.3 转换后的三件必做校验
转换完不要立刻开训练,做三个快速校验能拦截大多数问题。
校验一是可视化抽检,把YOLO格式的归一化框换算回像素,画到原图上保存再人工看一遍。写一个几十行的OpenCV脚本就能做,不用复杂框架。
import cv2 def draw_yolo_box(image_path, label_path, class_names): img = cv2.imread(image_path) h, w = img.shape[:2] with open(label_path) as f: for line in f.readlines(): parts = line.strip().split() cls = int(parts[0]) cx, cy, bw, bh = map(float, parts[1:]) 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, class_names[cls], (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) return img校验二是范围检查,统计所有txt文件里cx、cy、w、h是否都在0到1之间。这步可以直接用shell快速捞出异常文件。校验三是类别分布统计,看看每类框的数量,如果digit只有几十个框,后面训练时就要针对性增强或者收集补充数据。
4. 用这个数据集跑通第一个屏幕检测模型:数据划分、训练参数与评估指标
4.1 按7:2:1划分并固定随机种子
数据划分这步看似简单,但做法直接影响模型可信度。常见做法是按文件名字母序直接取前70%做训练,这在变电站场景里是有问题的——拍摄图像通常按巡检时间命名,同一时间段拍的是同一个柜子,按名字切片等于把同一场景分散到训练集和测试集,评估出来的指标虚高。正确做法是先把文件名打乱,再切片,还要固定随机种子保证每次划分一致。
import os import random random.seed(42) ann_dir = "./VOC/Annotations" xml_names = [f[:-4] for f in os.listdir(ann_dir) if f.endswith(".xml")] random.shuffle(xml_names) n = len(xml_names) n_train = int(n * 0.7) n_val = int(n * 0.2) train_ids = xml_names[:n_train] val_ids = xml_names[n_train:n_train + n_val] test_ids = xml_names[n_train + n_val:] os.makedirs("./VOC/ImageSets/Main", exist_ok=True) def write_list(path, items): with open(path, "w") as f: f.write("\n".join(items)) write_list("./VOC/ImageSets/Main/train.txt", train_ids) write_list("./VOC/ImageSets/Main/val.txt", val_ids) write_list("./VOC/ImageSets/Main/test.txt", test_ids) print(f"train={len(train_ids)}, val={len(val_ids)}, test={len(test_ids)}")固定seed=42的意义在于复现:别人用同一份数据跑出来什么指标,你也能跑出来,后续调参才有比较基础。划分结果大约是490张训练、140张验证、70张测试,单类别的框足够支撑一次完整训练。
4.2 从YOLOv8n起步,训练参数先照抄再调
对700张的工业小样本数据,我一般不建议一上来就上YOLOv8x或者YOLOv8m。原因很直白:模型越大越容易在小数据集上过拟合,训练集上loss能降到很低,验证集上mAP纹丝不动。反过来,YOLOv8n这种nano级别容量小,加上COCO预训练权重迁移,反而能更快收敛到可用状态。等验证集指标不再涨了,再考虑换大模型试,而不是一开始就把算力耗在过拟合上。
数据集配置文件用一个yaml就能搞定:
path: ./data train: images/train val: images/val names: 0: screen 1: alarm_light 2: digit训练用ultralytics的命令行即可,无需额外封装:
yolo detect train \ model=yolov8n.pt \ data=screen.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ patience=20参数初值怎么定:epochs从100起步,训练集只有490张,100轮已经遍历近五万次样本;patience=20表示二十个轮次验证集指标没提升就提前停,防止白跑。imgsz选640是平衡点,屏幕在画面里的占比如果低于十分之一,640下屏幕内部的小字符可能只有十几个像素,这时候可以试imgsz=1280,但显存占用会翻倍,batch要相应减半。batch=16在单张消费级显卡上比较稳妥,显存小的改成8也行。整条命令里最需要关注的是model=yolov8n.pt这个预训练权重,它提供的底层边缘和纹理特征对屏幕这类规则物体很有效,这是小样本能跑起来的关键前提。
4.3 评估指标要看全,不能只盯mAP@0.5
训练完验证,命令很简单:
yolo detect val \ model=runs/detect/train/weights/best.pt \ data=screen.yaml但指标解读要分场景。屏幕检测里screen类是矩形面板,和背景差异大,mAP@0.5通常会很高。digit这类小字符框则容易拖后腿,如果整体mAP@0.5能到0.85以上,而digit单独拿出来只有0.5,问题不在模型,在数据分布。FLOPs、参数量这些指标在这个阶段意义不大,重点看类别级别的recall和precision。
实际部署时还要额外关注置信度阈值。默认0.25的阈值会带来很多低置信度误检,巡检机器人按帧巡检时误检会被放大。我一般做法是画出PR曲线,挑准确率收敛的位置定阈值,常见定在0.4到0.5之间。验证集只有140张,单帧指标波动大,要结合PR曲线整体趋势,不要因为某一个batch的mAP波动就调整网络结构。
5. 常见问题与避坑排查:屏幕检测数据集的五条血泪经验
5.1 文件名不对应:训练一开始就报图片路径错误
训练时日志里出现“image not found”或者大量图片加载警告,启动就中断。原因通常是图像文件被批量重命名,而XML内的filename字段还停留在旧文件名。很多标注软件在重命名图片后不会同步更新XML,导致按XML内filename去找图片必失败。
解决思路:转换脚本里不读XML的filename字段,直接用XML文件名做基名去匹配图像文件后缀。图像统一转成jpg格式再入训练集,避免jpg和png混用带来的后缀匹配问题。这个坑在接手别人标注好的数据集时遇到概率极高,基本是首查项。
5.2 白天样本占九成:模型把“亮度”当成了屏幕特征
在实验室里验证集精度表现很好,拿到现场白天也还行,一到阴天或者夜间柜内照明变化,屏幕框就开始抖动。原因不复杂:数据集的VOC标签图像里九成以上是白天拍摄,屏幕发光区在暗背景下格外亮,模型学到的其实是“亮块”。现场环境光照一变,整个检测就翻车。
解决分三路并走。第一,训练阶段加大亮度增强幅度,把HSV颜色空间里V通道的扰动范围调大,让模型见惯各种亮度。第二,补充一部分傍晚、夜间、柜内灯管老化发暗的样本。第三,如果采集成本高,先做灰度图增强训练,看看模型是否依赖颜色信息——屏幕检测本身对颜色不敏感,灰度训练往往能顺带提升泛化性。
5.3 数字框太小:screen框得很准,digit一直漏检
现象很典型:screen类mAP很高,digit类recall不到0.5。原因是屏幕字符在1920x1080的全景画面里只占几十个像素,标准训练尺度下特征被压没了。小目标漏检是工业图像数据集的通病,尤其是在检测屏幕这种大目标的同时还要检测屏内小元素,本身就互相矛盾。
解决思路也要分几步。把digit这类小目标单独切成patch训练,也就是把屏幕区域裁出来作为独立训练样本;推理时用切片推理,把大图切成512x512的块再分别推理。还有一招是给digit框做过采样复制,让小目标样本在训练过程中多出现几次。
5.4 坐标越界:VOC转YOLO后框偏移到画面外
转换后做可视化回贴,发现框全部挤在右下角,或者坐标大于1。原因大多出在XML的size节点与图像真实尺寸不一致,少数情况是标注工具导出时坐标写成了归一化值而不是像素值。
解决办法已经在转换脚本里做了兜底:先比较XML的width和实际图像尺寸,不一致时打印警告;然后对xmax/ymax小于xmin/ymin的数据直接跳过,不让异常框进入训练集。转换后跑一遍归一化范围统计,能快速定位到底是哪几张图有问题。这个坑只要踩过一次,之后每一次做数据转换都会把可视化回贴当成标配动作。
5.5 漏标负样本:背景柜面被频繁误检成屏幕
模型在验证集上precision比recall低不少,把柜门、标签牌、按钮区域误检为screen。原因是训练集里每张图都有屏幕,模型没见过没有屏幕的柜面,它学到的假设是“画面里一定存在屏幕”。
解决方法是主动往训练集里塞负样本。从现场素材里挑几十张完全没有屏幕的柜面图像,或者把屏幕区域用黑色遮罩抹掉后加入训练集,对应YOLO标签为空文件。这会让模型学到“柜子在,但屏幕不在”的情况,误检率通常会显著下降。这类空标签txt文件不能省略,YOLO训练时会跳过无目标的图片,但保证它们存在可以让数据加载逻辑更稳定。
6. 进阶玩法:从屏幕检测到数值识别,让模型真正替人读表
6.1 检测加OCR的经典识别管线
检测模型输出屏幕框后,下一步是让系统“读出”屏幕上的数值。常见做法是先把screen框区域裁剪下来,做一次透视矫正,把歪斜屏幕拉正后再交给OCR模块。变电站屏幕大多是段码液晶和点阵屏,字符结构规整,OCR难度比拍照文本低,但反光和数字粘连仍会坑人。
import cv2 from paddleocr import PaddleOCR ocr = PaddleOCR(use_angle_cls=True, lang="ch") img = cv2.imread("screen_crop.jpg") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) gray = cv2.morphologyEx(gray, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))) result = ocr.ocr(gray, cls=True) for line in result: for word in line: text = word[1][0] print(text)开运算的目的就是去掉屏幕上的细小噪点,把数字笔画理干净。如果现场数字都是七段数码管风格,模板匹配往往比OCR模型更稳,但你消耗的精力也会更多。我的习惯是先跑一版通用OCR,观察失败集中字符粘连和反光的比例,再决定要不要专门训练一个数码管识别模型,而不是一上来就做定制。
6.2 切片推理:小目标问题的推理侧解法
训练侧对digit类做了过采样,推理侧也建议用切片策略。YOLOv8本身对屏显字符这种小目标不友好,把整张1920x1080的柜面图缩放成640输入后,字符可能只剩几个像素。常见做法是先用screen模型定位屏幕,再对屏幕切块放大识别,或者直接用切片推理库把整图切成多个小图分别推理。
切片推理的代价是推理时间线性增加。巡检机器人如果一帧花费100毫秒变成400毫秒,同一条巡检路线上的总耗时就会拉长。所以切片倍率要按实际目标尺寸算,屏幕内最小字符宽度低于20像素时才值得开切片。
6.3 现场补拍验证集,别让模型死在交付前
数据集只有700张图,就算训练指标再漂亮,交付前也一定要带上相机去现场补拍三五十张不参与训练的图像,作为影子验证集。补拍时不要挑日期,阴天、晴天、夜间各拍一部分,模拟机器人真实的巡检视角。这步能筛出不少只在训练集里成立的模型。
我现在的流程是:拿到类似屏幕检测图像数据集后,第一件事不是写训练代码,而是把XML框画回原图做人工抽检,确认标注质量再动手。这个习惯救过我很多次,也让我避免了一次次因为小坑重训模型的返工。数据集的真实价值不在样本数量,而在它和现场的贴近程度,这决定了模型交付后是长期跑下去,还是三天两头被叫回来救火。希望这些经验能帮你在同样的任务上少走弯路。
本文还有配套的精品资源,点击获取