
这份数据集是我这边为钢珠计数场景专门整理的一份检测数据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的标准结构。我整理数据时用的标注工具是LabelImgWindows和Linux都有现成版本操作逻辑很简单打开图片框选目标填类别名保存后自动生成同名XML。标注流程上我的习惯是这样的先建好类别文件里面只有一行Steel_Ball避免多人标注时类别名拼写不一致。一张图一张图过框选钢珠时尽量贴近边缘。钢珠是圆的标注框如果太松会让模型学到一个包含大量背景的框影响定位置信度。重叠遮挡的钢珠也要单独框出来不能因为被挡住就跳过。计数任务最怕漏标漏标一颗模型训练时就少一次学习这个位置的机会。每标完一批随机抽样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 datasteel_ball.yaml modelyolov8n.pt epochs200 imgsz640 batch16 patience30需要重点说的是几个参数模型规格方面钢珠这种小目标计数场景yolov8nnano到yolov8ssmall之间的规格比较合适。因为目标数量多、尺寸小大模型比如yolov8l或x的参数量会带来训练变慢、推理变慢但检测精度的提升并不明显。目标检测里一个常见规律是当目标特征不够丰富时模型容量再大也学不到更多信息。钢珠就是一个典型例子——它就是一个亮斑特征就是这么简单大模型在这里几乎没有用武之地。输入尺寸imgsz的选择值得认真权衡。图片原始分辨率如果是1280x1280直接以640输入意味着钢珠在输入图像里只有原来一半大小小目标问题会变得更严重。我的做法是先用imgsz640跑一个基准版本看看漏检情况如果边缘小尺寸钢珠漏检多再试着提升到imgsz960或1280。对这份749张的数据集imgsz640大概能在单张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, conf0.25, iou0.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张图足够用了如果你要上产线长期运行还是建议尽早搭建一个现场的数据回传与自动标注通道让模型每个月都能吃到最新的现场样本。被动维护数据集和主动收集数据集最终效果差距很大。就算从零开始新建一份类似的数据集也建议沿用这里说的框架逻辑双格式备份、按场景划分、负样本补充、保留原始图。这些基本功到位了后面的训练和部署自然会顺很多。