☰
钢珠计数目标检测数据集:VOC与YOLO双格式工业级实践指南
2026/10/1 10:01:31 网站建设 项目流程

这份数据集是我这边为钢珠计数场景专门整理的一份检测数据,749张图片,单类别,VOC和YOLO两种标注格式都给了。如果你正在做小零件计数、流水线物料清点或者类似的工业视觉项目,这份数据可以直接拿来训YOLO系列模型。这篇就围绕这份数据集聊透彻:它到底解决什么问题、数据是怎么组织起来的、双格式怎么用、训练和落地时有哪些钢铁直男式的坑。

1. 钢珠计数检测到底在解决什么生产问题

1.1 一个看着简单但实际很磨人的需求

钢珠——圆珠笔里的、轴承里的、自行车脚蹬里的、五金件装配里的。无论哪个场景,只要涉及批量分装或者物料盘点,就躲不开一个问题:这一盒到底装了多少颗?人工数,大家都会,但产线上连续数8小时,眼睛盯着一堆反光的金属小球,数错几乎是必然的。我见过一个配件厂的质检工位,专门安排两个人来回数两遍来互相验证,即便如此,抽检时还是能发现误差。

视觉方案解决这个问题的思路很直接:用目标检测模型把每颗钢珠的位置框出来,然后数检测框的数量。理论上,训练好的模型一帧图像丢进去,检测结果里有多少个框,就是多少个钢珠。这个方案之所以能被工业场景接受,核心在于:它不需要精密的结构光、不需要特殊硬件,一个普通的俯拍相机加上一个开源检测模型就能起步。

但方案能不能成立,决定性因素不是算法多新,而是数据是否贴合现场。钢珠这种目标,和日常的COCO数据集里的物体差别很大:尺寸小、数量多、表面反光、互相紧挨甚至堆叠。这种数据形态,市面上没有现成的大规模公开数据集能直接覆盖,唯一的做法就是自己攒数据、自己标注、自己训。

1.2 这份数据集的定位:工业级小目标计数的地基

这份钢珠计数检测数据集之所以设计成749张、1个类别、VOC和YOLO双格式,目的很明确:让拿到手的人能省掉数据格式适配的脏活,直接进训练流程。

749张图,对于目标检测来说不算大,但需要说明的是,钢珠计数这个场景下,单张图的钢珠数量通常不是一颗两颗,而是几十颗甚至上百颗。如果用每张图的标注框数量来折合,这份数据集的监督信号远不是749个样本那么简单,它实际上提供了几万个有效目标实例,足够把一个标准的YOLO模型训练到能用的程度。

单类别(通常是Steel_Ball或者Ball)是刻意的简化设计。计数任务不需要区分球的种类、型号,只需要把所有目标找全、别漏。类别越少,模型的容量越可以集中在"找得准、找得全"上,而不是浪费在类间区分上。对于起步阶段的产线验证,单类别完全够;如果要区分不同直径的钢珠,可以在这个基础上把类别扩展成Ball_Small、Ball_Large,标注文件做增量修改即可。

2. 数据采集和标注:原料决定了模型上限

2.1 采集场景设计的两个关键决策

采集钢珠图像,不是拿手机随便拍一拍就可以的。我在整理这份数据时,对采集环境做了两个关键控制:背景和光照。

背景上,钢珠是强反光的球形物体,如果背景同样是金属色或者带复杂纹理,检测器会学到一堆干扰特征。这套数据里主要使用了黑色哑光背景和白色哑光背景两种。黑色背景对钢珠的轮廓干扰最小,因为钢珠受光后会形成高光区,和黑色背景的对比度很清晰;白色背景则用来模拟浅色托盘上的检测场景,增加模型的泛化能力。

光照上,需要注意光源的角度和形态。钢珠是球面,点光源会在表面形成集中的高光晕,有时候高光区域比钢珠本体还亮,这会导致检测边框偏移。比较稳的做法是使用柔光光源或者环形光,让钢珠表面形成均匀的亮斑。如果你自己补拍数据,建议至少保证光源面积足够大,或者在光源前加一层柔光幕布,别用小功率射灯直射。

