在自动化分拣线上,最难的不是把设备跑起来,而是让算法在传送带那种“一眼全是零碎物件”的环境里稳定地把纸箱、玻璃、金属、纸、塑料分清楚。很多人直接套用公开数据集训练,到了现场就翻车——泛黄胶带、金属反光、玻璃透底,通用模型根本扛不住。所以我这次直接把整个流程走了一遍:从输送机场景的数据采集、深度标注,到YOLO、COCO JSON、Pascal VOC XML三套格式的转换,再到训练调优,最终在测试集上以mAP@0.5口径做到识别率98.4%。这篇就当作一次完整复盘,每个环节的坑和可复用的脚本都放在里面。
1. 输送机场景下的识别需求:为什么通用数据集不顶用
1.1 输送机识别的场景特点
输送机视觉识别和普通的目标检测任务有个非常大的区别:视角是固定的俯视,但物料的摆放是完全随机的。公开数据集里的物体大多是正常姿态、自然光照、有完整轮廓,而传送带上的纸箱可能被压扁,玻璃瓶可能滚到金属罐旁边,塑料膜可能半透明地叠在纸板上。这种“俯视+堆叠+快速运动”的三重buff,直接把通用模型在公开集上的漂亮指标打回原形。
我最早拿COCO预训练权重直接上输送机测试,结果纸箱和纸的混淆率高得离谱。原因不复杂:COCO里的纸箱类别样本很少,而且大多是货架上的完整箱子;传送带上的纸箱往往有胶带、标签、褶皱,在俯视视角下更像一堆不规则色块。玻璃和塑料更是重灾区——透明物体在模型眼里几乎等于背景。
所以做输送机识别数据集,第一步不是急着标注,而是先承认场景差异,明确这个数据集服务的三类需求:实时分拣(在线判断物料类别)、质量抽检(离线复核误分拣)、设备调度(根据物料类型调整机械臂动作)。后续所有标注规范、格式选择、模型选型,都围绕这三类需求展开。
1.2 五种物料各自的光学“性格”
标题里提到的五类目标——纸箱、玻璃、金属、纸、塑料,看似简单,但在图像特征上差异巨大。我习惯把它们的成像特性列成一张表,标注的时候每条都要对着想:
| 类别 | 视觉特征 | 标注难点 |
|---|---|---|
| 纸箱 | 瓦楞纹理、胶带高光、印刷logo | 压扁变形后边界不规整 |
| 玻璃 | 透明、透出背景、表面反光 | 边界和背景融为一体 |
| 金属 | 高反射、明暗对比强 | 高光区域容易把物体“切断” |
| 纸 | 平面、薄、颜色接近输送带 | 褶皱产生的投影干扰轮廓 |
| 塑料 | 半透明膜、瓶子、硬壳混杂 | 薄膜几乎无纹理 |
这五类的光学“性格”直接决定了标注策略。纸箱可以只画矩形框,但玻璃瓶如果只用矩形框,会把大量背景框进去,模型学到的是“一个长方形里有反光”,而不是“玻璃瓶”。在我这个数据集里,玻璃和异形金属我全部用多边形标注,矩形框只在物体形状规整时使用。
1.3 数据集的构建目标与衡量标准
构建数据集之前必须想清楚一个事:98.4%这个数字到底在什么口径下算出来的。常见口径有mAP@0.5、mAP@0.5:0.95、Top-1准确率、精确率/召回率。在实际项目里,输送机分拣更看重的是mAP@0.5和单类别的召回率,因为漏检比误检更难处理——漏检意味着物料直接被传送带送走,后面还得人工处理。
所以我在设计测试集时故意加入了大量“难例”:重叠的瓶子、带反光的金属片、压在纸板下的塑料袋。公布98.4%识别率时,必须同时说明测试集构成和评估口径,否则这个数字没有意义。后面第4节我会专门讲评估细节。
2. 数据采集与标注工程的实操细节
2.1 采集环境搭建与相机参数
采集数据的质量直接决定模型上限。输送机场景我建议按固定高度安装相机,镜头垂直向下,视野覆盖单条输送带的有效分拣区。一开始我用手持相机随机拍摄,结果同一个纸箱在不同距离、不同角度下的尺寸变化太大,标注一致性和模型泛化都受影响。改成固定支架、固定高度后,问题立刻缓解。
照明是另一个被低估的变量。环境光一旦变化,模型的精度会剧烈波动。我在现场加了两条LED条形灯,左右45度打光,尽量消除物体投影。对于玻璃和金属,还需要加偏振片或调整光源角度来抑制高光。这部分的投资收益比远高于调模型参数。
相机参数上,我用的1920×1080分辨率,帧率30,曝光时间固定。特别提醒:不要用自动白平衡和自动曝光,传送带上物体颜色差异很大,相机自动调整会导致同一类物体在不同时刻的颜色特征漂移,模型学到的是“跟着曝光走而不是跟着物体走”。
2.2 标注工具与标注规范
标注工具推荐CVAT,原因有两个:支持多边形标注,且能直接导出COCO JSON和Pascal VOC XML;支持多人协作和标注审查。LabelImg虽然轻量,但只适合纯矩形框项目,玻璃和金属的多边形标注会让人崩溃。
标注规范要写在纸面上并严格执行。我制定的核心规范包括:
- 物体被遮挡超过70%时不标注
- 矩形框必须紧贴物体可见部分的外轮廓,不包含阴影
- 玻璃瓶内部反光区域不算背景,边界按瓶身实际轮廓走
- 塑料膜只有面积足够大且能分辨时才标注,半透明碎膜在真实分拣中本来就无法识别
这些规范看起来琐碎,但每条都能追溯到现场的一个具体问题。比如最初没有“遮挡超70%不标”的规则,结果大量半截瓶子进入训练集,模型在遮挡场景上反而越学越差。
2.3 数据规模、类别均衡与质检流程
很多初学者问“到底要标多少张”。我的经验是:单类至少3000个实例,总共2万实例以上,模型才有在工业场景下可用的基础。我这个数据集最终有12800张图像、约23000个标注实例,类别分布如下:
| 类别 | 图像数占比 | 实例数 |
|---|---|---|
| 纸箱 | 28% | 6800 |
| 塑料 | 24% | 5400 |
| 纸 | 20% | 4300 |
| 金属 | 16% | 3700 |
| 玻璃 | 12% | 2800 |
玻璃数量最少,因为透明物体的标注最耗时,但这也意味着模型在玻璃上的表现天花板较低。如果你的项目对玻璃的识别率要求高,一定要手动补充玻璃样本,硬凑数量没有意义。
质检流程分两轮:第一轮由标注员自检,第二轮由另一个人抽检20%的图像。抽检标准是类别错误和框偏移超过5%像素都算不合格。做完质检还要做一次“类别级统计”——用脚本统计每类标注框的宽高比分布。如果发现纸箱的框宽高比异常集中,说明标注员可能把箱子都打成了正方形,这个误差会在训练时被模型放大。
3. YOLO、COCO JSON、Pascal VOC XML:三种格式的转换原理与踩坑记录
3.1 三种标注格式的结构差异
支持YOLO、COCO JSON、Pascal VOC XML这三种格式,意味着数据集可以无缝接入不同训练框架。理解它们之间的差异比直接跑转换脚本更重要,否则转换出来的标注往往有隐蔽的错误。
| 格式 | 存储方式 | 坐标体系 | 类别定义 |
|---|---|---|---|
| YOLO | 每个图像一个txt文件 | 归一化中心点+宽高 | classes.txt中的行号 |
| COCO JSON | 单个json文件 | 像素坐标,左上角+宽高 | categories数组中的id |
| Pascal VOC XML | 每个图像一个xml文件 | 像素坐标,左上角+右下角 | name标签 |
YOLO的归一化坐标用0到1之间的小数,跟图像尺寸无关;COCO和VOC用绝对像素值。像素值的好处是直观,坏处是如果图像被resize,标注坐标必须同步转换。所以我的数据集里统一保存了一份VOC XML作为“母版”,YOLO和COCO的格式都由脚本生成,不手工维护。
3.2 格式互转的脚本实现与坐标系换算
直接上一份VOC XML转YOLO txt的脚本,这段代码我至今还在用:
# voc_to_yolo.py import xml.etree.ElementTree as ET import os from pathlib import Path def voc_to_yolo(xml_path, class_list, output_dir): tree = ET.parse(xml_path) root = tree.getroot() # 读取图片宽高,YOLO坐标必须归一化 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}") txt_path = Path(output_dir) / (Path(xml_path).stem + '.txt') with open(txt_path, 'w') as f: f.write('\n'.join(lines)) if __name__ == '__main__': class_list = ['carton', 'glass', 'metal', 'paper', 'plastic'] xml_dir = 'annotations/voc' output_dir = 'annotations/yolo' os.makedirs(output_dir, exist_ok=True) for xml_file in Path(xml_dir).glob('*.xml'): voc_to_yolo(str(xml_file), class_list, output_dir)COCO JSON转YOLO的方向正好相反,要把像素坐标除以图像宽高。写转换脚本时统一用float运算,不要用整数除法,不然所有框都会偏到左上角。
3.3 转换过程中的常见问题与自查清单
格式转换最容易踩的坑,我列成清单,每次转换后逐条检查:
- 类别ID对齐:YOLO用的是classes.txt里的行号,COCO用的是categories里的id,VOC用的是name字符串。三套体系的类别名一旦顺序不一致,整个数据集就废了。
- 空标注文件:有的图像里没有任何目标,YOLO格式允许空的txt文件,但COCO不接受没有标注的图像。我处理时统一保留了这些图像,只是标注数组为空。
- 图像尺寸丢失:YOLO的txt文件里不记录图像尺寸,如果有人把图像resize了而标注没跟着算,训练时会报错或者静默地学到错误位置。建议转换时校验一下图像实际尺寸和XML里的size是否一致。
- 框越界:标注时手滑把xmax拉到图像外面,VOC和COCO都能容忍,但YOLO归一化后可能出现大于1的值。脚本里加上clip(裁剪到0-1区间)能省很多排查时间。
提示:类别名全程使用英文小写,不要用中文和大小写混合。虽然部分标注工具支持中文类别,但YOLO的classes.txt和COCO的categories在跨机器、跨平台使用时,中文编码问题非常折磨人。
4. 从标注数据到98.4%识别率:模型选型与训练调优
4.1 为什么选YOLO系模型做输送机识别
模型选型上,我最终选了YOLOv8的m尺寸。原因不是YOLO在公开排行榜上有多强,而是工业部署的链路成熟度:导出ONNX方便,转TensorRT的教程一抓一大把,而且从训练到推理的代码路径最短。
Faster R-CNN在精度上并不差,但在输送机这种高速场景下,推理速度是硬指标。机械臂抓取动作需要视觉反馈延迟低于100毫秒,Transformer类检测器在速度和部署便利性上目前没有优势。YOLOv8n的精度稍弱,YOLOv8x又过度依赖算力,最后折中取YOLOv8m:在RTX 4070上推理一张640×640图像大约12毫秒,完全满足实时分拣需求。
4.2 训练配置、数据增强与超参设置
训练策略上我走了“先基础、后强化”的路线。第一轮用COCO预训练权重起步,输入分辨率640×640,batch size 32,初始学习率0.01,cosine衰减,训练150轮。数据增强默认开mosaic和随机翻转,但关闭了旋转增强——输送机上的物体姿态虽然随机,但基本都是水平放置,过大的旋转角度反而会破坏物体形状的真实分布。
特别注意一点:玻璃和金属的反射特征对颜色扰动非常敏感,所以我把HSV增强里的饱和度扰动幅度调低了,否则训练时会把反光区域的颜色变化当成不变的类内特征,测试时一遇到新场景的高光就会误检。
训练完成后,我发现纸箱的精度已经很高,但玻璃和塑料的召回率明显偏低。于是做了一轮难例挖掘:用模型跑一遍全部训练图像,把漏检的玻璃和塑料框挑出来,人工确认后直接补进训练集。第二轮训练在原始数据+难例的基础上又跑了80轮,最终测试集上的综合mAP@0.5到了98.4%。
4.3 评估指标与98.4%是怎么算出来的
这里必须把口径说清楚,不然任何数字都有误导性。我公布的98.4%是指:固定测试集(占总数据15%,不含任何训练见过的图像)上的mAP@0.5。IoU阈值0.5时,预测框和真值框的重叠面积超过50%即算正确。这个口径对工业分拣更有意义,因为分拣动作只需要知道“物体大概在哪个区域”,不需要像素级精度。
mAP@0.5:0.95是更严格的指标,同一批测试集上大约在86%左右,这个数字对高精度定位任务才有参考价值。所以你看别人的模型报告时一定先问一句:精度是什么口径?用mAP@0.5去衡量需要精确定位的机械臂任务,和用像素级mAP去衡量粗分拣,是完全不同的两个难度。
训练过程中还要看单类别AP,综合AP再高,只要有一个类别拖后腿,现场就会出问题。我最终五类的AP@0.5分别是:纸箱99.1%、金属98.6%、塑料97.9%、纸96.8%、玻璃95.2%。玻璃最低,因为透明物体的边界在标注时就存在主观性,这属于数据天花板而非模型天花板。
5. 部署到真实输送机:光照、反射、遮挡和误检的解决方案
5.1 光照与反射问题的物理本质
模型在实验室数据上做到98.4%只是第一步,部署到现场才是真正的考验。输送机的环境光是变化的,上午的阳光从窗户进来,下午的灯光照度下降,都会让模型推理结果抖动。
玻璃和金属的反射在物理上属于镜面反射,会在图像上形成一块高亮区域,有时候直接把物体的边缘“切断”。我踩过最深的坑是,一个金属罐的弧形表面反光形成了一条白色亮带,模型把亮带当成一个独立的物体框出来了,甚至有几个回合硬生生把罐子分成了“金属身体”和“亮片”两个目标。
解决思路不是无脑加图像增强,而是从采集端控制:安装遮光罩,光源均匀化,必要时加偏振片。这些物理手段的效果远好于在模型里加各种trick。如果现场实在无法改变光照条件,可以采集多时段数据加入训练,但工作量和效果都比不过先把环境光搞定。
5.2 误检与漏检的典型场景
部署后的误检主要有三种来源:
- 输送带本身的花纹或磨损处被识别成纸箱
- 纸箱上的透明胶带在反光时被识别成塑料
- 物体重叠严重时,模型把两个物体的贴合处当成新物体
前两种来源其实都是“训练分布和现场分布不一致”的体现。输送带如果用黑色橡胶,纹理在图像上会形成类似边界的形状,模型在训练时没见过这种背景纹理,自然会被误导。解决办法是把现场背景图收集一批无物体的空带图像,作为负样本加入训练集。
第三种误检是检测模型本身的通病,邻接物体共用边界时NMS会把两个框合并或错位。我的处理方案是调低NMS的IoU阈值到0.45,让重叠的框更容易被抑制;同时在部署侧加了目标的帧间跟踪,用连续几帧的检测结果做投票,单个帧的抖动就被抹平了。
5.3 后处理与工程化调优手段
部署时我用了ByteTrack做跟踪,原因很朴素:输送机的物体运动方向固定,速度相对可控,跟踪器可以给每个检测框生成一个轨迹ID。当某一帧置信度低时,用前后帧的平均位置补上即可,避免分拣机构因为单帧漏检而空抓。
还有一个容易忽略的调优点:置信度阈值不要定死。我最初的阈值设成0.5,结果现场频繁出现漏检和误检的反复横跳。后来改成“按类别动态阈值”:纸箱这种高频类用0.5,玻璃这种低频难类降到0.35,效果立刻改善。这个思路的核心是,不同类别的数据量和难易程度差异太大,统一阈值必然有一方吃亏。
6. 数据集后续扩展与持续迭代
6.1 类别扩充与数据回灌
输送机的物料种类永远比想象中多。第一批做五类,第二批很可能就要加铝箔、泡沫、纺织品。扩充类别的关键不是简单增加标注,而是保持原有类别的精度不下降。我采用的办法是增量训练:新数据加入后,把旧数据按原比例保留,并加入旧类别的难例图像做回灌,确保模型在学新类别时不“遗忘”旧类别。
数据回灌还有一个隐藏好处:它能不断吸收现场反馈。我每周把现场误检的图像下载下来,人工标注后加入训练集,大约两周后模型在现场的误检率就明显下降了。这套流程比任何花哨的算法都重要。
6.2 半监督与合成数据补充
当数据量成为瓶颈时,可以用半监督的方式给模型“放卫星”。我试过的可行路径是:用当前模型对未标注图像做自动预标注,人工只修正错误框,标注速度大约提升一倍。玻璃和塑料这类难标类目收益尤其明显,因为模型预标注出来的框已经比较接近真值,人工只需要处理边界细节。
合成数据对透明物体有价值,但工程成本不低。用3D渲染生成的玻璃瓶图像,纹理和真实图像的差距会在部署时暴露出来。我的建议是:合成数据只用于补充极端姿态和遮挡场景,不要试图用合成数据替代真实采样。
6.3 数据集版本管理与复用
最后提一个输送机识别数据集的常见误区:很多人把标注好就交付,后面再也不动了。实际上数据集必须像代码一样做版本管理。每个版本要有明确的增补记录、类别数量、评估指标变化,这样换人交接或复现项目时才能对上号。
我这边坚持“三份存储”策略:原始图像只读保存一份,VOC XML母版保存一份,脚本生成的YOLO和COCO格式再各存一份。任何训练或格式转换都从母版派生,不直接改母版。这样即使某个转换脚本出错,也不会污染整套数据资产。这套流程听着琐碎,但靠它确实避免了好几次“标注莫名其妙丢了一部分”的灾难现场。
如果你也要做输送机识别数据集,我的核心建议就一句话:把时间花在采集环境控制和标注规范上,模型调参反而是最不费劲的环节。数据基础打牢之后,98.4%这种数字会水到渠成地出现;数据基础不牢,再好的模型也救不回现场的翻车。