凌晨一点四十分,车间里的灯光暗下来大半。白班和晚班的工人都撤了,只剩下那台贴着"请勿断电"的工控机还在嗡嗡作响。我盯着屏幕上的灰度图,那个红框又开始在零件边缘的倒角处晃——YOLOv11把模具纹理当成了缺陷,而真正那处只有三个像素大的针眼,它视而不见。这不是我第一次深夜调参,但"YOLOv11遇上产线微小瑕疵"这个组合,确实把我过去几年攒下的经验重新洗了一遍。
这个项目做的是某电子厂连接器塑胶外壳的在线外观检测。零件比拇指甲大不了多少,产线节拍要求每秒过检两个,瑕疵种类包括划痕、污点、压伤、针孔、边缘缺损,其中最头疼的是"微缺陷":成像后只有2到5个像素宽,和背景纹理的灰度差常常不超过20个灰度级。传统视觉算法做不过来,深度学习方案的成本又让人眼馋,于是YOLOv11被推上了前线。
如果你也在做类似的产线视觉项目,正被漏检、误报、深夜反复调参折磨,这篇文章应该能给你一些参考。我会把从数据准备、模型配置、训练调参到部署推理的完整过程,包括那些凌晨才敢动的参数,尽量讲清楚。坦白说,没有一夜之间点石成金的技巧,但有些坑是可以提前绕开的。
1. 微小瑕疵为什么难检:先搞清楚模型到底在跟什么较劲
1.1 目标尺度:当瑕疵只有几个像素时,模型看到了什么
先交代下成像条件。现场用的是200万像素黑白工业相机,视野大约40mm乘30mm,每个像素对应实际尺寸约0.02mm。一条宽度0.05mm的划痕,在图像里只有2到3个像素宽,长度可能有10几个像素。标注的时候,这类瑕疵框绝大多数小于16乘16像素,极端情况下甚至只有6乘6。如果用YOLOv11默认的640输入尺寸训练,模型输出的特征图最大也只是80乘80,一个6像素的目标在40乘40的特征层上只有不到0.5个像素的信息。你可以理解为:隔着十米看一张A4纸上落的一根头发丝,人眼都有点吃力,模型却要从中提炼出可判别的特征。
YOLO系列天生不是为小目标设计的。公开数据集里的通用目标通常占据图像面积的百分之几甚至更高,而产线微小瑕疵往往只占图像面积的万分之几。这不是调一个参数就能解决的,是尺度的天然劣势。我见过很多人一上来就堆模型,YOLOv11x直接上,结果该漏还是漏。原因很简单:输入分辨率不提高,模型再大也看不到足够多的像素信息。
1.2 下采样与感受野:YOLOv11 backbone的天然瓶颈
YOLOv11的backbone会逐步把特征图下采样,普通检测头对应的是8倍、16倍、32倍三个尺度。32倍下采样层上,一个8像素的瑕疵连一个点都不算;16倍层勉强有半个点,但经过连续卷积后,这些许信息很容易被周围大面积的背景纹理稀释。默认配置下,模型的学习重心会不自觉地偏向"更容易看到的目标"——比如浇口、水口、定位孔这些大结构,微小瑕疵的梯度贡献微乎其微。
针对这一点,业内常见方案有这么几个。第一,把输入分辨率从640提到960或1280,让瑕疵在特征图上占据更多像素;第二,在模型结构中增加P2浅层检测头,专门负责小目标;第三,采用切片推理,先把大图切成小块,再分别检测。我在这个项目里先尝试了提高分辨率,毕竟改yaml最省事。imgsz=960加上YOLOv11s,训练时间翻了一倍多,但小召回率确实有明显回升。P2头也试着改过一版,效果不错,但推理耗时涨得厉害,后面为了上线的实时性又放弃了。这些取舍后面会细说。
1.3 背景干扰:纹理、光照、反光如何淹没缺陷
塑胶外壳表面有模具咬花纹理、合模线、水口残留,在特定角度光照下还会形成强反光。这造成了一个很麻烦的现象:缺陷和纹理在灰度空间上经常长得很像。模型很容易把高亮纹理边缘误报成划痕,而真实缺陷由于对比度太低,置信度始终上不去。
一开始我以为是模型太笨,后来和光学工程师聊完才明白,问题很大程度出在成像端。同轴光、低角度光、漫射光的组合能显著压低纹理反光、突出凹凸缺陷,但改造产线光学结构不是一两天能完成的。所以在模型侧,我们加大了亮度对比度扰动、高斯噪声、模糊等灰度层面的数据增强,模拟不同光照环境,逼着模型学会"不要见到高亮就当划痕"。这些都是深夜调参时反复试出来的,效果比想象中明显。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 深夜调参实录:从漏检八成到漏检个位数的三轮优化
2.1 第一轮:跑通baseline,先面对现实
第一轮我直接用YOLOv11s,输入640,默认参数,训练100轮。训练集只有3200张标注图,瑕疵目标约5000个,平均每张图1.5个目标,集中在划痕和污点两类。由于缺数据,我用了官方在COCO上的预训练权重做迁移学习。当时心里想的是,再差也能混个七七八八,结果被狠狠打脸。
在产线抽出的200张测试图上,mAP50只有0.42,召回率只有0.23,漏检率接近77%。更让人崩溃的是置信度分布:大量真瑕疵的置信度低于0.1,而很多纹理边缘的置信度反而在0.5以上。把检测结果可视化出来,我发现模型关注的区域都是零件轮廓、棱角、料纹这些"大结构",真正的针眼和细微划痕处几乎没有响应。
那个晚上我第一次意识到,用通用目标检测的思路做工业微缺陷,必须重新思考数据、分辨率、损失权重三件事。baseline的意义不在于效果,而在于让你直观看到模型的"盲区"在哪。
2.2 第二轮:类别不平衡、损失权重与检测头的取舍
YOLOv11是anchor-free结构,损失主要由分类损失和边界框回归损失构成。当瑕疵目标很小、正样本数量又远少于背景时,模型很容易陷入"全都预测为背景"的局部最优。这时候需要人为拉高难样本的约束。
我用的做法是修改训练超参数里的损失权重配置。在YOLOv11官方框架里,可以通过数据配置文件指定部分损失相关参数,比如给分类损失设置正样本权重,让模型更重视难分类的小目标;同时把边界框回归损失权重调大,因为微小瑕疵的位置偏差非常敏感,一个像素的偏移就可能导致检测框完全偏离。Focal Loss的gamma我也从默认值往上调了,让模型把精力集中在置信度低的难例上。下面是我当时参考的配置片段:
yaml复制loss:
cls_pw: 1.5
box: 7.5
fl_gamma: 1.5
注意,不同框架版本对损失超参数的暴露程度不一样,上面只是思路示例,具体数值需要在自己数据集上验证。调完这一轮,召回率从23%提到了45%,但误报同步上涨。原因是模型变得敏感了,开始把合模线、模具纹理边缘也当成潜在缺陷。于是我又做了两件事:一是把"看似缺陷但实际不算不良"的图片作为负样本单独整理出来加入训练;二是推理时把置信度阈值从0.25提到0.35,误报降下来了,但漏检又反弹回去。这个阶段最磨人,按了葫芦起了瓢。
2.3 第三轮:数据合成、增强和多尺度训练带来质变
真正让指标出现质变的,是数据本身。我们翻仓库把所有留样的不良品找出来重新采集,又请工艺工程师配合做了几十个人工缺陷样本,标注量增加到6000张、瑕疵目标约9000个。但这点数据对深度学习来说仍然不够,于是开始上数据合成和强增强。
在YOLOv11的增强配置里,我打开了mosaic、mixup、copy-paste,但每个都做了克制调整:mosaic=0.5,mixup=0.2,copy_paste=0.3。原因在于,产线零件很规整,太多的mosaic会把零件切得乱七八糟,反而让模型学到错误的空间分布。另外,这是黑白工业相机成像,基于HSV的颜色增强意义不大,重点要放在亮度、对比度、噪声、轻微模糊这些灰度变换上。
多尺度训练也很有用。通过设置scale参数在0.5到1.5之间随机变化,模型能同时适应近距离大缺陷和远距离微缺陷,不至于过拟合到某一固定尺度。GPU资源有限,我把输入分辨率提到960,batch size从16减到12,epoch从100加到200,训练时长从4小时变成十几小时,但深夜正好可以慢慢跑。最终验证集召回率75%,漏检率约12%,误报压到每班3次以内。虽然离生产要求的5%以下还有距离,但至少看到了工业化落地可能性。
3. 训练之外:推理端的"最后一公里"部署与踩雷
3.1 ONNX导出时遇到的事:自定义尺寸和批处理
训练指标好看是一回事,部署到工控机上能跑才是真本事。这个项目现场没有GPU,只能用CPU工控机做推理,所以模型导出成了关键步骤。我最初用官方框架导出ONNX,直接踩了坑:固定batch大小、固定输入尺寸,导致测试时换一张不同尺寸的图就报错。后来改用动态输入,又遇到某些算子在高版本opset下不兼容。
最终做法是导出时固定batch为1,动态H和W,opset用12。这样既能用OpenVINO在CPU上加速,又能适应现场不同分辨率输入。导出命令大概长这样:
bash复制yolo export model=runs/train/exp/weights/best.pt format=onnx opset=12 dynamic=True imgsz=960
这里特别提醒:导出前的预处理必须和训练时完全一致。比如灰度图是三通道重复还是单通道输入、归一化除不除以255,稍有出入,推理效果就明显变差。我们在这个问题上白白折腾了一晚,最后逐行比对预处理代码才发现是BGR和RGB通道顺序不一致,一个很小的错误,导致置信度大面积漂移。
3.2 NMS参数与产线节拍的拉扯
非极大值抑制的IoU阈值,一般默认0.7,但在工业微缺陷场景下需要重新考虑。产线上经常出现多个针眼紧密排列的情况,如果IoU阈值设得太高,多个相邻缺陷会被合并成一个框,漏检数看起来没变,实际缺陷数量统计错了。如果设得太低,同一个缺陷在轻微形变下又会被重复框选,影响后续分类统计。我们后来把NMS的IoU阈值调到0.5,配合类别置信度过滤,基本消除了重复框问题。
不过真正影响生产节拍的是输入分辨率。imgsz=960在CPU上的OpenVINO推理大概需要120毫秒,产线节拍要求单张处理不超过500毫秒,理论上勉强够。但现场相机触发频率并不均匀,有时候传送带上零件靠得近,一下来三张图,就需要排队。排队意味着漏检,因为后面的零件直接过去了。最后我们采用了ROI裁剪方案:先用一个简单的传统视觉算法定位零件主体区域,再把裁剪后的区域缩放到模型输入尺寸。这样背景干扰少了,模型精度也提了,推理耗时反而降到了60毫秒左右。
3.3 成也置信度阈值,败也置信度阈值
深夜调参调得最多的,就是conf_thres这个值。离线测试时我们把置信度阈值定在0.25,感觉一切完美。可是产线上一跑,发现很多真实瑕疵的置信度只有0.2左右,漏检率突然飙升。把阈值降到0.15之后,漏检确实下降了,但误报多到生产线工人想骂人——每班要处理几十次停机确认,根本没法生产。
那段时间我反复在0.15和0.25之间横跳,直到有一天凌晨突然想明白:不同区域的误报风险本来就不一样。零件中心区域是平整面,误报少,可以用低阈值保召回;零件边缘、棱角、纹理过渡区误报多,要适当提高阈值。于是我在推理代码里加了一个后处理逻辑:根据检测框中心坐标落在图像区域的位置,动态决定置信度阈值。
python复制if bbox_center_x > roi_left and bbox_center_x < roi_right:
conf_threshold = 0.15
else:
conf_threshold = 0.30
就是这么个不起眼的改动,让漏检率降到8%,误报控制在每班4次以内。这段代码我至今留着,它比任何模型结构改动都值钱。
4. 一些没法写在文档里的经验
4.1 算力不够时,怎么给模型"减重"又不掉点
如果现场只有CPU或者低端GPU,YOLOv11s可能都嫌重。我的建议顺序是:先做ROI裁剪,减少输入面积;再做模型蒸馏,用YOLOv11m当教师,YOLOv11n当学生,把教师模型在验证集上的软化输出作为额外监督。蒸馏之后,学生模型在微小瑕疵上的召回率可以接近教师模型的90%,参数却少了一大半。最后再考虑量化。对微小瑕疵这种极度依赖特征细节的任务,PTQ一次性量化到int8风险很大,我见过太多模型量化后微小目标直接消失的案例。如果要用OpenVINO的int8,请一定用现场采集的图像做校准集,不要拿训练集凑合,否则光照差异会让量化后的模型在产线上完全失控。
4.2 产线现场才是真正的调参战场
实验室指标和实际产线效果的差距,只有上过线的人才知道。白班和夜班灯光差异、传送带振动导致的对焦偏移、相机自动增益在阳光透过窗户时的波动,都会让同一个模型表现出完全不同的成绩。我在这个项目里吃过夜班的亏:白天验证漏检率5%,夜班一跑变成15%。后来把所有自动增益、自动曝光全关掉,固定光源强度,再按早中晚三个时段分别采集数据加入训练集,才把波动压下去。还有一点,数据版本管理比调参重要得多。每个深夜训练出来的权重,必须连带训练集版本、增强参数、验证集结果一起记录。否则三天之后你就分不清哪个模型对应哪批数据,一切归零。
4.3 跟工艺工程师沟通,是比调参更费心的事
很多被模型判为"误报"的缺陷,根源其实是模具脏污、材料批次波动、注塑参数偏移。工艺工程师调一下模具温度或者更换材料批次,模型的表现就可能天翻地覆。后来我和工艺组约定:凡是出现新的"疑似缺陷",先让检测系统保存低置信度图像,每周集中复盘一次。确认是新型缺陷就补标注、增量训练;确认是伪缺陷就加入负样本库。这个闭环跑起来之后,模型不再是一次性的交付物,而变成了产线的持续质量基线。说到底,YOLOv11再强也只是一双需要不断校准的眼睛,真正让它在产线上站稳脚跟的,是数据、场景和人的协作。
深夜调参的日子现在还在继续,但能踩的坑已经少了很多。如果你正准备把YOLOv11搬到自己的产线上,记住一句话:先别急着追求榜单上的mAP,先让模型在深夜的产线上看清楚那根头发丝。