2.2 标注工具与流程:LabelImg的实战操作

这份数据的VOC格式标注是基于XML的,也就是Pascal VOC的标准结构。我整理数据时用的标注工具是LabelImg,Windows和Linux都有现成版本,操作逻辑很简单:打开图片,框选目标,填类别名,保存后自动生成同名XML。

标注流程上,我的习惯是这样的:

  1. 先建好类别文件,里面只有一行:Steel_Ball,避免多人标注时类别名拼写不一致。
  2. 一张图一张图过,框选钢珠时尽量贴近边缘。钢珠是圆的,标注框如果太松,会让模型学到一个包含大量背景的框,影响定位置信度。
  3. 重叠遮挡的钢珠也要单独框出来,不能因为被挡住就跳过。计数任务最怕漏标,漏标一颗,模型训练时就少一次学习这个位置的机会。
  4. 每标完一批,随机抽样20%让另一个人复核,重点检查重叠区域。标注这件事,一个人做久了,很容易产生惯性遗漏。

2.3 双格式数据的组织思路

很多人问,VOC格式和YOLO格式选一个不就行了吗?为什么两份都给。实际工程里,VOC格式是很多开源工具链的通用接口,比如一些数据增强库、模型评估脚本原生读VOC;而YOLO系列训练框架原生吃YOLO格式的txt标注文件。两份都给,本质上是为了容错和兼容,也方便在不同的训练框架之间来回切换。

数据目录的组织结构如下:

steel_ball_counting_dataset/ ├── images/ │ ├── train/ (训练图片) │ ├── val/ (验证图片) │ └── test/ (测试图片,可选用) ├── annotations/ │ ├── train/ (VOC格式XML,与images/train对应) │ ├── val/ (VOC格式XML,与images/val对应) │ └── test/ (VOC格式XML) └── labels/ ├── train/ (YOLO格式txt,与images/train对应) ├── val/ (YOLO格式txt) └── test/ (YOLO格式txt)

训练图片和对应标注文件的名字必须完全一致,这个一致性是后面一切顺利的前提。我自己穿过一次文件名大小写不一致的坑,在Linux上训练时直接少了一半标注,后来写了脚本批量统一成小写才解决。

3. VOC和YOLO标注格式的转换细节

3.1 两种格式到底差在哪里

VOC格式的标注框是绝对的像素坐标,一个XML文件里包含图片尺寸、通道数、目标类别和四个坐标值xmin、ymin、xmax、ymax。这个格式对人类友好,因为可以直接在图片上看懂框的位置。

YOLO格式则完全不同。一个txt文件里每一行对应一个目标,五个数值依次是:类别索引、归一化中心点x、归一化中心点y、归一化宽度w、归一化高度h。所有坐标都除以图片宽度或高度,压缩到0到1之间。

这带来的一个直接后果是:当图片被缩放、裁剪或者放入Letterbox时,YOLO的归一化标注可以保持不变(前提是等比缩放),而VOC的绝对坐标需要跟着图片尺寸重新计算。这就是"为什么工业项目里很多人直接用YOLO格式到底"的原因——省去做数据预处理时同步改标注的麻烦。

但反过来说,如果要做分析(比如统计标注框分布、检查异常框),VOC格式的XML结构更直观。所以我通常的建议是:原始标注保存为VOC格式作为主数据源,训练的时候通过脚本生成YOLO格式,原始文件永远不丢。这份数据集两份都给,就是省掉了这个二次生成的环节。

3.2 样例级别的转换脚本

如果你拿到的是VOC格式,需要转成YOLO格式,可以参考下面这个脚本。它的逻辑是:读取XML中的目标框和图片宽高,计算出归一化的中心点和宽高,写入同名txt文件。

