如果你在笔记本整机组装线后端待过,应该对这一幕不陌生:工人打着小手电,贴着刚装好的LCD屏幕找角度,看有没有亮点、暗点、划伤、异色斑块。屏幕缺陷检测在很多人眼里是"老掉牙"的视觉课题,但它恰恰是整机厂自动化覆盖率最低的工位之一。原因也很直白——LCD屏幕缺陷又小、又暗、又杂,传统图像处理算法和通用目标检测模型到了这里都会出现不同程度的失灵。
我这次要分享的项目,是围绕"笔记本LCD屏幕缺陷检测与分类"做的一套 YOLO11-SEG 改进方案。整套系统以 YOLO11 的实例分割分支为底座,加入了一个针对小缺陷设计的 SDI 模块(Scale-aware Detail Injection,尺度自适应细节注入),专门解决亮点、暗点这类只有 3~5 个像素的微小缺陷在深层特征中丢失信息的问题。从数据采集、标注体系设计,到模块改造、训练调参、部署落地,我会把整个项目从 0 到 1 的完整链路都摊开来说。这篇文章适合正在做工业缺陷检测落地、或者想用分割模型处理小目标场景的算法工程师和视觉项目负责人,哪怕你对 YOLO11 不熟,看完也能明白这套方案到底改了什么、为什么这么改、放到产线上还能不能扛住真实节拍。
1. LCD产线质检里的老问题:为什么通用目标检测在这些场景上会失灵
1.1 任务定义:缺陷检测与分类到底要解决什么
先明确一下我们说的"LCD屏幕缺陷检测与分类"具体指什么。在笔记本整机组装厂的测试工位,屏幕会依次显示纯白、纯灰、纯黑、红、绿、蓝等测试画面,相机需要在每个画面下抓取屏幕区域图像,然后判断屏幕上是否存在异常,如果存在,还要进一步判定它属于哪一类缺陷。
常见的缺陷类别包括:亮点(单个或一小簇像素始终高亮)、暗点(像素始终不亮或亮度明显偏低)、RGB子像素坏点、划伤(细长的亮线或暗线)、异物(内屏与外屏之间进入的灰尘、纤维)、水痕/云斑(大面积的亮度不均或水渍状痕迹)。注意,这里面的"亮点"可能只有 3×3 像素,在 800×600 的图像上只占大概 0.0025% 的面积,对比度可能只有 20~30 个灰度值。这个难度和自动驾驶里识别行人、道路上找车辆完全不在一个量级,属于典型的"极小目标 + 低对比度 + 背景单一但复杂"场景。
1.2 两类主流方案的局限
大多数团队接到这个需求,第一反应是先上传统图像处理:拍一张纯色图,计算灰度均值,做一个差分,然后用阈值分割把异常区域切出来。我在早期POC阶段也试过这条路,实际跑下来有三个绕不开的问题。
第一,不同批次、不同型号的屏幕亮度本身就有差异,同一套阈值参数在这块屏上合适,换一块屏就疯狂误检。第二,亮点和传感器噪声在灰度形态上非常相似,单纯靠阈值很难区分,误报率能做上去,但漏检率一直压不下来。第三,水痕和云斑这类缺陷没有清晰的边缘,灰度过渡非常平缓,传统形态学处理基本无能为力。
后来换成了基于 BBox 的通用目标检测模型。模型能给出缺陷的位置和类别,但新的问题又来了:划伤是长条状的,异物可能是细丝状的,一个矩形框里真正属于缺陷的像素可能不到五分之一。标注的人画框时被迫把大量正常区域包进框里,模型学出来的特征天然带噪声。更麻烦的是,脏污上面叠一道划伤的情况非常常见,矩形框的表达能力根本没法描述这种"缺陷套缺陷"的关系。
1.3 为什么最后需要"分割+检测"一体模型
所以我在方案选型的时候,把"分割能力"定成了硬指标。只有把缺陷像素精确分割出来,才能做准确的面积统计、形态判断和类别归属,才能解决矩形框标注带来的特征污染问题。而纯分割模型(比如只做语义分割的架构)又不够用,因为产线最终还是要知道"这是哪一类缺陷",分类信息必须和分割结果一起输出。
这也是我选择基于实例分割模型改造而不是另起炉灶的原因:实例分割天然是"检测+分类+像素级mask"三合一,一个前向过程就能输出所有需要的字段。剩下的问题就是选哪个底座、怎么改。这里我最终选定了 YOLO11-SEG,并在此基础上加入了 SDI 模块。后面几节我会详细解释这一选择背后的逻辑和整套方案的落地过程。
2. 数据这块是真正的护城河:采集、标注与样本扩增
2.1 采集环境的三个关键点
做缺陷检测项目,算法只是最后一公里,前面几十公里全在数据上。我第一次意识到这一点,是在拿到第一批客户样屏数据的时候:样张是在产线现场用手机拍的,角度歪、反光强、底色偏色,直接用这批数据做训练,模型根本学不出稳定的缺陷特征。后来重新搭建了采集环境,才算是走上正轨。
第一,环境光必须控制。会议室白天拉窗帘、晚上关灯都不够,最终是在暗室环境里用遮光布围了一个简易暗箱,排除所有杂散光干扰。第二,相机和屏幕之间要有固定的距离和角度,并加装偏振片消除偏光膜产生的摩尔纹。第三,屏幕亮度标准要统一,测试画面由信号发生器给定固定的颜色值,而不是靠屏幕自带的显示设置。
这里有一个很容易忽略的细节:每一块待测屏幕对应一组多底色图像(纯白、纯灰、纯黑、RGB),数据划分时必须以"屏"为单位划分,而不是以"图"为单位。如果同一块屏的纯白图和纯灰图被分别划到训练集和验证集,模型实质上已经"见过"这块屏幕了,验证指标会虚高五到十个点。这点后面训练章节还会再提。
2.2 标注体系设计:不要一上来就九分类
我跟标注团队磨合了很久,最终把标注类别收敛到五类:bright_dot(亮点/亮斑)、dark_dot(暗点/暗斑)、scratch(划伤)、foreign_matter(异物)、water_stain(水痕/云斑)。一开始客户给的清单里有十几类,像是"亮点A类""亮点B类""细划伤""粗划伤",分类越细标注一致性越差,同一个缺陷两个标注员能标出三种类型,模型学到的类别边界也模糊。后来先合并成五大类,保证标注质量,再在后续产线上用人工复检细分。
标注方式上,我坚持不使用矩形框,而是用多边形精细勾勒缺陷的真实边缘,每个缺陷实例输出一个闭合多边形作为分割mask。像亮点这种只有几个像素的缺陷,多边形其实就是一个小方块或者三角形,外围再留 1~2 个像素的过渡带,标注规范里明确要求"宁缺勿滥",无法确定边缘的像素不标,避免给模型引入噪声。
2.3 合成缺陷数据的正确做法
屏幕缺陷的样本天然稀缺。一块屏可能只有一两个缺陷,要攒几千张真实缺陷图,得靠好几个月的产线积累。我的做法是两条腿走路:一边复盘历史不良品,集中补拍;另一边写了一个缺陷合成程序,在正常屏幕上叠加模拟缺陷。
合成程序里最常用的几类操作:
- 亮点:随机生成 2×2 到 5×5 像素的高亮核心,再加 1~2 像素的高斯羽化,模拟背光漏光效果
- 暗点:类似亮点,但亮度比周围低 30~50 个灰度值
- 划伤:用一条带 alpha 通道的细长线段,配合轻微模糊,随机旋转后叠加
- 异物:去素材库里找真实灰尘、纤维的高分辨率显微照片,抠出前景后随机缩放、旋转、降低透明度再贴到屏幕区域
最终训练集大约 1.5 万张图,其中真实缺陷图约 4000 张,合成图约 11000 张。这个比例经过验证比较安全:合成数据占比过高,模型会学到"假缺陷风格",在真实样本上误检飙升;占比过低,小缺陷类别又欠拟合。当然,合成数据不能做得太干净,我会有意识地在合成图上叠加屏幕本身的纹理噪声、轻微的曝光梯度,让模拟数据更接近实拍风格。
3. 选型逻辑:为什么最终落在YOLO11-SEG上
3.1 候选方案横向对比
在定 YOLO11-SEG 之前,我把市面上能想到的方向都过了一遍。传统 CV 方案前面已经说了,直接淘汰。基于预测框的目标检测模型也排除,理由在第一章讲过。剩下就是语义分割模型、实例分割模型,以及像 YOLO11-SEG 这种带分割头的检测模型。
我做过一个简单的横向对比:
| 方案 | 精度表现 | 推理速度 | 工程生态 | 结论 |
|---|---|---|---|---|
| 传统阈值+形态学 | 低,依赖固定场景 | 快 | 简单但不稳定 | 不适合 |
| 通用BBox检测模型 | 中,长条缺陷表达差 | 快 | 成熟 | 分割能力不足 |
| 重分割网络(如Mask R-CNN路线) | 高 | 慢,部署成本高 | 参数多 | 节拍吃紧 |
| YOLO11-SEG | 高 | 快 | 导出/部署友好 | 最合适 |
这里面的关键矛盾是精度和速度的平衡。产线节拍一般要求单台设备 1~2 秒内完成一张图像的检测,换算成模型推理时间只有几十到一百多毫秒。重分割网络在精度上确实有优势,但推理成本在工业部署时很难接受。YOLO11-SEG 把分割头挂在检测主干上,一次前向同时出框、类别和 mask,整体计算量比"检测+分割两阶段"省了一半以上,部署时导出 ONNX 或者转成 TensorRT 都很顺。
3.2 YOLO11-SEG 的结构优势
我选择 YOLO11-SEG 还有几个更具体的理由。
从模型结构看,YOLO11 主干在高层引入了更强的全局建模能力,对"水痕/云斑"这类大范围、低对比度的区域缺陷更友好;分割头则建立在多尺度特征金字塔之上,能够同时输出不同尺度的实例掩膜。这意味着一个大到覆盖三分之一屏幕的云斑和一个小到几个像素的亮点,理论上都能在同一个网络里被处理。
从训练角度看,YOLO11 官方提供的预训练权重覆盖了通用分割任务,迁移到屏幕缺陷场景后,即使我的真实样本只有几千张,也能很快收敛。我问过自己很多次"要不要换个更新、更花哨的架构",但考虑到团队落地能力、部署工具链的成熟度、社区踩坑资料的丰富程度,YOLO11-SEG 在工业项目里是最稳的选择。
3.3 创新点在哪里:SDI不是为了标新立异
定下 YOLO11-SEG 之后,我原以为直接训练就能出活。但第一批测试结果很打脸:亮点、暗点这两个小目标类别的 AP 只有 0.5 左右,分割 mask 经常出现空洞和整体丢失。用特征图可视化工具看过之后,我发现问题出得很典型——3×3 的亮点在经过主干网络多次下采样之后,在 stride 32 的特征图上只剩一个点,语义信息接近零。分割头拿到的深层特征对小缺陷来说基本是"睁眼瞎"。
这就是 SDI 模块存在的意义。它不是为了让方案看起来更有创新性,而是为了针对性地补充浅层高分辨率特征里的细节信息。YOLO11-SEG 原始结构中,浅层特征虽然分辨率高、细节丰富,但进入分割头时被压缩得不够,语义偏差大。SDI 做的事情是:用深层语义特征作为指导,去筛选浅层特征里真正属于缺陷细节的通道,然后把处理后的浅层特征重新注入分割流程。简单说,它让网络同时拥有"看得全"的深层语义和"看得细"的浅层细节。
4. SDI模块拆解:从设计思路到具体实现
4.1 小缺陷分割丢失的是什么信息
要理解 SDI,先要理解一点卷积网络的分层特性。浅层特征图分辨率高,每个像素对应的感受野小,所以能看到边缘、纹理、孤立亮点这种细节;深层特征图分辨率低,感受野大,能感知整体语义,但也因为多次下采样和池化操作,把小的细节抹掉了。
YOLO11-SEG 在预测 mask 时,分割头会把不同尺度的特征图融合起来,浅层和深层都会用到。问题在于融合是"平等"的,深层语义和浅层细节各占一定权重,但对于亮点这种小目标,浅层细节是唯一有效信息,如果融合策略不能突出它,小目标检测自然就崩了。
我做过一个实验:单独拿浅层特征过一个小卷积头,直接预测亮点 mask,结果 AP 比原版高了不少,但误报也高,因为浅层特征分不清"亮点"和"高光噪声"。这说明问题的关键不是"有没有细节信息",而是"如何让网络知道哪些细节是缺陷、哪些细节是背景"。
4.2 模块结构与实现细节
SDI 模块的思路也因此变得非常清晰:用深层语义来"指导"浅层细节,告诉浅层特征"哪些通道、哪些位置才是真正重要的"。模块大致分三步完成:
第一步:从主干拿两个输入,一个是浅层细节特征(取主干第 2 或第 3 个 stage 的输出,分辨率高),另一个是深层语义特征(取第 4 或第 5 个 stage 的输出,语义信息丰富)。
第二步:对深层语义特征做全局池化,生成一个表示"当前样本语义偏向"的向量,再通过两个全连接层和 sigmoid 激活得到通道权重。这一步本质是让网络学习"看到什么语义时,应该重点相信哪些通道的细节"。
第三步:用通道权重对浅层特征做加权,接一个 1×1 卷积做通道对齐,然后和主分割特征相加完成注入。这样分割头在解码时就能从融合特征里找回小缺陷的精确位置。
配合伪代码更好理解:
class SDI(nn.Module): def __init__(self, detail_channels, semantic_channels, out_channels): super(SDI, self).__init__() self.gap = nn.AdaptiveAvgPool2d(1) self.fc = nn.Sequential( nn.Linear(semantic_channels, semantic_channels // 4), nn.ReLU(inplace=True), nn.Linear(semantic_channels // 4, detail_channels), nn.Sigmoid() ) self.align = nn.Conv2d(detail_channels, out_channels, kernel_size=1) def forward(self, detail_feat, semantic_feat): b, c_d, _, _ = detail_feat.shape semantic_ctx = self.gap(semantic_feat).view(b, -1) channel_weight = self.fc(semantic_ctx).view(b, c_d, 1, 1) detail_weighted = detail_feat * channel_weight return self.align(detail_weighted)实际使用时,我在 backnone 的 C2 和 C3 输出各接一个 SDI,分别对应两个不同尺度的分割支路。语义特征统一取 C4 或者 C5 的输出,保证指导信号足够"高层"。
4.3 融合位置与参数开销
一个经常被问到的问题是:SDI 为什么不放在特征金字塔的最前端,而是放在分割头附近?我做了对比实验,放在最前端会导致浅层特征被加权后直接被后续卷积"淹没",因为后续还有多层卷积在做语义提取,会把细节再次模糊掉。放在分割头附近注入,细节特征在最后阶段才参与融合,保留效果最好。
另一个问题是新增算力成本。SDI 本身只有全局池化、两个全连接、一个 1×1 卷积,参数增加不到整体模型的 3%,推理时间增加约 1~2 毫秒。对比小目标 AP 从 0.5 提升到 0.8 以上的收益,这点成本完全可以接受。
5. 训练中的关键细节:参数选择与调优过程
5.1 输入尺寸和增强策略
训练 YOLO11-SEG 时,输入尺寸直接决定了小缺陷还有没有机会被网络看到。官方默认 640,我第一轮训练用的就是 640,结果亮点类别的 recall 一直上不去。后来切成 800,小目标 AP 提升明显,原因是 3×3 像素的亮点在 800×800 的输入下能映射到更大的特征区域,丢失概率大大降低。我把 960 也试了一遍,精度提升只有 0.3~0.5 个点,但推理时间涨了接近一倍,最终定了 800。
增强策略上,我用得很克制。Mosaic 增强能提升整体鲁棒性,但对小目标很不友好——拼接过程中小缺陷被缩放、裁剪,经常缩到 1~2 个像素甚至完全消失,模型等于在学空气。我的做法是启用 Mosaic 但把概率降到 0.2,同时打开 copy-paste 增强,专门保护小目标实例不被破坏。
5.2 优化器、损失权重与调度
优化器我用 AdamW,很多 YOLO 教程默认 SGD,但在小目标场景下 AdamW 收敛更稳。参数上,初始学习率 0.001,weight decay 5e-4,学习率调度用 cosine annealing,总轮数 200 轮。加载官方预训练权重后,前 10 轮冻结主干前两层,只训练分割头和 SDI 模块,等基础损失降下来再解冻全部层做精调。
损失权重方面,YOLO11-SEG 的默认 loss 包含 box loss、cls loss、dfl loss 和 segmentation loss。我把 segmentation loss 权重从默认值调高到 1.25 倍,因为 mask 质量是后续分类和面积判断的基础,值得给更多权重。对于暗点这个最难学的类别,单独在 cls loss 上加了 1.5 倍的类别权重。小样本类别给加权是老办法,但在这个项目里非常有效。
5.3 训练曲线与收敛判断
训练时我盯三个指标:验证集 mAP50、mAP50-95 和分割 mask 的 IoU。前 50 轮 mAP 涨得很慢,尤其是亮点类别一直在 0.3 附近徘徊,我一度怀疑 SDI 模块写错了。后来检查发现,是因为前 10 轮冻结了主干,SDI 模块单独训练时缺少底层特征配合,收敛自然慢。第 50 轮解冻后,各项指标开始陡增,到第 150 轮左右 mAP50 稳定在 0.91 附近,验证 loss 开始出现小幅回升的过拟合信号,我就把训练提前终止在了第 160 轮。
这期间最大的体会是:不要用训练 loss 判断模型好坏,一定要把验证集指标放出来看。训练 loss 一直在降,但验证 mAP 一旦不再上升,继续训练只会过拟合到训练集的噪声上。
6. 实测效果与部署:评估指标、推理速度与工程落地
6.1 评测结果与误检率分析
最终在约 800 张图的独立测试集上,整体 mAP50 达到 0.92,mAP50-95 在 0.71 左右。分开看各类别的表现:
| 缺陷类别 | mAP50 | 典型漏检情况 |
|---|---|---|
| 亮点/亮斑 | 0.88 | 极小亮点偶尔漏检 |
| 暗点/暗斑 | 0.76 | 对比度最低,最难 |
| 划伤 | 0.85 | 极细划伤易断 |
| 异物 | 0.90 | 透明异物易漏 |
| 水痕/云斑 | 0.93 | 大面积低对比度,效果最好 |
真正到了产线,我反而不太关心 mAP,更关心两个实际指标:漏检率和过检率。漏检率的目标是小于 1%,过检率可以容忍在 5% 左右,因为过检的样本还能靠人工复检兜底,漏检就意味着不良品流出。实测漏检率 0.8%,过检率 4.3%,算是达到交付标准。从混淆矩阵看,误判主要集中在异物和水痕之间——一些带有水痕纹理的异物被归到了水痕类。这类边界情况我留给了复检工位,不强行让模型承担所有分类责任。
6.2 部署导出与推理性能
训练完成后,我把模型导出为 ONNX,再转成 TensorRT 的 FP16 推理引擎。在量产级显卡上,800×800 输入的单帧推理时间约 16~20ms,加上图像预处理、后处理,整链路稳定在 35ms 以内。对 1~2 秒节拍的产线来说,算力余量非常充足,甚至可以一台工控机带两台相机工位。
后处理阶段我加了几个业务逻辑:mask 面积过滤(小于 2 像素的 mask 当成噪声丢弃)、置信度阈值调整(不同类别用不同阈值,暗点阈值放低到 0.3,水痕阈值提高到 0.5),以及连通域合并(同一个缺陷在相邻图像上被切成几段时合并处理)。这些规则看起来简单,但它们决定了模型输出能不能真正落到业务闭环里。
6.3 工程化中最容易被忽视的环节
部署之后,我发现模型精度再高,也敌不过现场环境的漂移。屏幕老化会导致亮度衰减,相机镜头积灰会引入新的噪声,环境温度变化会影响传感器响应。我们没有一劳永逸的方案,只建立了两个例行机制:
- 每班次开始前用一块标准缺陷样张对模型做一次"冒烟测试",如果检出率掉到合格线以下,系统弹窗提示现场操作人员做清洁或重新标定
- 每两周从产线抽一批实时图像回流到离线测试集,跟踪模型指标是否缓慢下滑
算法模型的准确率只是工程化的其中一环,真正决定项目长期稳定性的,是这些看起来不怎么"高大上"的运维动作。
7. 踩坑记录与经验沉淀
7.1 标签噪声比想象中致命
第一次训练到 100 轮的时候,分割 mask 的 IoU 一直卡在 0.7 上不去,后来逐张检查标注数据才发现,很多小亮点缺陷的标注多边形边缘参差不齐,有的甚至把相邻的正常像素也圈了进来。label 层面的噪声会直接变成训练信号里的噪声,后面再怎么调参都补不回来。我们做了一次彻底的标注复核,统一了多边形边缘的平滑规则,重新训练后 IoU 直接涨到 0.78。从那以后,我再也不敢小看标注质量这个环节。
7.2 合成数据与真实分布的偏差
合成数据的问题出现在一个很隐蔽的地方:我在合成亮点时用的都是纯黑背景或纯白背景,而真实缺陷屏的背景往往带有一点渐变或者屏幕本身的亮度不均。合成数据训练出来的模型,在真实均匀背景下表现很好,一遇到亮度不均的屏幕就疯狂误检。解决方法是给合成缺陷叠加真实背景——直接从实拍图库里随机裁背景块,再把缺陷贴上去。这个改动看似不起眼,却把过检率从 8% 压到了 4% 出头。
7.3 数据增强在缺陷检测中的副作用
最后一条坑一定要说:数据增强不是越多越好。我一开始按通用目标检测的习惯开了大量增强,包括随机旋转、随机色域扰动、水平翻转等。训练出来的模型在验证集上指标还行,但到了产线上,因为现场图像没有训练时的"花哨"变化,反而暴露了模型对真实分布的拟合不足。后来我把增强强度调低了一个档位,把多出来的算力让给了更大输入尺寸,效果反而更好。对小目标缺陷检测来说,把细节保真放在增强多样性前面,基本不会错。
如果让我给这个项目做一句话总结,我会说:LCD 屏幕缺陷检测这类场景,难点从来不在模型结构本身,而在于数据质量、细节信息的保留和工程化闭环的把控。YOLO11-SEG 给我们提供了一个足够好的底座,SDI 模块则精准补上了小缺陷在深层特征里的信息空缺。这个方案的关键在于理解每一步选择背后的原因,而不是照搬参数。
最后再分享一个部署时期的实用技巧:因为产线上绝大多数屏幕是正常的,模型算力浪费在了"无缺陷"样本上。我们加了一个前置粗筛——先对整幅图像做全局灰度统计,如果完全没有低对比度异常区域,直接跳过模型推理,只有灰度分布异常的图像才进入 YOLO11-SEG 精检。这个粗筛逻辑用传统图像处理几行代码就能实现,却把整条产线上模型的实际推理负载降了将近一半。这种"传统算法兜底、模型精判"的两级结构,在工业现场远比单靠一个模型更实用。