简介:指针仪表数据集面向从事目标检测的开发者与研究者,聚焦指针式仪表的检测与读数提取任务,可用于训练模型定位表盘、指针方向与刻度读数,服务于自动化抄表、能源管理、智能家居等场景,适合具备一定深度学习基础、需要现成标注数据做训练与验证的读者。压缩包共785个文件、约950.3MB,其中783个jpg为实拍仪表图像,另有2个json文件负责标注与数据组织;数据已划分训练集与测试集,标注涵盖仪表位置、指针方向与度数等属性,测试集用于检验模型对未见数据的泛化效果。目前已有303人学习下载。数据集规模与划分方式较为完整,可直接用于搭建训练与评估流程、对比不同检测模型的效果,并作为指针读数识别任务的数据基础。
1. 指针仪表数据集:标注成 XML 的背后,是读数这件事没法直接标
前段时间有人拿着一批指针式仪表的照片来找我,说想要一个“拍照就能读数”的模型。我问他标签长什么样,他打开文件夹:train/、test/两个目录,每张图配一份 Pascal VOC 的 XML。他很困惑,XML 里只有表盘框和指针框,没有“3.2 MPa”这样的数字,这数据集到底怎么用。这个问题其实指向了指针仪表识别最容易踩的一个认知坎:读数不是标注出来的类别,而是一个几何量换算出来的结果。同一根指针停在 45°,在量程 0~1.6 的表上是 0.2,在量程 0~10 的表上是 1.25。所以正规的指针仪表数据集只标“表盘在哪、指针在哪、刻度区在哪”,XML 负责把这些位置关系固化下来,训练集测试集负责把泛化能力验出来,读数则由下游的角度和量程映射算出来。这篇就按一份含训练集、测试集和 XML 标注的指针仪表数据集,从拆标签、转格式、选路线一路讲到读数标定和避坑,适合刚拿到这类数据集、准备动手训第一版模型的人。
2. 拆开一份 VOC XML:字段口径、类别设计与训练集/测试集划分
拿到数据集先别急着写 DataLoader,先把一份 XML 从头到尾读一遍。指针仪表数据集的标注质量差异极大,有的标了表盘、指针、刻度三条类别,有的只标一个整表框,还有的把数字读数区域也框出来。搞清楚手里这份到底标了什么,比调参重要得多。
2.1 VOC XML 的节点结构与指针仪表场景下的实际含义
Pascal VOC 的结构很固定,annotation根节点下先是图像级信息,再是若干个object。指针仪表场景下,每个字段的落地含义如下表:
| 字段 | 典型值 | 在指针仪表数据集里的实际含义 |
|---|---|---|
folder/filename | JPEGImages/IMG_0231.jpg | 图像目录与文件名,是图文对齐的唯一键,改动会导致找不到图 |
size.width/size.height | 1920/1080 | 标注时原图尺寸,和磁盘上图片尺寸不一致就是错位事故 |
size.depth | 3 | 通道数,灰度表盘图常见为 1,读取时要注意 |
object.name | dial/pointer/scale | 类别名,决定模型输出几类,拼写必须完全一致 |
object.difficult | 0/1 | 是否为难样本,评估时可选择跳过 |
object.truncated | 0/1 | 表盘被画面裁切时置 1,边缘样本多的话这个字段有用 |
bndbox.xmin/ymin/xmax/ymax | 412/288/1106/962 | 框的左上右下像素坐标,是最容易出错的部分 |
真正需要盯的是bndbox。多数标注工具导出的 VOC 坐标是1-based 且 xmax/ymax 包含在框内,也就是说框的实际像素跨度是xmax - xmin + 1。YOLO 那套归一化坐标用的是 0-based 连续坐标,直接拿xmax当右边界会带来 1 像素的系统性偏移。1920 宽度的图上 1 像素无所谓,但指针这种细长目标,框窄的时候 1 像素能占宽度 5%,角度估计就会被带偏。我一般会在转换时统一成xmax_exclusive = xmax + 1,把口径拉齐。
类别设计上,如果这份数据集只标了pointer一类,那它只能支撑“找出指针”,读数还得靠框内二次处理;如果标了dial+pointer,可以做“先定位表盘、再在表盘内算角度”;如果有scale(刻度区)或者零刻度、满刻度标记点,那就能直接进标定环节。这一步决定后面走哪条路线,所以务必先统计一遍类别分布。
2.2 训练集/测试集怎么分才不泄漏
这份数据集已经把train和test分好了,但“分好了”不等于“分对了”。指针仪表数据集的采集特点决定了它极容易发生泄漏:同一只表在同一个工位上连拍几十张,光照、角度、背景几乎一样,如果这些图被随机打散分到训练集和测试集,模型只要记住这块表盘的纹理就能拿高分。
判断有没有泄漏,成本最低的办法是做一次图像相似度粗筛。用感知哈希或者简单的缩略图均方误差都行,把训练集和测试集两两比一遍,看看有没有高度相似的图对。有的话说明划分口径是按图片随机分的,而不是按表型号、按拍摄场景分的。
正确的划分口径是分层 + 分组:先按表型号(量程、表盘直径、刻度密度)分成若干组,再按拍摄条件(正面、侧拍、补光、反光)分层,保证测试集里至少出现一种训练集没见过的表型号。这样测出来的指标才能回答“换一块表还能不能用”,而不是“这块表我背下来没有”。
一份健康的指针仪表数据集,训练集和测试集的样本比例通常在 4:1 到 9:1 之间,测试集里各类别实例数不应少于 30 个,否则单类指标波动会大到没有参考价值。如果测试集只有十几张图,与其纠结指标,不如先补数据。
提示:划分前先统计每个表型号在训练集和测试集中的实例数,出现“某型号只在训练集里”的情况,测试指标就没有外推意义。
3. XML 转训练标签:一致性检查、YOLO txt 与关键点两套脚本
XML 是给人看的,模型吃的是张量。中间这一层转换最容易出隐性错误——不报错、不崩,训练也能跑,但指标就是上不去,回头查半天才发现是标签错了。所以转换脚本里必须带自检。
3.1 先跑一遍标注一致性检查
在写任何训练代码之前,先跑这个脚本,把 XML 里所有能查的毛病一次性捞出来:框越界、宽高为零、类别名异常、XML 与图片尺寸不符、文件名对不上。
import os import glob import xml.etree.ElementTree as ET from collections import Counter from PIL import Image def audit_voc(xml_dir, img_dir): stats = Counter() problems = [] for xml_path in glob.glob(os.path.join(xml_dir, "*.xml")): tree = ET.parse(xml_path) root = tree.getroot() fname = root.findtext("filename") img_path = os.path.join(img_dir, fname) if not os.path.exists(img_path): problems.append(("missing_image", fname)) continue # 标注尺寸 vs 实际图片尺寸,不一致说明图被裁过或缩放过 w = int(root.findtext("size/width")) h = int(root.findtext("size/height")) with Image.open(img_path) as im: real_w, real_h = im.size if (w, h) != (real_w, real_h): problems.append(("size_mismatch", fname, (w, h), (real_w, real_h))) objs = root.findall("object") stats["images"] += 1 stats["objects"] += len(objs) for obj in objs: name = obj.findtext("name").strip() stats[f"cls_{name}"] += 1 bb = obj.find("bndbox") xmin = int(float(bb.findtext("xmin"))) ymin = int(float(bb.findtext("ymin"))) xmax = int(float(bb.findtext("xmax"))) ymax = int(float(bb.findtext("ymax"))) if xmax <= xmin or ymax <= ymin: problems.append(("degenerate_box", fname, name, (xmin, ymin, xmax, ymax))) if xmin < 0 or ymin < 0 or xmax > w or ymax > h: problems.append(("out_of_bound", fname, name, (xmin, ymin, xmax, ymax))) # 极端长宽比往往说明标注时拖歪了,指针目标除外 bw, bh = xmax - xmin + 1, ymax - ymin + 1 if name != "pointer" and max(bw, bh) / max(1, min(bw, bh)) > 8: problems.append(("weird_ratio", fname, name, round(bw / bh, 2))) return stats, problems stats, problems = audit_voc("dataset/train/annotations", "dataset/train/images") print("类别分布:", {k: v for k, v in stats.items() if k.startswith("cls_")}) print("问题总数:", len(problems)) for p in problems[:20]: print(p)这段脚本的返回值里,stats用来确认类别分布和实例总量,problems是排查清单。重点看三类:size_mismatch说明图像被外部工具改过而 XML 没同步,这类样本必须整条剔除;out_of_bound是标注时裁切到了边界外,训练前要 clip 到图像范围内;weird_ratio用来抓标注时手抖拖出来的畸形框,指针类因为天然细长所以要排除掉。跑完这一遍,问题条数应该压到 0 或者个位数,剩下的手工处理。
3.2 转 YOLO txt:归一化、类别索引与 1 像素口径
转 YOLO 格式的核心就两件事:坐标归一化成中心点 + 宽高,以及把类别名映射成从 0 开始的整数索引。索引映射表要单独存成文件,训练和推理都读同一份,避免今天顺序是dial, pointer明天变成pointer, dial。
import os import glob import xml.etree.ElementTree as ET CLASSES = ["dial", "pointer", "scale"] # 顺序一旦定下就不要改 CLS2ID = {c: i for i, c in enumerate(CLASSES)} def voc_to_yolo(xml_dir, out_dir, img_dir): os.makedirs(out_dir, exist_ok=True) skipped = 0 for xml_path in glob.glob(os.path.join(xml_dir, "*.xml")): root = ET.parse(xml_path).getroot() w = int(root.findtext("size/width")) h = int(root.findtext("size/height")) lines = [] for obj in root.findall("object"): name = obj.findtext("name").strip() if name not in CLS2ID: continue # 未登记类别直接丢,但要记账 bb = obj.find("bndbox") # VOC 的 xmax/ymax 是闭区间且 1-based,转成独占右边界 xmin = float(bb.findtext("xmin")) - 1.0 ymin = float(bb.findtext("ymin")) - 1.0 xmax = float(bb.findtext("xmax")) ymax = float(bb.findtext("ymax")) # 裁剪到图像内,防止越界框把归一化坐标推成负数或大于 1 xmin, xmax = max(0.0, xmin), min(float(w), xmax) ymin, ymax = max(0.0, ymin), min(float(h), ymax) if xmax - xmin < 2 or ymax - ymin < 2: skipped += 1 continue cx = (xmin + xmax) / 2.0 / w cy = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{CLS2ID[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") stem = os.path.splitext(os.path.basename(xml_path))[0] with open(os.path.join(out_dir, stem + ".txt"), "w") as f: f.write("\n".join(lines)) print("丢弃的退化框数量:", skipped)这里有三个参数必须讲清楚。CLASSES的顺序是硬约束,训练配置里的nc(类别数)和names必须和它对上,差一个就会静默学错类。减去 1.0 是处理 1-based 到 0-based 的偏移,不做这步,宽高会整体偏大 1 像素。max(0.0, ...)和min(float(w), ...)的裁剪不能省,越界框归一化后会出现大于 1 的坐标,部分框架不报错但会把损失拉爆。
至于images和labels目录的对应关系,常见做法是保持同名不同后缀、放在平行目录下:
dataset/ ├── images/ │ ├── train/ IMG_0001.jpg ... │ └── val/ IMG_0231.jpg ... └── labels/ ├── train/ IMG_0001.txt ... └── val/ IMG_0231.txt ...跑训练前先抽查几张:把 txt 里的归一化坐标反算回像素,画在原图上,用眼睛看框贴不贴合。这一步五分钟,能省掉后面两天的“为什么 loss 不降”。血泪经验是——标签错的时候,模型不会告诉你它错在哪,它只会安静地学一个错的东西。
3.3 关键点格式:如果要做指针方向回归
只做检测的话 YOLO txt 就够了,但如果目标是让模型直接输出指针方向,就需要关键点格式。指针仪表最常用的关键点是两个:表盘中心点(旋转中心,大致等于dial框的几何中心)和指针尖端(指针远离中心的那一端)。
麻烦在于,bndbox本身不含方向信息。一根从左下指向右上的指针,和一根从右上指向左下的指针,框可能完全一样。所以从纯检测标签推关键点是不可能的,必须满足以下条件之一:
第一种,数据集额外提供了指针尖端的点坐标(XML 里可能有自定义的<point>节点,或者干脆另有一份 json)。这种情况直接读,转成归一化的(x, y)加可见性标志。
第二种,数据里指针类别标得很细,标注框只覆盖指针本体且足够窄,这时可以用框的两个短边中点作为候选端点,再结合图像判断哪一端离表盘中心更远。这种方法在指针较短、框较准时误差尚可,但一旦指针被表盘刻度或反光干扰,就会退化成猜。
第三种,也是最靠谱的:用分割掩膜或者框内像素处理来求端点。这就跳出了纯标签转换的范畴,进入第 4 章的路线讨论。如果你手里这份数据集只有 bndbox、没有点标注也没有掩膜,我的建议是老老实实先做检测路线,别硬凑关键点标签——用几何近似出来的关键点去训回归头,误差上限从标注那一刻就锁死了,后面怎么调都救不回来。
关键点标签的 YOLO-pose 格式是每行cls cx cy w h px1 py1 v1 px2 py2 v2,其中v为 0/1/2 表示不可见/遮挡/可见。转的时候注意v的语义:如果表盘中心被指针根部挡住看不出来,标 1 而不是 2,训练时的损失权重会区别对待。
4. 三条识别路线选型:检测加几何、关键点回归、分割取骨架
同样的 XML 数据集,能走出三条完全不同的技术路线,精度、标注成本、部署开销都不一样。选错路线,后面补标注的成本比重新做一遍还高。
4.1 路线一:检测表盘和指针,框内用几何求角度
这是 bndbox 标注最直接的用法。模型只负责回答“表盘在哪、指针在哪”,指针的朝向靠后处理算:在指针框内做一次阈值分割或者 Canny 边缘检测,拿到指针的像素点集,再对点集做 PCA 或者最小二乘直线拟合,得到一条过指针的直线。
难点在旋转中心。指针绕表盘中心转,但表盘框的几何中心未必就是真正的转轴——俯拍、镜头畸变、表盘偏装都会让两者有偏差。常见的修正做法是用刻度区或者多个角度的指针直线求交点:同一块表在不同角度拍若干张,每张拟合出一条指针直线,这些直线的交点就是转轴。标定时算一次,之后固定下来。
import numpy as np def fit_center_from_lines(lines): """lines: 若干条 (a, b, c) 形式的直线,满足 ax + by + c = 0 用最小二乘解超定方程组求交点,比两两求交再平均稳""" A = np.array([[a, b] for a, b, c in lines], dtype=np.float64) rhs = np.array([-c for a, b, c in lines], dtype=np.float64) center, *_ = np.linalg.lstsq(A, rhs, rcond=None) return center # (cx, cy) 像素坐标np.linalg.lstsq比手动两两求交再取均值稳,因为不同角度拍的图拟合出的直线质量不一致,直接平均会被最差的那条拖偏,最小二乘是全局最优。rcond=None用默认的奇异值截断阈值就行,除非直线几乎平行(指针只在小角度范围内摆动),那种情况要显式降阈值。
这条路线的好处是标注成本最低、可解释性最强,每一步都能画出来给人看;坏处是精度天花板由框的贴合度决定,而且后处理里有一堆阈值,换个光照条件就要重调。
4.2 路线二:关键点回归,网络直接吐端点
用 YOLO-pose 或者 HRNet 这类结构,输入整图,输出表盘中心和指针尖端两个点,角度由两点连线直接算。优点是端到端、没有后处理阈值、对光照变化更鲁棒;缺点是标注成本高,每个实例都要精确点到像素级,而且尖端这个点在小表盘、低分辨率图里本身就模糊,标注者之间的一致性差。
这条路线适合的场景是:表盘类型少但拍摄条件多变,或者对推理延迟敏感(少一步后处理)。如果你的数据集只有 bndbox,别走这条,先补点标注再说。
4.3 路线三:分割出指针掩膜,骨架化后拟合方向
标注掩膜成本最高,但拿到掩膜之后,角度提取会非常干净:二值化 → 形态学去噪 → 骨架化 → 对骨架点做 RANSAC 直线拟合。指针被刻度线遮挡、被反光截断的情况下,RANSAC 还能靠内点比例给出置信度,这是前两条路线给不了的。
代价是标注。一份含几千张图的指针仪表数据集,如果要从 bndbox 升级到掩膜,人力投入是检测标注的三到五倍。所以这条路通常用在两个场景:一是精度要求极高的计量类应用,二是把它当教师模型,蒸馏出一个轻量的检测或关键点模型去部署。
4.4 三条路线的横向对比与选型建议
| 维度 | 检测 + 几何 | 关键点回归 | 分割 + 骨架 |
|---|---|---|---|
| 现有 bndbox 标签可用度 | 直接可用 | 需补点标注 | 需补掩膜 |
| 角度精度(经验值) | 中,受框贴合度限制 | 较高 | 高 |
| 光照/反光鲁棒性 | 差,依赖阈值 | 较好 | 好 |
| 后处理复杂度 | 高 | 低 | 中(RANSAC) |
| 推理速度 | 快 | 快 | 慢 |
| 可解释性 | 强 | 中 | 强 |
我的建议是按数据现状走:手头这份含训练集、测试集和 XML 的数据集,先按路线一跑通全流程,把读数映射和评估链路搭起来,拿到一版基线误差。如果基线误差满足业务要求(工业表盘一般要求满量程 ±2.5% 以内),就别折腾了;如果不满足,再评估是补点标注走路线二,还是补掩膜走路线三。不要一上来就奔着最复杂的方案去,很多时候误差的大头不在模型,在标定和后处理。
5. 避坑排查:指针仪表 XML 数据集最常见的 5 个翻车点
这一章是我踩过的和见别人踩过的坑里,重复出现频率最高的五条。每一条都按“现象 → 原因 → 解决”写,对照着查能省不少时间。
5.1 验证集 mAP 0.95,换一批图全错
现象:训练收敛得很漂亮,验证集 mAP 稳定在 0.9 以上,但拿现场新拍的图一测,表盘都框不准。
原因:训练集和测试集之间发生了图像级泄漏。同一只表、同一机位连拍的图被随机分到了两边,模型学到的是“这块表盘的纹理 + 这个背景”,不是“表盘这个物体”。这是指针仪表数据集最典型的翻车方式,因为工业现场的图往往是成批连拍的。
解决:按表型号和拍摄场景分组划分,而不是按图片随机划分。做一次相似度粗筛,把 pHash 汉明距离小于阈值的图对找出来,强制分到同一侧。划分脚本写死随机种子,保证可复现。
5.2 框总是整体往外偏一点,小目标尤其明显
现象:把转换后的标签画回原图,框比目测的物体大一圈,细长指针的偏差肉眼可见。
原因:VOC 的xmax/ymax是闭区间且坐标 1-based,YOLO 用的是 0-based 连续坐标。直接套公式不减去 1,每个框的宽高都会多 1 像素。对于大表盘这 1 像素是无所谓的,但对宽度可能只有十几像素的指针来说,宽高偏差 1 像素就是 5%~10% 的相对误差,直接体现在角度估计上。
解决:转换时统一xmin - 1、ymin - 1,xmax、ymax保持不变当作独占右边界。如果标注工具是 0-based 导出的,就得反着来。这件事没有通用答案,必须拿一张图手工数像素确认口径,别信任何“应该是这样”。
5.3 训练时报类别数不匹配,或者训出来的类别顺序是乱的
现象:配置文件里写nc=2,训练报错说标签里有class 2;或者训练能跑,但推理时把指针识别成表盘。
原因:类别名映射不一致。XML 里的name可能有大小写差异(Pointer和pointer)、前后空格、全角半角混用,转 YOLO 的时候如果用一个临时字典边转边加,顺序就会随文件遍历顺序变化,今天和明天不一样。
解决:把CLASSES列表写死在代码里,作为唯一事实来源,训练配置和推理配置都从它派生。转换前先跑一遍Counter统计所有出现过的name原始字符串(包括空格),确认没有变体。遇到未登记的类别,宁可报错退出也不要静默跳过,静默跳过会让你少标注一批样本还浑然不觉。
5.4 标签看着没问题,模型就是在某些图上表现异常
现象:一批特定来源的图,检测框总是歪的,但把 XML 打开看坐标又是对的。
原因:EXIF 方向信息。手机或某些工业相机拍的图带 EXIF 旋转标记,PIL.Image.open读出来的size是未旋转的原始尺寸,而看图软件显示的是旋转后的。如果 XML 是在旋转后的视图上标的,坐标就和程序读到的像素对不上。另外size.depth=1的灰度图被当成三通道读进来,通道顺序也会出问题。
解决:加载图片时统一做ImageOps.exif_transpose,把方向信息烧进像素;然后对比一次size和 XML 里的width/height,不一致的直接报错。灰度图转三通道用convert("RGB"),不要自己拼通道。
5.5 检测框都对了,读数还是不准
现象:表盘和指针都框得挺准,但算出来的读数和人工读数差了 10% 以上,而且误差随指针角度变化。
原因:量程映射缺失或标定错误。角度到读数不是简单线性关系就完事——表盘刻度有线性刻度、对数刻度、还有带死区的;旋转中心估计偏了几个像素,在指针靠近中心时角度误差会被放大;不同表型号的量程不一样,但数据集里没有量程字段。
解决:给每个表型号建立标定档案:零刻度角、满刻度角、量程上下限、旋转中心坐标。这些信息不在 XML 里,得靠人工用两三张已知读数的图反解,或者直接对着表盘刻度做几何标定。没有标定档案的指针仪表数据集,只完成了“定位”这一半,读数那一半还没开始。
注意:以上五条里,5.1 和 5.5 是最容易被忽略的,因为它们不会让训练报错,只会让最终结果悄悄变差。
6. 进阶:角度到读数的映射与分档误差验收
模型输出指针角度只是中间结果,真正交付的是读数。这一步做不扎实,前面检测再准也白搭。我一般的做法是给每个表型号建一份标定档案,字段就四个:theta_zero(零刻度对应的指针角度)、theta_full(满刻度角度)、v_min、v_max。角度用图像坐标系下的 atan2 算,注意 y 轴向下,角度增长方向和数学坐标系相反。
映射本身是线性的,坑在角度展开。指针从 350° 转到 10° 的时候,直接相减会得到 -340°,误差巨大。所以要先做 unwrap,把角度序列映射到连续区间。
import numpy as np def unwrap_deg(theta, ref): """把 theta 展开到 ref 附近,处理跨 0/360 的情况""" d = (theta - ref + 180.0) % 360.0 - 180.0 return ref + d def angle_to_reading(theta_deg, calib): t0 = calib["theta_zero"] t1 = unwrap_deg(calib["theta_full"], t0) # 满刻度角展开到零刻度角附近 t = unwrap_deg(theta_deg, t0) # 待测角同样展开 ratio = (t - t0) / (t1 - t0) # 允许小于 0 或大于 1,用于发现越界 return calib["v_min"] + ratio * (calib["v_max"] - calib["v_min"]) calib = {"theta_zero": 135.0, "theta_full": 405.0, "v_min": 0.0, "v_max": 1.6} print(angle_to_reading(270.0, calib)) # 0.8 左右 print(angle_to_reading(10.0, calib)) # 跨零点的样本也能正确映射unwrap_deg里的+180) % 360 - 180是把角度差折到(-180, 180],这是处理跨零点最省事的写法。ratio故意不 clip,如果算出来是 1.3 或者 -0.2,说明指针角度估计已经跑出量程范围,这种样本应该被标记成异常而不是强行截断——强行截断会把系统性的角度偏差藏起来,变成黑匣子。
验收指标别只看 mAP,那个只反映定位质量。真正要盯的是分档误差:把读数误差按满量程百分比分档,统计各档的样本占比。工业场景常见的验收口径是这样:
| 误差档位 | 含义 | 一般验收要求 |
|---|---|---|
| ≤ ±1.0% FS | 优 | 高精度计量场景 |
| ≤ ±2.5% FS | 合格 | 多数工业巡检场景 |
| ≤ ±5.0% FS | 可用 | 趋势判断、粗读数 |
| > ±5.0% FS | 不合格 | 需查标定或重训 |
评估时按表型号分组统计,不要只看总体均值。我见过总体 MAE 只有 1.2% FS、但某个量程的表全部超差的情况,被平均值掩盖了。分组之后一眼就能看出是标定档案写错了还是模型对该型号不敏感。
最后说个我自己的习惯:每次改完标定参数或者重训模型,我都会固定抽 20 张图,把原图、检测框、拟合出的指针直线、算出的读数四样画在一张图上存下来。这批图不看指标,就用眼睛过一遍。指标会骗人,图不会——指针直线明显歪了的时候,读数误差早就藏在平均值里了。这套流程跑顺之后,一份含训练集、测试集和 XML 的指针仪表数据集,从解压到出第一版读数误差报告,一天之内能走完。希望帮到你。
本文还有配套的精品资源,点击获取