import xml.etree.ElementTree as ET import os def convert_voc_to_yolo(xml_file, out_txt, class_list): tree = ET.parse(xml_file) root = tree.getroot() 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'): name = obj.find('name').text if name not in class_list: continue class_id = class_list.index(name) bndbox = obj.find('bndbox') xmin = float(bndbox.find('xmin').text) ymin = float(bndbox.find('ymin').text) xmax = float(bndbox.find('xmax').text) ymax = float(bndbox.find('ymax').text) x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") with open(out_txt, 'w') as f: f.write("\n".join(lines)) class_list = ["Steel_Ball"] # 示例调用 # convert_voc_to_yolo("annotations/train/0001.xml", "labels/train/0001.txt", class_list)

脚本看起来很简陋,但生产环境里够用。有几个点需要注意:

先检查XML里的坐标有没有越界(xmax大于图片宽度之类)。标注工具理论上不会产生这种错误,但手动编辑过文件的话就会出问题。YOLO训练框架对这些越界值非常敏感,轻则警告,重则直接跳过该目标,导致你的训练数据莫名其妙少了几个框。

另外,空标注文件的情况也要单独考虑。如果某张图确实没有钢铁、没有目标,转换脚本会生成一个空txt。这个对于单类别计数场景不太常见,但如果你的数据里有少量空图,记得保留空txt文件,而不是直接跳过生成。YOLO训练逻辑里,图片路径存在但标注文件缺失,会被视为训练异常;空文件则不会。

3.3 反向转换:YOLO转VOC的场景

反过来,YOLO转VOC用的场景不多,但有一种很常见:你训练完之后要分析错误案例,把预测框画出来叠加到图上,这时候VOC格式或者直接画矩形比解析txt方便。做法是:读取txt的归一化坐标,乘回图片宽高,得到像素坐标,然后直接调用OpenCV的rectangle函数画框。这个操作我在后面排查漏检问题时反复用到,算是调试阶段的必备动作。

4. 749张图怎么划分,数据增强加到什么度

4.1 按场景划分切训练验证集,别随手随机

数据集划分是一个看起来没技术含量、但实际影响很大的环节。随机划分有一个隐患:如果钢珠图片里有一部分是黑色背景、一部分是白色背景,随机划分可能让其中一种背景全部落在训练集里,验证集里恰好没有这种分布,导致验证指标虚高。

在我整理的这份钢珠计数检测数据集里,划分时是按采集场景(背景类型、光照条件)先做了分组,再按组进行比例划分,保证每一种场景在训练集和验证集中都有一定占比。这样做的直接好处是:训练过程中的验证mAP不会被某一个特殊场景拉满,导致你误以为模型已经能打。如果你要基于这份数据做二次实验,我的建议是沿用同样的思路——先按场景分层抽样划分。

常规的划分比例是8:1:1,也就是训练集600张左右,验证集75张左右,测试集75张左右。单类别目标检测里,验证集不需要太大,但必须覆盖所有形态,不然早停判断会失真。

4.2 要不要做数据增强,怎么做

数据增强在钢珠计数场景里要分层级来看。

基础的几何增强——翻转、旋转90度、缩放——几乎是无脑加。钢珠的检测不依赖方向,翻转不会改变目标的语义,不会产生违背物理规律的目标形态。尺度增强(随机缩放)也很有必要,因为钢珠有大有小,增强缩放能让模型对尺寸变化更稳定。

颜色类增强要克制。钢珠的材质决定了颜色特征有限,但光源不同会造成色温差异,轻微的亮度扰动和对比度扰动可以模拟不同光照环境,增强泛化性。但是,不要加随机擦除或者Cutout之类的强遮挡增强。钢珠本身就是密集目标,互相遮挡已经很常见,再人为遮挡会让目标的可见部分太少,导致检测器学到"有半个亮斑就算钢珠"的错误倾向。

YOLO默认的Mosaic增强(四张图拼接)在小目标计数场景下我是建议保留的。它能让目标在缩小的尺度过一遍,强迫模型学习小尺寸特征,对钢珠这种小目标尤其有用。但需要注意,如果钢珠数量太多,四张图拼在一起出现几百个目标,训练时数据加载和标签匹配的耗时明显上升,训练速度会下降。如果遇到这个问题,可以把Mosaic的开启概率调低,从默认的1.0降到0.5,训练后期甚至可以直接关掉。

