简介:这份资源面向工业视觉检测方向的算法工程师、学生与科研人员,提供一套可直接用于YOLO系列目标检测训练的工业指针仪表数据集,帮助解决真实产线场景下仪表读数识别样本不足、标注格式不统一的问题。压缩包共2000个文件,约427.86MB,包含1000张真实场景采集的高质量图片,以及对应的voc(xml)、coco(json)与yolo(txt)三种格式标签,另附yaml配置文件、划分脚本与教程页面,方便按需转换与训练。资源还提供训练集、验证集、测试集划分脚本,以及Windows与Linux双平台的YOLO环境搭建和训练案例教程,读者可据此快速复现训练流程并迁移到自有数据。目前已有259人学习下载,适合作为工业仪表检测项目的起步数据与实操参考。
1. 工业指针仪表检测为什么值得单独做一份数据集
做过变电站、化工厂或者产线巡检项目的朋友应该都有体会:指针式仪表看着简单,真要让模型稳定读数,难点根本不在网络结构,而在数据。表盘反光、指针细、刻度密、拍摄角度偏、光照忽明忽暗,这些因素叠加起来,通用 COCO 预训练权重直接拿来用,mAP 往往掉得很难看。这份 YOLO 工业指针仪表检测数据集就是冲着这个场景来的——1000 张真实场景图片,用 labelImg 标注,同时给了 VOC(xml)、COCO(json)、YOLO(txt)三种格式标签,还附了环境搭建、训练教程和划分脚本。它适合两类人:一类是想快速跑通 YOLO 工业检测流程的新手,另一类是手里有巡检项目、需要一个能直接改的基线数据集的从业者。下面我按「拿到手怎么用 → 参数怎么设 → 哪里会翻车」的顺序拆一遍。
2. 三种标签格式怎么选:VOC、COCO、YOLO 的差异与转换逻辑
2.1 先搞清楚三种格式各自长什么样
很多人拿到压缩包第一反应是「三个文件夹,我该用哪个」。答案取决于你用的框架和训练脚本。这份数据集把三种格式分文件夹存放,本质是同一批标注的不同序列化方式,坐标含义完全一致,只是组织方式不同。
VOC 格式是每张图对应一个同名 xml,里面用<bndbox>记录xmin/ymin/xmax/ymax,坐标是绝对像素值,原点在左上角。它的好处是可读性强,labelImg 默认就吐这个格式,方便人工复核。
COCO 格式是一个大的 json,包含images、annotations、categories三个核心数组,bbox 是[x, y, width, height],注意这里是左上角坐标加宽高,不是右下角。COCO 的类别 id 从 1 开始,0 通常留给背景,这点和 YOLO 差别很大。
YOLO 格式是每张图一个 txt,每行class_id cx cy w h,全部是归一化到 0~1 的相对值,cx cy是框中心点。它没有单独的类别文件,类别靠data.yaml里的names列表顺序对应。
| 格式 | 文件形态 | 坐标类型 | 类别 id 起点 | 典型用途 |
|---|---|---|---|---|
| VOC | 每图一个 xml | 绝对像素 xmin/ymin/xmax/ymax | 按 name 字符串 | labelImg 复核、转其他格式 |
| COCO | 单个 json | 绝对像素 x/y/w/h | 从 1 开始 | mmdetection、COCO 评测 |
| YOLO | 每图一个 txt | 归一化 cx/cy/w/h | 从 0 开始 | ultralytics YOLO 系列训练 |
2.2 用脚本在三种格式之间转换
如果你拿到的原始标注是 VOC,但训练要用 YOLO,就得转。下面这个脚本是我常用的 VOC 转 YOLO 写法,逻辑清晰,也方便你按自己类别改。
import os import xml.etree.ElementTree as ET # 类别列表,顺序决定 class_id,必须和 data.yaml 的 names 完全一致 classes = ["meter"] def voc_to_yolo(xml_dir, img_dir, out_dir): os.makedirs(out_dir, exist_ok=True) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(xml_dir, xml_file)) root = tree.getroot() # 用图片实际尺寸做归一化,不要用 xml 里的 size,有些标注工具会写错 img_name = xml_file.replace(".xml", ".jpg") from PIL import Image w, h = Image.open(os.path.join(img_dir, img_name)).size lines = [] for obj in root.iter("object"): cls = obj.find("name").text if cls not in classes: continue cls_id = classes.index(cls) bnd = obj.find("bndbox") xmin = float(bnd.find("xmin").text) ymin = float(bnd.find("ymin").text) xmax = float(bnd.find("xmax").text) ymax = float(bnd.find("ymax").text) # 转成中心点 + 宽高,再归一化 cx = (xmin + xmax) / 2.0 / w cy = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") with open(os.path.join(out_dir, xml_file.replace(".xml", ".txt")), "w") as f: f.write("\n".join(lines)) voc_to_yolo("Annotations", "JPEGImages", "labels")这段代码有三个关键点值得说。第一,归一化用的是PIL读出来的真实图片尺寸,而不是 xml 里<size>节点写的宽高——labelImg 偶尔会因为图片被替换而留下过时的 size,直接用会得到错误的归一化坐标,模型学出来的框会整体偏移。第二,classes列表的顺序就是最终class_id,一旦定了就不要改,改了等于把之前训练的标签全废掉。第三,if cls not in classes这行是过滤,防止标注里混进拼写错误的类别名导致脚本崩溃。
反过来,如果你要把 YOLO 转回 VOC 做可视化复核,核心就是乘回宽高、再算 xmin/ymin/xmax/ymax,思路一样,这里不重复贴。
2.3 类别映射与 data.yaml 的对应关系
YOLO 训练时读的是data.yaml,它把图片路径、类别数、类别名绑在一起。这份数据集如果只有「指针仪表」一个类,nc就是 1。常见错误是nc写成 2 但names只有一个,训练不报错但评估时类别对不上,mAP 直接归零。
path: ./dataset train: images/train val: images/val test: images/test nc: 1 names: ["meter"]提示:
names的顺序必须和生成 txt 时classes列表的顺序一模一样,差一个位置,模型学到的就是错的类别。
3. 数据集划分脚本怎么用:train/val/test 与 ImageSets 两条路线
3.1 两种划分脚本的适用场景
压缩包里给了三个划分脚本,名字分别是「训练集、验证集、测试集划分脚本」「训练集、验证集划分脚本」和「split_train_val 生成 ImageSets 下 txt 文件划分脚本」。前两个是直接把图片和标签复制/移动到新文件夹,适合 ultralytics 这类按目录读数据的框架;第三个是生成ImageSets/Main下的 txt 列表文件,适合 VOC 风格或需要自己写 Dataset 类的场景。
选哪个取决于你的训练代码怎么读数据。用 YOLOv8/v11 官方训练入口,直接选前两个,它会按images/train、labels/train这种目录结构找。用自己写的 PyTorch Dataset,往往更习惯读一个 txt 列表,那就用第三个。
3.2 按目录划分的脚本执行流程
以「训练集、验证集、测试集划分脚本」为例,典型用法是把它和图片、标签放同级目录,然后命令行传比例。
# 假设目录结构:images/ 放所有图,labels/ 放所有 txt # 脚本会按 8:1:1 划分,并把文件复制到 train/val/test 子目录 python split_train_val_test.py --img_dir ./images --label_dir ./labels --ratio 8 1 1执行后你会得到images/train、images/val、images/test和对应的labels/...。这里有个血泪经验:脚本默认是复制不是移动,所以原始images/里的图还在,训练时如果data.yaml的train路径写错指到了总目录,会把验证集图片也当训练数据喂进去,指标虚高。跑完一定ls一下确认目录里只有划分后的子文件夹被引用。
3.3 生成 ImageSets 列表的脚本与 train_list.txt
第三个脚本生成的是ImageSets/Main/train.txt、val.txt这类文件,每行一个图片名(不带扩展名)。压缩包里还带了一个train_list.txt,可以理解成已经生成好的示例列表。如果你要自己写 Dataset,读法大致是这样:
import os from PIL import Image class MeterDataset: def __init__(self, list_file, img_root, label_root, classes): self.classes = classes with open(list_file) as f: # 去掉空行,每行是一个图片名 self.names = [x.strip() for x in f if x.strip()] self.img_root = img_root self.label_root = label_root def __getitem__(self, idx): name = self.names[idx] img = Image.open(os.path.join(self.img_root, name + ".jpg")).convert("RGB") # 标签路径按同名 txt 找 label_path = os.path.join(self.label_root, name + ".txt") boxes = [] if os.path.exists(label_path): with open(label_path) as f: for line in f: c, cx, cy, w, h = map(float, line.split()) boxes.append([c, cx, cy, w, h]) return img, boxes参数说明:list_file就是train.txt或val.txt,img_root是图片总目录,label_root是标签总目录。注意name + ".jpg"这里写死了扩展名,如果你的图片是 png,要么改代码,要么在生成列表时统一扩展名。这是新手最容易卡住的地方——列表里存的是不带扩展名的名字,读图时扩展名对不上就报 FileNotFound。
3.4 划分比例与随机种子的坑
划分脚本一般会带随机打乱。如果你没固定随机种子,每次跑划分结果都不一样,两次实验之间就没法公平对比。常见做法是在脚本里加random.seed(42)。另外,工业仪表数据如果来自不同电站或不同批次,最好按批次划分而不是纯随机——同一块表的不同角度照片如果同时进了训练和验证集,验证指标会偏乐观,实际部署到新表上就翻车。这一点在数据集只有 1000 张时尤其要注意。
4. 环境搭建与训练:Windows 和 Linux 两条线的实操要点
4.1 环境搭建教程覆盖了什么
压缩包里 Windows 和 Linux 各有一份环境搭建 html,Linux 那份还带了 Ubuntu 安装教程。按常见做法,YOLO 训练环境核心就三样:Python 3.8~3.10、PyTorch(带 CUDA)、ultralytics 或对应版本的 YOLO 仓库。教程里给的是案例流程,你照着装完,用python -c "import torch; print(torch.cuda.is_available())"验证一下,返回 True 才算 GPU 可用。
Windows 上最容易出问题的是 CUDA 版本和 PyTorch 版本对不上,装完torch.cuda.is_available()返回 False,训练默默跑在 CPU 上,1000 张图一个 epoch 能跑半小时。Linux 上相对顺,但要注意驱动版本,nvidia-smi右上角显示的 CUDA 版本是驱动支持的上限,不是你必须装的版本,装 PyTorch 时按官网给的组合来。
4.2 用案例改自己的数据集
教程的核心思路是「拿官方案例改路径和类别」。以 ultralytics 为例,训练命令就一行:
yolo detect train data=./data.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16参数逐个说:data指向你的 yaml;model是预训练权重,工业小数据集强烈建议从预训练起步,别从零训;epochs100 起步,1000 张图通常 100~200 轮收敛;imgsz640 是常规选择,如果仪表在图中占比很小,可以提到 960 或 1280,但显存要够;batch按显存调,8G 显存 640 分辨率大概能到 16。
改案例时最容易漏的是data.yaml里的路径。教程里的路径是相对官方案例写的,你换成自己的绝对路径最稳,省得被工作目录坑。
4.3 训练日志里该盯哪几个指标
跑起来之后别干等,盯box_loss、cls_loss和验证集的mAP50。box_loss前期下降快是正常的,cls_loss如果一直不降,多半是类别映射错了或者标签里有脏数据。mAP50在工业仪表这种单类任务上,数据干净的话 50 轮内应该能看到明显上升。如果 30 轮还趴着不动,先别调参,回去查标签——十有八九是归一化坐标越界或者类别 id 对不上。
注意:验证集 mAP 高不代表能读数。检测框准只是第一步,指针角度回归是另一个问题,这份数据集解决的是「找到表盘」,读数逻辑要自己接。
5. 避坑与排查:这份数据集落地时最容易翻车的五件事
5.1 标签坐标越界导致训练报错或静默失效
现象:训练启动就报AssertionError或者坐标相关异常,或者不报错但 mAP 一直是 0。 原因:VOC 转 YOLO 时如果用了 xml 里错误的<size>,归一化后 cx/cy 可能大于 1 或小于 0。YOLO 对越界坐标有的版本直接断言失败,有的版本静默裁剪,后者更坑。 解决:转换后加一步校验,遍历所有 txt,任何一行出现坐标不在 [0,1] 就打印文件名和行号,人工回去看原图。
import os for txt in os.listdir("labels"): with open(os.path.join("labels", txt)) as f: for i, line in enumerate(f): parts = line.split() if len(parts) != 5: print("字段数不对", txt, i); continue vals = list(map(float, parts[1:])) if any(v < 0 or v > 1 for v in vals): print("坐标越界", txt, i, vals)5.2 图片和标签文件名对不上
现象:训练时提示找不到标签,或者某张图被跳过。 原因:划分脚本复制文件时,图片和标签的扩展名处理不一致,比如图是.JPG大写,标签是.txt,脚本按小写匹配就漏了。 解决:统一扩展名,或者在脚本里做大小写无关匹配。跑完划分后统计一下images/train和labels/train的文件数量,两者必须相等。
5.3 类别 id 从 0 还是 1 开始搞混
现象:COCO 格式转 YOLO 后,所有框都标成了背景或者错类。 原因:COCO 的 category_id 从 1 开始,YOLO 从 0 开始。直接拿 COCO 的 id 当 YOLO 的 class_id,整体偏移一位。 解决:转换时建一个coco_id -> yolo_id的映射表,别偷懒直接减一,因为 COCO 的 id 可能不连续。
5.4 验证集混入训练集导致指标虚高
现象:验证 mAP 0.95,部署到新图上惨不忍睹。 原因:划分时没按场景/批次分,同一块表的多角度照片被随机分到了训练和验证两边。 解决:按拍摄批次或设备编号分组划分,保证验证集里的表在训练集里没出现过。1000 张图如果来自几十块表,按表划分比按图随机划分靠谱得多。
5.5 显存不足导致 batch 自动缩小
现象:设了 batch=16,日志里显示实际 batch 是 4,训练变慢。 原因:ultralytics 在显存不够时会自动降 batch,不报错但影响收敛节奏。 解决:要么降imgsz,要么换更小的模型(n 换 s 是反向,n 最小),要么老老实实把 batch 设成显存扛得住的真实值,别让它偷偷降。
6. 从检测框到指针读数:一个可验证的进阶思路
数据集把「找到表盘」这件事解决了,但工业巡检真正要的是读数。这里给一个我实际用过的衔接思路,不展开成完整项目,但足够你验证数据集训出来的模型能不能往下走。
第一步,用训好的 YOLO 模型对整图推理,拿到表盘 bbox 后裁剪出来。裁剪时往外扩 5%~10%,避免表盘边缘被切掉导致后续刻度定位失败。
第二步,在裁剪图上做表盘圆心和刻度的定位。常见做法是霍夫圆检测找表盘外圆,再按角度采样找刻度线。这一步对光照敏感,建议先做直方图均衡或者 CLAHE。
第三步,指针检测。指针是细长目标,用检测框回归角度不如用语义分割或者关键点。如果只有检测框,可以取框的中心点连线和圆心的夹角作为指针方向,但精度有限。
第四步,角度到读数的映射。指针式仪表刻度通常是线性的,记录指针指向最小刻度和最大刻度时的角度,做线性插值即可。非线性刻度的表(比如某些压力表)需要分段标定。
import cv2 import numpy as np def read_meter(crop_img, angle_min, angle_max, val_min, val_max): # 灰度 + 自适应阈值,突出指针 gray = cv2.cvtColor(crop_img, cv2.COLOR_BGR2GRAY) gray = cv2.equalizeHist(gray) # 霍夫圆找表盘 circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, gray.shape[0] / 8, param1=100, param2=30, minRadius=int(gray.shape[0] * 0.3), maxRadius=int(gray.shape[0] * 0.5)) if circles is None: return None cx, cy, r = circles[0][0] # 指针角度检测这里省略,实际用轮廓或直线检测 # 假设已得到指针角度 pointer_angle pointer_angle = 0 # 占位 # 线性映射到读数 ratio = (pointer_angle - angle_min) / (angle_max - angle_min) return val_min + ratio * (val_max - val_min)参数说明:angle_min/angle_max是指针在最小/最大刻度时的角度,需要你拿几张标注图手动量一次;val_min/val_max是仪表量程。这段代码里指针角度检测是占位的,实际项目里我用的是先二值化再找最长直线,或者干脆训一个关键点模型。之所以把它写出来,是想说明检测数据集和读数之间还隔着一层,别指望 YOLO 直接输出读数。
验证方法很简单:挑 20 张没参与训练的表盘图,人工读一遍数,再跑一遍上面流程,误差在满量程 2% 以内就算可用。超过 5% 就得回去看是指针检测偏了还是角度映射标定错了。
从那以后我每次拿到新的仪表数据集,都会先抽 10 张把标签画回原图上看一眼,确认框和类别都对,再开始训。这个习惯帮我省了无数次白跑一晚上的电费。希望帮到你。
本文还有配套的精品资源,点击获取