钢珠计数,看着是个小需求,做起来却比想象中复杂。在轴承装配、五金件包装、滚珠滑轨出厂质检这些环节,“这一袋到底装了多少颗钢珠?”几乎是每天都要回答的问题。人工数珠子既慢又伤眼,传统视觉算法遇上重叠、反光、光线波动就崩盘。我这次整理的这个钢珠计数检测数据集VOC+YOLO格式749张1类别,就是一套可以直接拿去训练YOLO系列模型、用来完成实时钢珠检测与自动计数的标注数据集,核心解决的是从“能不能数准”到“部署后还稳不稳”的一系列问题。
这个数据集的特点是:图片全部为钢珠的实拍场景,目标类别只有“钢珠”一类,单张图像里的钢珠数量从几颗到几十颗不等,密集程度也有明显梯度。同时我把标注文件同时准备了VOC的XML格式和YOLO的txt格式,不管你是要用LabelImg继续标注、跑开源评估脚本,还是直接丢给YOLOv8、YOLOv9训练,都不用再费劲做格式转换。无论你是刚接触目标检测的新手,还是已经在做工业质检的算法工程师,这套数据集配合下面的制作思路和训练参数,都能帮你少走不少弯路。
1. 项目拆解:钢珠计数到底难在哪
1.1 生产线上的痛点与传统方案的局限
钢珠体积小、表面抛光、反光强烈,而且在实际产线上经常是成堆出现的。装配车间里最常见的场景是:一颗颗钢珠滚落在料盘里,互相叠加、遮挡,光线一照还泛起一片高光。这时候如果靠人手数,效率低不说,数到后来眼睛发花,误差率反而上去了。传统机器视觉的做法通常是灰度化、二值化、找轮廓、再统计连通域数量,听着挺顺,实际跑起来问题一堆。
第一个问题是反光。钢珠的高光区域和背景的灰度差可能比钢珠本身与背景的灰度差还大,阈值一设,钢珠中间就出现了一个“黑洞”,一个目标被拆成两个。第二个问题是粘连。钢珠挤在一起时,二值化后连成一个不规则的白色块,轮廓合并成一团,连通域计数严重偏少。也有人尝试用分水岭算法做分割,但钢珠和钢珠之间没有明显的“山谷”,分水岭经常把一颗大钢珠劈成好几瓣。这些问题归根结底在于:传统算法靠的是人工设计的规则,而钢珠场景的干扰因素实在太多,规则写不全。
深度学习目标检测不一样,它不依赖你手写特征,而是从标注数据里自己学“什么样的区域像钢珠”。只要标注质量可控、场景覆盖足够,模型就能同时处理反光、遮挡和密集排列。而这种方案落到实际产线上,只需要一个普通工业相机搭配一台带GPU的工控机,检测速度就能跑到实时级别,计数逻辑也变得极其简单:检测出几个框,就是几颗珠。
1.2 为什么设计成1类别、749张的数据集
很多做数据集的人在类别设置上容易犯一个毛病:总想着把颜色、尺寸、型号都拆成单独类别,结果类别多了,每个类别的样本数量被摊薄,模型反而什么都学不好。钢珠计数这个任务,本质是单一类别目标的密集小目标检测。不管钢珠是不同批次、不同磨损程度,它们终究还是钢珠,检测框要回答的问题是“这里有珠子”还是“这里没珠子”。设置成单类别,模型可以把全部表达能力集中在“钢珠形态”这个特征上,收敛更快,漏检率更低。
749张这个数量级,说实话不算大。放在目标检测的常规标准里,动辄几千上万张的数据集才算“豪华”。但对于单一类别、目标形态高度一致的场景,749张是够用的,前提是场景多样性要到位。我在整理图片时特别关注了三个变量:光照条件(强光、柔和光、侧面光)、钢珠密度(稀疏几颗、中等散布、密集堆叠)、背景材质(深色磨砂底、浅色料盘、带反光的金属盘)。这三个变量展开后,模型见到的“钢珠世界”就远比749这个数字本身丰富。再配合数据增强和预训练权重,跑出来的效果完全能上产线验证。
1.3 VOC+YOLO双格式:不只是“备份”
现在很多数据集直接给YOLO格式的txt,理由是YOLO系列用着方便。但实际做项目你会发现,VOC格式(也就是Pascal VOC的XML标注)依然是行业里绕不开的“通用语”。老牌的工具链、不少评估脚本、还有一部分部署框架,只认XML。另外,XML是明文结构,标注信息里带着图片尺寸、目标类别名、坐标值,出问题时一眼能看出是哪里的毛病;YOLO的txt全是归一化浮点数,单看数字很难判断对错。
我选择同时保留VOC和YOLO两套标注,还有一个很实际的原因:方便交叉验证。训练时用YOLO格式,检查标注质量时用VOC格式配合可视化工具画框,两边一对比,坐标有没有偏、类别名有没有写错,马上就能暴露。后面如果需要用Roboflow、CVAT这类平台做二次标注或清洗,导入VOC格式也是兼容性最好的选择。这套数据集的“双格式”不是一个摆设,而是整个数据生命周期管理的基础设施。
2. 数据集的构建:从采集到标注的一整套规矩
2.1 图像采集:光源、背景和分辨率怎么定
数据采集这一步,决定了一个检测项目的天花板。钢珠这种反光强烈的小目标,拍摄环境不讲究的话,后续再怎么调模型都白搭。我自己的经验是三个关键词:哑光背景、柔光源、高分辨率。
哑光背景优先选深灰色的磨砂板或者防静电胶垫,避免浅色背景让钢珠的阴影变成“第二目标”。光源一定要用漫射光,最好是加柔光罩的条形灯,从两侧45度角打过来。直射光打在钢珠上会形成一个特别亮的高光点,虽然人眼能辨认出是钢珠,但对模型来说,这个高光区域和背景的对比度会迷惑特征提取。相机方面,分辨率建议不低于1920x1080,钢珠直径在图像里可能只占十几个像素,分辨率太低,特征信息都被像素化抹掉了。拍摄时相机固定,保持俯拍角度,避免透视变形给标注带来额外麻烦。
存储格式上,原图存JPEG没问题,但如果后续要自己扩数据或做精细标注,建议同时留PNG底图。JPEG在高光边缘会有压缩伪影,密集钢珠的边界会变得毛糙,PNG会干净很多。涉及产线环境的采集,还要注意拍几个批次的钢珠,不同厂家、不同磨损状态都放进去,不然模型容易对“某一批珠子的特定光泽”过拟合。
2.2 标注规范:比模型更影响效果的细节
标注是数据集制作里最琐碎、也最影响模型效果的环节。同样一份图片,不同人标出来的模型表现能差好几个点。所以动手标之前,一定要先把标注规则定死。这套数据集的标注规则,我总结成几条硬性指标。
第一,完整可见的钢珠全部标注。只要钢珠主体在画面内且轮廓能分清,就画框,不要漏。第二,边缘被裁掉一半以上的钢珠,不标。被裁掉大半的目标特征残缺,标进去反而会干扰模型学习“完整体貌”。第三,重叠遮挡的目标,轮廓主体可辨认就两个都标,哪怕一部分被挡住,矩形框把整体范围框住即可。第四,反光到肉眼看不清边界的钢珠,不标。这类样本属于标注噪声,模型训练时会收到冲突信号。第五,所有框统一紧贴目标边缘,宁可略紧不要过松。框太松会把相邻钢珠包进去,模型判断时边界是模糊的。
至于矩形框的选择,钢珠明明是圆的,为什么不直接标一个包含圆的外接框?目标检测的数据格式本来就只有矩形框一种,小目标密集场景下矩形框足够用。模型输出的就是一个矩形区域,“这个区域里有一颗钢珠”。等到后续如果要精确到球心坐标,再训练一个关键点分支也不迟,那是另一个任务了。标注时给矩形框留1到2个像素的边缘过渡带上是个好习惯,不要死贴到0像素,因为卷积神经网络的特征提取需要一点上下文信息。
2.3 工具选择与质检流程
标注工具我用的是LabelImg,开源、轻量、对VOC格式支持好。导入图片目录后,预先定义好类别名“steel_ball”,剩下的就是体力活。快捷键熟练之后,一张中等密度的图大概1到2分钟能标完,749张一个人集中做三四天可以完成。如果团队协作,也可以用Label Studio做Web端标注,方便多人同时在线、实时同步,但初期配置成本偏高。
标注完成后,质检这步绝不能省。我处理这类数据集时,会拿OpenCV写一个简单的画框脚本,把标注框叠加到原图上输出成预览图,然后人眼逐张翻看。重点看三类问题:漏标、错标、框偏移。漏标是最大的隐患,模型见没见过“这里明明有钢珠但没框”的负样本,直接影响漏检率。错标就是把阴影、螺丝孔等干扰物框成了钢珠。框偏移则会导致训练时的IoU计算偏差,模型学到的边界就不准。预览图全部翻一遍,速度也快,一个晚上能过完749张。
3. VOC与YOLO格式互转:坐标计算和脚本实战
3.1 两种格式的结构对比
VOC格式的标注信息存放在XML文件里,每个文件对应一张图。核心结构是这样的:annotation节点里有图片尺寸的size子节点,包含width、height、depth;每个目标的object节点里有name(类别名)和bndbox(xmin、ymin、xmax、ymax四个像素坐标)。这种结构人可读性很强,打开XML就能看到“这张图800x600,在左上角某个区域有一个钢珠”。
YOLO格式则完全不同,每张图对应一个同名txt文件,每行文本是一个目标,格式为“class_id x_center y_center width height”,五个数值用空格分隔。class_id是从0开始的整数索引,后四个坐标全部归一化到0和1之间。这种格式极其精简,一条目标一行文本,模型训练时解析速度快,也方便直接对应到网络输出层。缺点是脱离图片单看txt,你无法直观判断坐标是否合理,所以格式转换后必须做可视化校验。
3.2 归一化坐标计算的完整过程
VOC转YOLO的核心是坐标换算,搞懂这个换算,你就再也不用到处找人要“转换工具”了。假设图片宽度为W、高度为H,XML中标出的目标框为xmin、ymin、xmax、ymax,那么YOLO的归一化坐标公式为:
x_center = ( (xmin + xmax) / 2 ) / W
y_center = ( (ymin + ymax) / 2 ) / H
width = (xmax - xmin) / W
height = (ymax - ymin) / H
举个例子,一张1920x1080的图片里,某个钢珠框的坐标是xmin=795、ymin=126、xmax=801、ymax=132。先算中心点像素坐标:x_center_pixel = (795+801)/2 = 798,y_center_pixel = (126+132)/2 = 129。再归一化:798/1920 ≈ 0.415625,129/1080 ≈ 0.119444。宽度= (801-795)/1920 ≈ 0.003125,高度= (132-126)/1080 ≈ 0.005556。最终这一行标注就是“0 0.415625 0.119444 0.003125 0.005556”。
这里有一个小细节,很多转换脚本懒得处理,但严格来说VOC的坐标是像素的起点和终点,物体实际占据的宽度应该是xmax - xmin + 1。不过因为归一化后这个1像素的差距通常在小数点后三位之外,对模型训练几乎无影响,所以大部分转换工具都直接用xmax - xmin。如果想更严谨,在代码里加上1也行,我实际对比过,结果几乎一致。
3.3 转换脚本与校验方法
下面这个脚本是我在实际项目中用的版本,支持批量转换整个VOC标注目录,产出YOLO格式的txt文件。为了照顾不同数据集结构,脚本里做了简单的路径容错,但默认按最常见的“Annotations存XML、JPEGImages存图片”来组织。
import os import xml.etree.ElementTree as ET from glob import glob # 配置路径 voc_annotations_dir = "datasets/VOC/Annotations" # VOC标注目录 yolo_labels_dir = "datasets/YOLO/labels" # YOLO标注输出目录 images_dir = "datasets/VOC/JPEGImages" # 图片目录 os.makedirs(yolo_labels_dir, exist_ok=True) # 类别名到索引的映射,必须和后续训练时data.yaml里的names一致 class_name_to_id = {"steel_ball": 0} def convert_xml_to_yolo(xml_path): tree = ET.parse(xml_path) root = tree.getroot() img_filename = root.findtext("filename") # 有的XML里没有filename,改用xml文件名 if img_filename is None: img_filename = os.path.splitext(os.path.basename(xml_path))[0] + ".jpg" size_node = root.find("size") img_width = int(size_node.findtext("width")) img_height = int(size_node.findtext("height")) yolo_lines = [] for obj in root.iter("object"): name = obj.findtext("name") if name not in class_name_to_id: print(f"警告: 未知类别 {name} 出现在 {xml_path},已跳过") continue class_id = class_name_to_id[name] bndbox = obj.find("bndbox") xmin = float(bndbox.findtext("xmin")) ymin = float(bndbox.findtext("ymin")) xmax = float(bndbox.findtext("xmax")) ymax = float(bndbox.findtext("ymax")) # 归一化计算,并用clip兜底防止越界 x_center = ((xmin + xmax) / 2) / img_width y_center = ((ymin + ymax) / 2) / img_height w = (xmax - xmin) / img_width h = (ymax - ymin) / img_height x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) w = min(max(w, 0.0), 1.0 - x_center) h = min(max(h, 0.0), 1.0 - y_center) yolo_lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") # 输出txt,文件名和图片名保持一致 txt_name = os.path.splitext(img_filename)[0] + ".txt" txt_path = os.path.join(yolo_labels_dir, txt_name) with open(txt_path, "w") as f: f.write("\n".join(yolo_lines)) for xml_path in glob(os.path.join(voc_annotations_dir, "*.xml")): convert_xml_to_yolo(xml_path) print(f"已转换: {xml_path}") print("全部转换完成")转换完成之后,第一件事不是急着训练,而是做可视化校验。写个三行代码,用OpenCV把YOLO的归一化坐标还原成像素坐标,在原图上画矩形框,随机抽100到200张图人眼检查。这一步能抓出大量低级错误,比如坐标翻转让框画到图外面、类别索引错乱导致框的颜色不对、空标注文件没生成等。我还会顺手统计一下所有标注框的宽高分布,如果绝大多数框的尺寸都在某个区间,说明标注风格统一;如果出现特别大的框,那很可能是误标了背景区域,需要回去重查。
4. YOLO训练钢珠检测的实际配置
4.1 网络选型:该用哪个YOLO版本
数据集准备好之后,接下来就是模型选型。当前YOLO系列已经迭代到YOLOv9、YOLO11,但如果做工业落地,我的建议是优先选生态成熟、文档丰富、坑都被填平了的YOLOv5和YOLOv8。两者的训练配置、数据格式兼容性都很好,社区资料也最多,遇到问题更容易搜到解决方案。
| 模型 | 参数量 | 推理速度(相对) | 适用场景 |
|---|---|---|---|
| YOLOv5s | 7.2M | 快 | 边缘设备、CPU推理、快速验证 |
| YOLOv8n | 3.2M | 最快 | 资源受限的嵌入式场景 |
| YOLOv8s | 11.2M | 快 | 常规产线工控机 |
| YOLOv8m | 25.9M | 中等 | 对召回率要求高、有GPU |
对钢珠计数这种单类别任务,YOLOv8s是甜点选择。它有足够的能力学习钢珠的边缘和纹理特征,速度又不会拖累产线节拍。如果目标是跑到嵌入式板卡上,就降级用YOLOv8n。如果发现密集小目标漏检严重,再升级到YOLOv8m,用更大模型换召回率。这个思路比一上来就堆大模型要划算得多。
4.2 训练配置和参数细节
训练前,先把数据集目录组织成YOLO标准结构:
datasets/ ├── data.yaml ├── train/ │ ├── images/ │ └── labels/ ├── val/ │ ├── images/ │ └── labels/ └── test/ ├── images/ └── labels/data.yaml的内容这样写:
path: /absolute/path/to/datasets # 建议用绝对路径,避免相对路径出错 train: train/images val: val/images test: test/images nc: 1 names: ["steel_ball"]训练命令用YOLOv8的标准格式:
yolo detect train \ --model yolov8s.pt \ --data data.yaml \ --epochs 200 \ --imgsz 640 \ --batch 16 \ --device 0 \ --close_mosaic 10参数上面有几个值得细说的点。imgsz,输入图像分辨率。如果钢珠在原始图片里只有10个像素左右,640分辨率下会被缩得更小,特征基本丢失。这种情况建议直接拉高到1280,代价是显存占用和训练时间上涨,但对于密集小目标,分辨率提升带来的召回收益远大于训练时间成本。batch,16是一个相对稳妥的起点,显存不够就降。epochs,200够用,单类别小数据集的收敛速度很快,通常在80轮左右loss就趋于平缓。close_mosaic=10的意思是最后10轮把Mosaic增强关掉,因为Mosaic会把4张图拼一起,目标位置和尺度都被人为变化了,训练末期需要回到真实数据分布上让模型稳定。
再提一个很多人忽视的细节:预训练权重的选择。直接用yolov8s.pt从头训练也行,但用ImageNet预训练权重做迁移学习能明显提升收敛速度和最终精度。这不是玄学,预训练权重里的低层特征是通用的边缘、纹理、颜色信息,钢珠表面的纹理和光泽在低层特征层面和自然图像是共通的。唯一要注意的是,数据集只有1个类别,和COCO的80类不同,模型最后的输出层会被自动重建,这部分参数没有预训练值,前几轮会初始化后重新学习,loss曲线开头会有一个小波动,属于正常现象。
数据增强方面,YOLOv8默认开启的马赛克、翻转、HSV扰动对钢珠场景基本够用。钢珠是圆形目标,水平翻转、竖直翻转不会改变其语义,旋转增强意义也不大。反而要小心的是,如果增强强度拉太高,比如把亮度压得太低或者饱和度调得太过,钢珠会看起来不像钢珠,模型学到的反而是“非真实感特征”。针对反光类目标,我的经验是适当降低HSV增强的强度,尤其是亮度扰动幅度,因为钢珠的高光特征是一张真实的“身份证”,人为抹掉反而会让模型误判。
4.3 针对“小目标+密集”的优化策略
钢珠检测的核心难点就是目标小、排布密。整理了不少实测经验后,我总结了三种行之有效的优化策略。第一种是提高输入分辨率,这个上文已经提过,效果最直接,但代价是推理变慢,具体取舍按产线节拍算。第二种是切片推理,业内也叫SAHI(Slicing Aided Hyper Inference),把一张大图切成若干重叠小块,分别送入模型检测后再合并结果。切块的好处是把“大图中的小目标”变成“小图中的大目标”,特征更清晰,后处理的聚合NMS能消除重叠区域的重复框。实测下来,对钢珠这种目标,切片推理能把召回率拉高5到10个点。第三种是修改训练分辨率与推理分辨率不一致,训练时用640,推理时用1280,模型虽然能适应一定尺度变化,但跨度太大会导致精度下降,所以这个方法我反而不推荐,不如老老实实全程用1280。
更进阶的玩法是控制输入图像中钢珠的密度分布。如果整张图里有40颗钢珠,一次性丢给模型,检测头要同时回归40个目标,压力很大,漏检率难免上升。切片推理本质上也缓解了这个问题。训练时如果发现高密度图漏检严重,可以检查一下loss曲线中等情况,看看小目标层的损失占比,辅助调整“目标框与锚框匹配机制”的阈值。
5. 常见问题排查与计数逻辑实现
5.1 数据集类问题排查
训练刚起步时,报错大概率出在数据集上。我见过最多的错误有三个:路径错误、类别索引错乱、空标注文件。路径错误通常表现为训练日志里出现“dataset not found”或者“image not found”,第一时间把data.yaml里的path换成绝对路径,图片路径里有中文、空格的,全部改成英文和下划线,这一条能解决九成路径问题。类别索引错乱的表现是训练能跑但精度极低,或者loss出现异常波动,原因通常是转换脚本里的class_id对应关系和data.yaml里的names顺序不一致。排查方法很直接:随机打开一个txt标注文件,看第一列数字是多少,再对照data.yaml里names列表位置,确认0对应的是“steel_ball”。空标注文件则会导致训练时某些图没有正样本,模型从这些图里学到的全是负样本,严重的话会把钢珠学成背景。我用脚本遍历了所有图片和txt,确保每张非空图都有对应的非空标注文件。
5.2 训练异常排查:BN崩溃和loss不下降
训练过程中最容易让人血压升高的就是loss曲线爆炸或直接变成NaN。这个现象在目标检测圈里常被称作“BN崩溃”,也就是BatchNorm层在统计均值、方差时出现了不稳定。钢珠数据集只有749张,batch又往往不大,BN层在批次样本少的情况下统计量波动剧烈,一旦某个batch里样本分布极端,统计量就会发散。解决办法:调大batch、降低初始学习率(比如从默认0.01降到0.001)、或者把前三层冻住先训练后面的层。我实测下来,最有效的还是降学习率,YOLOv8的训练脚本默认学习率对COCO这种大型数据集是合理的,但对小数据集相对激进。
还有一种“loss不下降”的情况,输出层loss一直高悬不降,画出来的曲线像一条水平的直线。这种多半是背景和目标的比例极度失衡,负样本(背景)太多,把正样本信号冲掉了。钢珠单类别数据集里,如果某些图片的钢珠数量特别少,模型会倾向于把所有区域都判断成背景,因为这样整体loss反而低。对策是调整训练时正负样本的采样比例,或者在数据增强里增加Mosaic概率,通过拼图人为制造更多正样本。如果项目进度紧,直接给loss加上类别权重也是一种应急手段,给钢珠类别一个略高的权重,强迫模型多关注正样本。
5.3 从检测框到钢珠计数的工程实现
训练完成之后,把模型接进业务系统,计数逻辑本身不复杂。用YOLOv8的推理接口,检测结果是一个boxes对象,每个检测框里包含置信度和类别,数量直接取len就行。核心代码如下:
from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") results = model.predict( source="inference.jpg", imgsz=1280, conf=0.25, iou=0.5, device=0 ) boxes = results[0].boxes count = len(boxes) print(f"检测到钢珠数量: {count}")注意几个参数。conf是置信度阈值,取0.25是一个比较宽松的默认值。对于计数任务,我建议保持这个值偏低,宁可多检测几个假阳性,也不要把真实钢珠漏掉。如果发现计数结果偏多,多半是背景干扰物被误判成钢珠,这时可以往上调到0.35甚至0.45。iou是NMS时的IoU阈值,默认0.5,在密集场景下如果钢珠重叠严重,两个框的IoU可能超过0.5,NMS会把其中抑制掉一个,导致计数偏少,这时把iou往下降,比如0.3,但代价是可能出现一个目标多个框的情况。实际使用中,用conf和iou组合调参,再对照人工计数结果做校准,找到自己场景里的最优组合。我自己项目的经验数值是conf=0.25、iou=0.4,误差率能控制在1%以内。
5.4 实测漏检、误检的调试方法
模型上线前一定要做一次系统的漏检、误检分析,不要只看总体mAP。mAP是平均指标,在749张的小数据集上可能看着不错,但具体到某些图,可能漏了一排钢珠,有些阴影又被当成了目标。我的调试方法分三步走。
第一步,在测试集上做可视化推理,把模型框和真实标注同时画在图上,人眼比对。这一步能直观地看到模型在哪里犯了错:是密集区域漏了?还是高光区域误判了?第二步,统计“错误图”的共性。如果漏检的图全是同一种光照,说明训练集里这种光照的样本不够;如果误判的全是同类背景纹理,说明模型把纹理当成了钢珠,得考虑加负样本或者在增强里加噪声。第三步,针对性优化。漏检偏多,优先试切片推理或提高置信度阈值;误检偏多,优先补充背景负样本或者调高conf阈值。这个流程跑两三轮,模型表现通常会有质的提升。
阈值调优还有一个隐藏的陷阱:混淆矩阵的总和并不是固定的。很多人看混淆矩阵时发现行和列的数字加起来不等于总样本数,以为程序写错了,其实是因为不同标签、不同置信度阈值下,预测框和真实框的匹配关系会变化,有的检测框可能没有匹配到任何GT,被归为“背景预测”,有的GT没有匹配到任何预测,被归为“漏检”,两者都不在TP/FP/FN/TN的简单四象限里。所以排查时不要纠结矩阵数字是不是“对账”,重点看漏检数量和误检数量的走向。
数据质量是第一生产力
这个项目从头到尾做下来,我最大的体会是:钢珠计数检测这类任务,精度瓶颈往往不在模型结构,而在数据质量。一张标注潦草的图,能让模型学歪一个方向;一套光照单一的采集,能让模型在另一条产线上直接失灵。749张单类别数据集看着不起眼,但只要采集场景覆盖到位、标注规则执行彻底、格式转换准确无误,它完全能够支撑起一个可用的产线级计数模型。
最后分享一个可以免费复用的经验:制作这种小目标密集数据集时,把每张图的“目标数量”记下来,做成一个CSV备注文件。训练完了用这个CSV做批量测试,统计每张图的绝对计数误差,你会发现模型的问题往往集中在“超过30颗的大数量图”上。找准这个规律,再去针对性地补数据、调参数,效率比盲调高得多。这套数据集的下一步扩展方向也很明确:增加不同型号钢珠的样本做多类别计数,或者加入钢珠球心坐标的关键点标注,让计数和定位走向更高精度。核心思路不变,永远是先让数据变得干净、充分,模型自然会给你回报。