5. 用这份数据训练YOLO的完整实操

5.1 组织YOLO训练环境与配置文件

我用的是ultralytics的YOLOv8框架,实操下来最省事。首先按它的格式建目录,图片统一放到一个images目录,标注文件放到同名labels目录,训练、验证子目录各自分开。

在这份数据集基础上,你需要写一个data.yaml配置文件,内容大概是:

path: /path/to/steel_ball_counting_dataset train: images/train val: images/val test: images/test names: 0: Steel_Ball

这里有个细节:path参数最好写绝对路径,或者保证你执行训练命令时的工作目录和yaml中的相对路径能正确拼接。YOLOv8对路径拼接不敏感,但一旦拼接错误,报错信息有时候挺让人困惑,表现是训练开始时显示"0 images found",然后整个进程直接退出。遇到这种情况,先检查路径,不要怀疑数据集本身。

5.2 训练命令与关键参数选择

数据就绪之后,训练命令非常简单:

yolo detect train data=steel_ball.yaml model=yolov8n.pt epochs=200 imgsz=640 batch=16 patience=30

需要重点说的是几个参数:

模型规格方面,钢珠这种小目标计数场景,yolov8n(nano)到yolov8s(small)之间的规格比较合适。因为目标数量多、尺寸小,大模型(比如yolov8l或x)的参数量会带来训练变慢、推理变慢,但检测精度的提升并不明显。目标检测里一个常见规律是,当目标特征不够丰富时,模型容量再大也学不到更多信息。钢珠就是一个典型例子——它就是一个亮斑,特征就是这么简单,大模型在这里几乎没有用武之地。

输入尺寸imgsz的选择值得认真权衡。图片原始分辨率如果是1280x1280,直接以640输入,意味着钢珠在输入图像里只有原来一半大小,小目标问题会变得更严重。我的做法是先用imgsz=640跑一个基准版本,看看漏检情况;如果边缘小尺寸钢珠漏检多,再试着提升到imgsz=960或1280。对这份749张的数据集,imgsz=640大概能在单张40系显卡上用15到20分钟训练完200个epoch,算力成本可以接受。960则时间大约翻倍。

早停patience设成30或50都是合理区间。第100个epoch之后如果mAP不再提升,早停会自动结束训练并保留最优权重,省得盲目烧卡。

5.3 模型评估与计数逻辑的联动

训练结束后关注的核心指标是Precision、Recall和mAP50。但在计数任务里,还有一个更接近业务的目标:数量误差。mAP反映的是检测框和真实框的匹配质量,而计数场景真正关心的是"模型输出的框数量"和"真实钢珠数量"差多少。

实操中我习惯写一个简单脚本:遍历验证集或测试集,对每张图执行模型推理,得到检测框列表,统计长度,然后和真实标注的目标数量做差值,计算平均绝对误差和最大误差。这个指标比mAP更直接,因为哪怕某些框定位偏移了但没漏检、没误检,数量对了,产线上就认为这件事干成了。

推理阶段,YOLO的输出可以直接处理:

from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") results = model("images/test/0001.jpg", conf=0.25, iou=0.5) for r in results: count = len(r.boxes) print(f"检测到钢珠数量: {count}")

conf阈值和iou阈值在计数场景里的调节逻辑和其他检测任务不太一样。conf设太高(比如0.7),有可能会漏掉因为反光过暗而置信度偏低的钢珠,导致计数偏少;设太低(比如0.1),背景杂物容易被误检成钢珠,导致计数偏多。我一般从0.25开始调,对比验证集的数量误差曲线,找一个误差最小的点。iou阈值对密集目标的影响很大,钢珠之间经常相互紧贴,默认的0.5在密集区域可能让两个相邻的检测框发生抑制合并,需要用0.3或者更低来留出更多空间,测试时从低到高扫一遍。

6. 钢珠检测实际落地中绕不过去的坑

6.1 小目标与密集粘连:NMS参数是第一道关口

