ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

工业视觉小目标检测实战:钢珠计数数据集与YOLO训练全流程

工业视觉小目标检测实战:钢珠计数数据集与YOLO训练全流程 做工业视觉的朋友应该都不陌生质检线上“数清楚小零件”这件事看着简单真正落地却十分折磨人。钢珠、滚珠、小型轴承滚子这类目标尺寸小、数量多、堆叠密集在图片里经常只有十几到几十个像素的大小用通用目标检测模型直接硬跑漏检率能让你怀疑人生。我最近正好整理完一套专门用于钢珠计数检测的数据集749张图片、单一类别、同时提供VOC和YOLO两种标注格式这里把制作思路、标注细节、格式转换和训练调优的完整过程分享出来给准备做类似小目标计数任务的兄弟一个可以直接参考的路线。简单说这份数据集解决的痛点是通用检测数据集里几乎没有针对“密集小目标计数”优化过的样本而工业上又特别需要“检测计数”合一的能力。把这749张图用好训练一个精度够用的钢珠计数模型完全可行整套流程也能迁移到螺丝计数、药片计数、电子元件点数等场景。无论你是刚接触目标检测的新手还是已经在YOLO上踩过坑的老手这份实操笔记里都有可以直接抄作业的部分。1. 核心需求拆解为什么钢珠计数不能当普通检测做1.1 场景特点决定了模型选型先聊一个很基本但很多人在项目启动时没想清楚的问题钢珠计数检测本质上是一个“密集小目标检测”任务而不是普通的目标检测任务。普通目标检测里一个画面中目标数量可能是个位数到几十个目标占画面的比例通常较大而钢珠场景下一张图里可能有几十甚至上百个钢珠每个钢珠的像素尺寸可能都小于32x32。在目标检测领域小于32x32的物体被统称为小目标而小目标的召回率恰恰是YOLO系列模型的老大难问题。这就引出一个关键结论不能直接拿默认配置的YOLO去训练。默认输入分辨率640x640的情况下你在标注框里看到的钢珠可能只有15x15像素经过模型的几次下采样之后特征图上的响应已经微弱到几乎不可用。因此制作数据集的时候就要考虑到分辨率策略推理的时候也要配合滑窗、切片等手段这些我在后面会详细说。1.2 为什么是749张、1个类别很多朋友拿到数据集的第一反应是749张是不是有点少类别只有钢珠一类够用吗这里有个行业共识需要先摆出来对单类别的密集小目标计数任务来说数据量不一定要追求上万张更重要的是“每一张图片的标注质量”和“场景覆盖度”。749张图如果每张平均标注40到60个钢珠总标注框数量就能达到3万以上这个量级对于训练一个二分类性质的目标检测器来说是足够的关键在于要把角度、光照、堆叠密度、遮挡程度这些变量铺开。至于为什么只有1个类别其实是为了把任务聚焦。工业计数场景下我只需要知道“画面里有多少个钢珠”并不需要区分钢珠的规格型号。类别越少模型的特征学习负担越小漏检率也越容易压下来。你如果后续要扩展多种规格完全可以在这份数据集的基础上继续标注新类别而不是从零开始重新攒数据。1.3 双格式的实用性分析这份数据集同时给了VOC格式xml文件和YOLO格式txt文件这不是画蛇添足。VOC格式在数据清洗、可视化检查、格式转换方面非常友好xml文件里记录了目标类别、边框坐标、图片尺寸等完整信息你可以写脚本随时转成COCO、YOLO、或是其他框架需要的格式。YOLO格式则是深度学习训练时的直接输入txt文件里每一行是“类别 中心点x归一化 中心点y归一化 宽度归一化 高度归一化”读取效率高训练代码不用再做额外解析。实际项目中我的习惯是用VOC格式作为数据存储的“母版”任何时候需要什么格式就从VOC转而不是手里只握着一堆txt结果想转成COCO格式的时候发现还缺图片尺寸信息还得重新翻原图。这就是双格式最大的价值——冗余但稳妥。2. 数据集制作全流程采集、标注、转换、划分2.1 图片采集与场景铺设制作数据集的第一步也是最容易翻车的一步是图片采集。绝大部分人只顾着拍图忽略了场景多样性结果训练出来的模型换个光照环境就崩。钢珠计数的图片采集我建议至少覆盖以下变量光照角度正光、侧光、暗场。工业现场不一定是理想均匀光照暗光下的钢珠轮廓会变模糊模型必须见过这种样本。堆叠密度稀疏摆放和密集堆放都要有。密集场景里钢珠之间紧密接触边界框会大量重叠这是模型召回率的主要压力来源。背景材质深色背景、浅色背景、金属反光背景。钢珠本身是高反光物体背景不同会让模型学到完全不同的特征。拍摄距离远景、近景混合。这能模拟不同工位相机安装高度不一致的情况。每张图的分辨率建议不低于1280x1280。749张图的采集其实不需要一次机械化拍完你可以在不同时间段、不同工位逐步积累拍完一轮标注一轮边拍边标更高效。如果实在受条件限制也可以通过裁剪大图来扩充数据把一张2000x2000的原图切成四张1000x1000的图并保留对应标注这样既保持了真实感又增加了样本数量。2.2 标注工具与标注规范标注工具我用的是LabelImg简单直接支持VOC格式的xml输出对Windows和Linux都很友好conda环境一条命令就能装好pip install labelImg打开后要特别注意PascalVOC格式的选项是默认开启的你只需要设置好“打开图片目录”和“保存标注文件目录”做好配对就行。标注时保存文件名的前缀建议和原图保持一致比如steel_ball_001.jpg对应steel_ball_001.xml后续处理脚本写起来会省很多事。真正考验人的是标注规范。小目标框的标注如果随意整个数据集的质量就废了。以下几个规则是我踩坑总结出来的建议你直接采用边界框要紧贴钢珠边缘不要留白边。钢珠是圆形用矩形框标注时矩形框的上下左右边界应与钢珠的相切宁可略微内缩也不要外扩因为外扩会把背景算进正样本特征里。严重遮挡的目标仍然要标注。两颗钢珠叠在一起时只露出很小部分的钢珠也要框出来只是框的尺寸会很小这正是训练模型克服遮挡的关键。不要漏标宁可错标也不要漏标。漏标对模型的负面影响比误标更大一句经验之谈密集目标场景下漏标会导致模型把“漏掉”当成正解后面再想补效果就很难了。标注完一个图片文件夹后要养成习惯抽查一下把xml和原图叠在一起肉眼检查。我试过用脚本把标注框画回图上批量生成预览图效率非常高值得每批数据都跑一遍。2.3 VOC转YOLO格式的转换实现VOC格式和YOLO格式的核心区别用一个表格就能讲清楚对比项VOC格式YOLO格式存储文件xmltxt坐标体系xmin, ymin, xmax, ymax像素绝对值x_center, y_center, w, h归一化相对值类别表示标签名称字符串类别索引数字可读性直观适合人看和调试紧凑适合训练框架直接读取转其他格式方便信息完整较麻烦缺图片尺寸信息转换的核心是坐标归一化计算。假设标注框为左上角(x_min, y_min)和右下角(x_max, y_max)图片宽高为(img_w, img_h)那么x_center ((x_min x_max) / 2) / img_w y_center ((y_min y_max) / 2) / img_h w (x_max - x_min) / img_w h (y_max - y_min) / img_h这个公式看起来简单但要注意两个细节。第一所有除法都是浮点运算不要提前转成整数否则归一化坐标会有误差虽然训练时影响不大但排查问题时会平白多一个变量。第二算出来的数值必须保证在0到1之间如果出现大于1或小于0的情况说明标注框本身越界了这种错误数据要删掉或修正。下面是我用的转换脚本可以直接复制改路径使用import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_file, out_txt, class_names): tree ET.parse(xml_file) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_names: continue class_id class_names.index(name) bndbox obj.find(bndbox) x_min float(bndbox.find(xmin).text) y_min float(bndbox.find(ymin).text) x_max float(bndbox.find(xmax).text) y_max float(bndbox.find(ymax).text) x_center ((x_min x_max) / 2) / img_w y_center ((y_min y_max) / 2) / img_h w (x_max - x_min) / img_w h (y_max - y_min) / img_h lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(out_txt, w) as f: f.write(\n.join(lines)) # 使用示例 class_names [steel_ball] voc_dir Annotations yolo_dir labels os.makedirs(yolo_dir, exist_okTrue) for xml_name in os.listdir(voc_dir): if not xml_name.endswith(.xml): continue xml_path os.path.join(voc_dir, xml_name) txt_path os.path.join(yolo_dir, xml_name.replace(.xml, .txt)) voc_to_yolo(xml_path, txt_path, class_names) print(fProcessed {xml_name})这里有两个容易踩的坑。一是类别列表class_names的顺序一旦确定就不要随意更改YOLO的txt里存的是索引数字训练时通过data.yaml里的names列表对应回去如果你改了顺序之前标注的所有txt都得重新生成。二是文件名的对应关系YOLO训练时要求图片和txt严格同名而且放在同一个父目录下的images和labels两个子目录里目录结构会在下一节详细写。2.4 数据集划分与目录组织数据做完转换后切分训练集、验证集、测试集也要讲究。我这次用的是大约9:1的比例也就是673张训练、76张验证。对749张这个体量验证集不需要太大76张足够评估模型效果了。要注意的是划分的时候要按图片名随机抽而且最好保证同一场景的连续帧不要同时落在训练集和验证集否则会有数据泄漏的风险验证集指标虚高。最终目录结构建议这样组织steel_ball_dataset/ ├── images/ │ ├── train/ │ │ ├── steel_ball_001.jpg │ │ └── ... │ └── val/ │ ├── steel_ball_401.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── steel_ball_001.txt │ │ └── ... │ └── val/ │ ├── steel_ball_401.txt │ └── ... └── data.yamldata.yaml的内容如下train: steel_ball_dataset/images/train val: steel_ball_dataset/images/val nc: 1 names: [steel_ball]路径建议使用绝对路径避免在切换工作目录时报出莫名其妙的找不到文件错误。如果你用的是Ultralytics YOLO框架它会自动去同名目录下找labels所以只需要在yaml里指定图片目录即可。3. YOLO训练实操从配置到计数结果3.1 模型选型与基线选择既然是做钢珠计数YOLO系列肯定是首选不用犹豫。我在这个任务上对比过YOLOv5s、YOLOv8s和一个YOLOv5m的变体给出一个不容易出错的组合建议用YOLOv8s作为起步模型。v8的C2f结构和Anchor-Free检测头在处理中小目标时比v5更稳而且Ultralytics的生态成熟训练、验证、导出一条龙对新手尤其友好。如果你的推理设备是老GPU显存只有6G以下那退回YOLOv5s也可以牺牲一点精度换更低的显存占用。要是你的场景对精度要求极高且推理设备是新的独立显卡还可以试RT-DETR但它的训练成本和推理延迟都比YOLO高对计数任务来说性价比一般。3.2 关键训练参数配置训练脚本我用的是Ultralytics的标准接口直接命令行就能跑yolo train datasteel_ball_dataset/data.yaml modelyolov8s.pt epochs200 imgsz1280 batch16 device0这里imgsz1280是最关键的一个参数。钢珠这种小目标如果使用默认的640分辨率会丢失大量细节实验数据也证实了这一点同一份数据imgsz640训练出来的模型mAP50可能只有0.75左右漏检率明显偏高调成imgsz1280之后mAP50能提升到0.87以上。代价是显存占用和训练时间几乎翻倍但工业计数任务更看重准确率这个代价值得。epochs200是基于749张数据量设置的因为数据量不大早停机制会自动判断最优模型。如果训练到100轮时mAP已经收敛后面的轮次只是微调不会产生过拟合的明显风险。batch16要结合你的显卡显存动态调整在训练前用nvidia-smi看一眼显存占用如果报CUDA out of memory就降到8或4。3.3 数据增强策略的取舍YOLO自带了一系列数据增强策略包括Mosaic、随机翻转、HSV扰动等。在钢珠计数任务里我的经验是默认增强基本够用但要注意关闭几个不适合的选项。Mosaic增强对密集小目标场景很有效它把四张图拼接在一起增加了每张训练图的目标数量也提升了模型对不同背景的适应力保留。垂直翻转和水平翻转可以开因为钢珠是圆形翻转不会改变语义。HSV扰动要谨慎。钢珠本身是金属色光照变化是重要特征但过强的颜色扰动会让模型学到不真实的色偏。建议把HSV的饱和度、亮度扰动控制在默认值一半。有一个参数需要明确关闭如果标注里密集重叠框数量多开启close_mosaic或某些版本里的mosaic的自动调节逻辑时不要额外设置scale过大否则会把钢珠缩得太小导致增强后的目标几乎不可辨识。3.4 训练过程观察与模型选择训练过程中我一般盯三个指标train/box_loss、val/box_loss和metrics/mAP50。box_loss的下降趋势正常的话mAP会同步上升。一个常见现象是损失曲线前50轮下降明显后面变得平缓这是正常的不要因为曲线平了就提前停。训练结束后在runs/detect/train目录下会生成weights/best.pt和weights/last.pt。别看名字使用best.pt是正确的它是在验证集上表现最好的权重不是最后一次迭代的权重。很多新手用last.pt去推理效果差一截白白误会了自己的模型。3.5 推理与计数实现模型训练好之后计数逻辑其实非常简单检测出多少个目标框数量就是多少。用Ultralytics的API也就几行代码from ultralytics import YOLO model YOLO(best.pt) results model.predict(steel_ball_test.jpg, conf0.25, imgsz1280) box_count len(results[0].boxes) print(f钢珠数量: {box_count})这里的conf0.25是置信度阈值计数场景下建议设置在0.2到0.3之间。阈值设得越高漏检越多计数偏低设得太低误检增多计数虚高。工业场景宁可偶尔误检也不希望漏掉钢珠所以我通常倾向低阈值配合后续的尺寸过滤做二次筛选。如果图片尺寸非常大比如工业相机拍的是4000x3000的整幅图直接把整图喂给模型推理效果会很差因为钢珠在被缩放到1280之后已经小到看不见了。这时需要滑窗推理把大图切成1280x1280的patch每个patch分别推理最后汇总每个patch的目标数。Ultralytics本身支持imgsz和stride设置但这不等同于滑窗推荐配合SAHI库做切片推理效果提升非常明显。3.6 计数精度评估检测模型的mAP是评价检测精度的但计数任务更关心计数误差。我建议计算两个指标MAE平均绝对误差预测数量与真实数量差的绝对值的平均。MAPE平均绝对百分比误差误差占真实数量的比例。比如验证集里某些图片真实有60颗钢珠模型数出58颗MAE就是2MAPE约3.3%。计数类项目的验收标准通常要求MAPE在5%以内大家训练时可以据此判断自己的模型是否达标。4. 训练与推理中的典型问题排查4.1 漏检集中在哪类图片如果你训练完做测试发现效果不理想先别急着盲目加数据。用我上面的预览脚本把预测结果画到图上仔细看漏检的目标长什么样然后你会发现漏检通常有明显的规律。最常见的三种情况密集堆叠区漏检多颗钢珠挨在一起时模型只检出边缘清晰的目标中间互相遮挡的漏掉。对策是提高imgsz或者改用更深的模型如yolov8m。暗光区域漏检图片角落光照不足钢珠和背景融为一体。对策是数据增强中增加暗光模拟或者在采集时多拍一些暗场景。边界处漏检目标被画面边缘截断框不完整。这类漏检通常发生在滑窗处理时需要设置滑窗重叠率让边缘目标在相邻patch里完整出现一次。4.2 误检与虚警怎么消除误检比漏检更难排查因为模型会把背景纹理误认为钢珠。我遇到过的典型误检是金属背景上的高光点被当成了钢珠。这种情况光调阈值没用要从数据层面解决在采集时把高光背景和钢珠的真实特征分开采样多标注一些“难负样本”——就是看起来很像钢珠但实际不是的背景区域。但要注意纯背景图不需要标注YOLO训练时会把没有标注的图自动当作负样本所以你可以往数据集里加入一些不含钢珠的背景图文件夹路径不变就行。如果误检已经发生了一个更快的补救措施是推理时增加一个尺寸过滤钢珠的真实物理尺寸范围是已知的如果检出框面积明显超出该范围直接丢弃即可。4.3 小目标专用优化手段除了提高输入分辨率还有更激进的做法。SAHISlicing Aided Hyper Inference切片推理是一种很高效的方案它把整图切成多个重叠的切片每个切片放大后再推理能让小目标的像素尺寸成倍增大召回率提升明显。使用SAHI也很简单from sahi.model import Yolov5DetectionModel from sahi.predict import get_sliced_prediction model Yolov5DetectionModel(model_pathbest.pt, confidence_threshold0.25) result get_sliced_prediction( imagesteel_ball_test.jpg, modelmodel, slice_height640, slice_width640, overlap_height_ratio0.2, overlap_width_ratio0.2, )切片尺寸一般取训练时imgsz的一半或等同尺寸重叠率0.2是常用值。切片数量不能太多否则推理耗时成倍上涨实时性要求高的项目要谨慎。另一种思路是调整模型本身的锚框或者检测头结构比如给YOLOv8换上P2检测层。但这属于改模型结构的高级玩法对新手来说维护成本高我建议先把前面的简单方案试完再考虑。4.4 数据扩充与半自动标注数据集用到后期如果你发现某些场景模型始终学不好最直接的解决方案是给这类场景补充数据。但手动标注几百张图实在痛苦我的做法是半自动标注先用现有模型预测新图片生成伪标注然后人工在LabelImg里加载这些标注进行修正。修正过程比从零标注快很多只需要拖动不准确的框、补上漏检的框、删掉误检的框即可。这样循环两三轮模型在新场景的表现会明显改善。这里有一个需要注意的流程细节用伪标注修正完的图片一定要重新生成对应的YOLO格式txt并且重新划分train/val目录不要把中途生成的临时代码文件混在数据目录里。5. 一些踩坑记录与后续扩展做这份数据集和配套训练的过程中最让我意外的一点是749张图和默认的640分辨率损失函数的收敛趋势看似正常但上线测试时小目标几乎全部丢失。当时我在mAP上看到的值并不差但部署到实际工位拍回来的高清大图上计数结果完全不可用。回头看问题就出在我用验证集的640缩略图评估模型而真实场景里模型面对的是未经缩放的密集钢珠图目标尺寸分布完全不同。后来统一改成1280训练加滑窗推理计数精度才真正稳定下来。所以大家在评估模型时务必用接近真实场景的分辨率图片做测试不要被验证集指标迷惑。另外还有一个建议如果后续要把这套能力扩展到不同规格的钢珠可以在这个数据集基础上做细粒度分类把类别拆分出来。但拆分类别意味着每类都需要足够的样本建议先跑通单类计数再逐步扩展否则标注成本和训练难度都会直线上升。其实做数据集和训练模型的过程很大程度上是在跟“数据分布”打交道。钢珠计数的难点从来不是模型不够强而是数据有没有覆盖到真实场景里的各种刁钻情况。你把采集、标注、格式转换、训练参数这几件事做扎实了749张图一样能撑起一个线上可用的计数系统。这套流程不仅适用于钢珠螺丝、药片、电子元件这类小物件计数场景思路都是相通的照着做就行。
返回列表