钢珠检测落地时最普遍的问题是漏检,而且往往是漏在密集区域。一群钢珠紧紧挨着,检测器可能输出了十几个框,但在极端遮挡的地方,两个相邻钢珠的检测框叠合度非常高,后处理的NMS(非极大值抑制)会认为它们是同一个目标,直接干掉一个框,计数瞬间少一颗。

处理思路分两步:第一步,把推理的iou阈值调低,让NMS不那么激进,保留更多相邻框;第二步,更高阶的做法是开启NMS的改进版本或者在依赖的部署SDK里调整为不抑制同类目标的重叠框。YOLOv8的默认NMS对这类密集目标确实不算友好,如果你发现中后期训练指标很好但是推理计数始终偏少,优先检查推理阶段的iou参数,而不是急着加数据重新训练。

6.2 金属反光形成的假阳性:硬样本挖掘最有效

钢珠表面反光,会在边缘形成一道亮弧。这道亮弧在特定光线下看起来和一个小钢珠的形态高度相似,尤其是只出现在图像角落、形态不完整的时候。模型很容易把这种反光弧线误识别为一个小目标,假阳性直奔10%以上。

对这种问题,我的经验是不要指望全局调阈值能解决,而是做硬样本挖掘:训练完第一轮模型后,用它去推理所有训练图片,把置信度在0.3到0.6之间的误检框找出来,把这些误检区域对应的图片做裁剪,然后用这些裁剪图重新训练一个分类器或者单独做微调。或者更简化一点,在训练数据里专门加入一些不带钢珠、只有反光背景的负样本图,让模型知道"没有钢珠的时候不要输出任何框"。这份数据集以正样本为主,如果你在实际部署现场发现反光误检严重,补拍一批空背景图作为负样本往往能立竿见影。

6.3 针对这份数据集的部署建议:从验证到产线

在验证环境里,对着图片跑检测和实际产线上对着实时视频跑,体验完全是两回事。产线对推理速度有要求,如果钢珠随传送带运动,单帧处理时间必须在几十毫秒量级。这会逼着你在模型规格和输入分辨率之间做取舍。

一个稳妥的落地路径是:先用yolov8n加640输入,在测试集上验证数量误差。如果预实验的精度能接受,直接部署。如果精度不够,优先提升输入分辨率到960或1280,而不是换大模型。因为钢珠的问题核心在小目标细节缺失,提升分辨率比增加模型容量更对症。

如果钢珠数量极大,比如一张图里超过500颗,并且对速度要求很苛刻,还有一条路是分块检测:把图像切成几块,每块分别推理,最后合并数量。这种方法能把小目标放大,代价是推理次数线性增加。可以在部署时做一个开关,根据单张目标数量动态决定要不要分块,算是一种简单实用的折中。

部署时最好把检测结果的可视化输出保留下来,哪怕产线不需要图像,也要在后台存一段时间的检测结果截图。这个建议是我吃过亏之后总结的——有时候产线反馈计数不准,但是现场已经过了半小时,如果没有截图存档,你根本无法判断问题是当时光照变化、钢珠形态异常还是模型自身缺陷。有图有真相,排查效率能快一个数量级。

7. 后续想扩展这份数据时,我建议这样做

钢珠计数检测数据集现在这份可以跑通基础流程,但它不是终点。实际项目里,可能遇到不同直径的钢珠混料、钢珠表面油污、满盘和浅盘等不同装载状态。这些扩展方向都可以在这份数据基础上增量进行:保留已有标注,补充新场景图片,重新划分训练集,再微调一轮。数据积累是一个持续过程,模型上线第一天往往不是最终形态,而是真正优化循环的起点。

如果你只打算跑通一个Demo验证一下想法,这份数据集的749张图足够用了;如果你要上产线长期运行,还是建议尽早搭建一个现场的数据回传与自动标注通道,让模型每个月都能吃到最新的现场样本。被动维护数据集和主动收集数据集,最终效果差距很大。就算从零开始新建一份类似的数据集,也建议沿用这里说的框架逻辑:双格式备份、按场景划分、负样本补充、保留原始图。这些基本功到位了,后面的训练和部署自然会顺很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询