简介:这份PDF文档系统梳理机器视觉工业缺陷检测的关键知识体系,适合机器视觉工程师、自动化检测从业者以及工业质检项目入门学习者,可帮助避开硬件选型与算法应用中的常见误区,提升项目落地效率。资源为单文件压缩包,仅含1个PDF文档,大小1.26MB,轻量便携,无需复杂环境即可阅读。目前已有1188人学习下载,在图像处理与机器视觉技术社区中具备参考价值。内容涵盖背向照明、前向照明、结构光等光源选择策略,CCD/CMOS相机参数、镜头及图像采集卡配置,并系统介绍图像预处理与检测处理流程,包括边缘检测、模板匹配、灰度变换等常用算法及市面主流算法库;同时结合金属划痕、PCB焊点、Logo印刷等典型缺陷场景,说明照明方式与算法搭配的实际效果。此外还涉及检测结果的数据管理与不合格品处理策略,兼顾理论与实践,适合工业质检项目直接参考。
1. 机器视觉工业缺陷检测:为什么你的产线缺一双“眼睛”
一条产线跑得顺不顺,缺陷检测是最后的守门员。人工目检一直是主力,但上了产能之后,人的稳定性根本跟不上:一个熟练工盯了四个小时屏幕,漏检率会肉眼可见地涨;不同班次的标准不一致,同一块料在前半夜算良品,后半夜就成不良品了。这时候就得靠机器视觉来补位。机器视觉工业缺陷检测的本质,是用相机加算法的组合,把“人眼看图”变成“代码认图”,在料件高速通过产线的时候完成有无、大小、类别、位置的判定。这事能解决的范围很广:半导体封装外观、锂电池表面、汽车零部件、PCB板、金属件划痕,几乎每个制造环节都能看到它的影子。适合谁读?刚接产线任务、需要快速上手视觉方案的工程师,或者已经在用传统算法、但在缺陷稍微复杂一点就频繁翻车的团队——这篇就是顺着这条落地路径讲的。
这套方案不是买台相机再装个开源库那么简单。成像、光源、算法选型、标注、训练、部署、持续迭代,一环套一环,中间哪步糊弄了,后面都得加倍还。接下来我按自己做了几年的顺序往下拆:先把成像方案立住,再谈算法选型,然后给出一个能套用的最小训练闭环,最后把你在现场最常踩的坑一次性列清楚。
2. 成像方案:打好光,选对相机
2.1 光源是“看不见的算法”,角度决定缺陷能不能被看见
很多刚入行的工程师会把精力全放在算法上,但我的经验是:光源才是整个系统真正的胜负手。一个划痕在普通白光下根本拍不出来,换个低角度光一打,背景是暗的,划痕亮得像荧光笔画的——因为划痕本身的漫反射特性和周围完好的镜面反射不一样。所以先别急着调模型,先看图像里目标到底显不显眼。
常见的光源选择有四种:高角度环形光、低角度环形光、同轴光、背光。高角度光适合凸起类缺陷,比如压伤、凸包;低角度光擅长表现划痕和凹坑;同轴光用于镜面表面,比如晶圆或玻璃;背光用来做轮廓检测,拍毛刺、缺料这类边缘缺陷非常灵。实际产线里经常会混合两三种光源,靠频闪切换来完成多次拍摄。
我在做半导体外观检测时最常用的组合是“高角度环形光加同轴光”,前者负责抓主体形变,后者负责抓表面脏污。但打光不是装上就行,关键参数是角度、工作距离和强度。角度太低会引入环境杂光,太高会把细微缺陷压没。一般用45度到60度之间的可调角度光源,先拿缺陷样件试拍,把缺陷对比度调到最大再加一点点余量,防止器件批次差异导致漏检。
2.2 相机与镜头选型:分辨率、帧率、像元大小的平衡
相机选型的核心是分辨率。产线要求最小检多大缺陷,直接决定你用多少万像素。比如要稳定检出0.1mm的划痕,至少要让这个尺寸在图像里占到3到5个像素,否则连算法都无从下手。计算公式是:视野宽度除以最小缺陷尺寸再乘3,就是所需像素数。假设视野是50mm,最小缺陷0.1mm,那么50/0.1×3=1500像素,对应到市面上就是200万像素级别的相机。
帧率也得算清楚。产线节拍如果是每分钟120个料件,每个料件要拍两张图,那么相机帧率至少是4fps以上,一般都直接选30fps以上的相机,留足余量。更隐蔽的一个参数是像元大小。同样是500万像素,像元大的相机感光更好,但空间分辨率略低,像元小的反之。在产线光照条件可控的情况下,我倾向于选像元大小3.45μm左右的全局快门相机,畸变小,拖影几乎为零。
镜头方面,定焦镜头优先,变焦只用在多尺寸产品共线的情况。焦距决定工作距离和视野的关系,一般用C接口的定焦镜头配合接圈来调节放大倍率。唯一要反复确认的,是镜头的靶面尺寸必须大于或等于相机的传感器靶面,否则边缘暗角会直接毁掉判定区域。这些基础参数如果不对,之后算法做得再精巧都没有意义。
2.3 成像方案的验证标准:不只看“能不能看见”
成像方案行不行,不能靠肉眼感觉。我的验证标准很简单:把良品和不良品的灰度分布直方图拉出来,看两者的峰是否分得开。峰分不开,算法再强也难搞;峰分得开,后面用传统方法或者简单模型都能做出不错的效果。所以在正式选算法前,我都会先写一段小脚本统计缺陷区域与背景的灰度差,目标是最小差值至少大于30(0到255灰度范围),否则就容易在光照波动时漏检。
这一步也决定一个关键资源:缺陷样本图库。产线初期最宝贵的东西从来不是代码,而是带着各种形态缺陷的现场图。光源方案相机的组合一旦定下来,就去产线上大量收图,标好缺陷类别、形态和位置。这套图库是后面训练模型和验证算法的地基,没有它,所有精度指标都只是纸面数字。
3. 算法选型:分清传统视觉与深度学习的边界
3.1 传统方法的黄金流程与它的局限
在深度学习普遍之前,工业缺陷检测的主流方案是“图像预处理 + 特征提取 + 规则判定”。预处理这一步常用高斯滤波降噪、形态学操作去毛刺;特征提取靠边缘检测、Blob分析、灰度统计;判定则以阈值、面积、长宽比等硬规则为主。这套方法到今天都没退役,因为它的计算量小、部署简单、可解释性强,而且不需要大量缺陷样本,很适合缺陷形态极其规则的场景。
比如一个纯白色的塑料件上检黑点,不需要任何模型,灰度阈值加连通域分析就能做到99.9%以上的精度。但它的死穴也很明显:只要缺陷形态一复杂,比如金属拉丝表面上的细微划痕、纹理背景上的微小凹坑,传统方法的误判率就会爆炸。因为这类缺陷的特征和背景太接近,固定的阈值和规则根本分不开,需要反复调参数,而且换一个产品型号可能就要整个重来,维护成本极高。
所以我的选型原则是:缺陷特征稳定、对比度高、形态固定的先上传统方法;缺陷特征多变、对比度低、需要泛化的场景直接走深度学习。两者不是谁替代谁,而是各自守住自己的边界。实际产线上,更多情况是结合用——先用传统算法做粗定位,裁出候选区域,再用模型做细分类。
3.2 深度学习模型的选择:分类、检测还是分割
进入深度学习这一步后,第一件事不是选网络,而是先明确任务类型。缺陷检测对应三种典型任务:图像分类、目标检测、语义分割。分类只回答“这块区域有没有缺陷”,适合只需要分拣、不需要定位的场景,计算最快;目标检测给出缺陷的矩形框,适合需要知道缺陷位置和数量的场景,产线上用得最多;语义分割则把缺陷精确到像素级,适合弧形缺陷、微小缺陷、需要量测缺陷面积的场景,但它对标注要求也最高。
对应到模型家族,分类常用ResNet及其变体;检测常用YOLO系列,主流是YOLOv5到v8之间;分割常用UNet及其实用变体。选型时不要盲目追新,要看你的产线资源:有没有GPU训练服务器、缺陷样本量有多大、推理端是工控机还是边缘设备。如果样本量只有几百张,再新的模型也白搭,不如去扩充数据。
我一般会先在固定数据集上跑一个基准,比如用YOLOv8n做检测、用UNet做分割,对比两者的mAP或IoU、单张推理耗时、模型文件大小。最终选型不只看精度,还要看维护成本——产线换型号是常事,模型能不能快速微调、持续集成,比论文里的刷点重要得多。
3.3 用一两个稳健架构先跑通,不追新
在工程落地上,我坚持“先跑通、再优化”。第一版模型选最稳妥的架构,比如检测用YOLOv8n,分割用UNet,把整个数据流和部署链路先打通。等数据量大了,再去对比更复杂的模型是否有收益。很多团队一上来就上大模型,结果训练慢、推理卡、标注跟不上,项目直接烂尾。
以分割任务为例,UNet在工业缺陷检测中一直没被淘汰,不是因为新颖,而是因为它在小样本、单目标、强边缘的任务上确实稳。它的U型结构能同时保留浅层的空间细节和深层的语义信息,对细小缺陷特别友好,而且显存占用比Transformer类模型小得多,在普通单卡上就能完成训练和推理。选型不是做论文,而是找当前条件下最不容易出错的那个。
为了让你有个具体参考,这里给出一个微型UNet在缺陷分割中的实现要点:输入归一化到0到1,裁剪成256×256的小块训练,损失函数用BCEWithLogitsLoss加Dice系数的组合,输出经过sigmoid后按0.5阈值转二值图,再做连通域过滤小噪点。这套组合在多数表面缺陷分割里足够稳定。
# 一个用于缺陷分割的UNet最小训练骨架(PyTorch简化版) import torch import torch.nn as nn class DoubleConv(nn.Module): def __init__(self, in_ch, out_ch): super().__init__() self.conv = nn.Sequential( nn.Conv2d(in_ch, out_ch, 3, padding=1, bias=False), nn.BatchNorm2d(out_ch), nn.ReLU(inplace=True), nn.Conv2d(out_ch, out_ch, 3, padding=1, bias=False), nn.BatchNorm2d(out_ch), nn.ReLU(inplace=True), ) def forward(self, x): return self.conv(x)这段代码定义了一个UNet的基本卷积块。注意,我在这里用了BatchNorm和ReLU的标准组合,批大小不能设得太小,否则BatchNorm的统计量会不稳定,建议批量大小至少8。如果显存不够,可以把图像裁小,但尽量保持批大小稳定。
4. 从标注到推理:跑通缺陷检测的最小闭环
4.1 标注工具与数据划分:别在这上面省时间
缺陷检测的模型效果好与坏,标注质量占一半。很多人以为标注就是拿框把缺陷框出来,其实里面的门道不少:分割任务要求逐像素标注,检测任务要求框必须紧贴缺陷边缘且类别明确,分类任务要保证背景样本的多样性。最常用的标注工具是Labelme和LabelImg,前者输出JSON格式的多边形标注,适合分割;后者输出XML格式的矩形框,适合检测。
数据划分这三层必须分开:训练集、验证集、测试集。测试集绝对不能参与训练过程,哪怕是想看看效果也不行——一旦你根据测试集调了任何参数,这个测试集就“脏”了,不能再代表真实表现。划分比例可以按8:1:1或者7:2:1,但更重要的是保证同一批材料的样本不会全落在同一个集里,比如上午拍的料和下午拍的料要混合切分,避免光照波动被模型当成判别依据。
我见过太多项目在标注阶段翻车:一类缺陷标得很草率,多边形边界缺了个角,模型把缺陷周围的正常纹理也学进去了,推理时误检率非常高。所以标注完一定要做人工复核,至少抽查20%的标注,看边界和类别是否一致。
4.2 训练代码的骨架:从目录结构到Loss曲线
以分割为例,训练集和验证集的组织方式一般是:
dataset/ ├── images/ │ ├── train/ (缺陷图与良品图) │ └── val/ └── masks/ ├── train/ (与images同名的二值标签图) └── val/划分数据之后,训练脚本的核心逻辑就是:读图、预处理、送入模型、计算损失、反向传播。下面给一个最直接的最小训练循环:
# train_unet.py - 最小可复现的训练脚本 import torch import torch.nn as nn from torch.utils.data import DataLoader, Dataset from torchvision import transforms from PIL import Image import os class DefectDataset(Dataset): def __init__(self, img_dir, mask_dir): self.img_dir = img_dir self.mask_dir = mask_dir self.names = [f for f in os.listdir(img_dir) if f.endswith('.png')] self.tf = transforms.Compose([ transforms.Resize((256, 256)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) def __len__(self): return len(self.names) def __getitem__(self, idx): img_path = os.path.join(self.img_dir, self.names[idx]) mask_path = os.path.join(self.mask_dir, self.names[idx].replace('.png', '_mask.png')) img = self.tf(Image.open(img_path).convert('RGB')) mask = Image.open(mask_path).convert('L') mask = transforms.Resize((256, 256))(mask) mask = torch.from_numpy(np.array(mask)).float() / 255.0 mask = mask.unsqueeze(0) return img, mask model = UNet(in_channels=3, out_channels=1) # 复用上一节的UNet criterion = nn.BCEWithLogitsLoss() optimizer = torch.optim.Adam(model.parameters(), lr=1e-4) loader = DataLoader(DefectDataset('dataset/images/train', 'dataset/masks/train'), batch_size=8, shuffle=True) for epoch in range(50): for imgs, masks in loader: preds = model(imgs) loss = criterion(preds, masks) optimizer.zero_grad() loss.backward() optimizer.step() print(f'epoch {epoch}: loss={loss.item():.4f}')这段代码的逻辑很清楚:每张图被Resize到256×256,归一化后进模型;标签图被缩放到同样的分辨率,作为二值目标。训练循环在50个epoch内反复迭代,每轮打印Loss值。判断是否过拟合,要看Loss在验证集上是否回升,如果回升就加数据增强或换早停策略。
4.3 推理阶段最重要的不是准确率,是“能不能扛住产线波动”
推理真正跑起来之后,代码只是整个系统的一小部分。常见做法是:用OpenCV读取相机图像,经过同样的预处理,送入模型,得到输出后做一次后处理,比如剔除面积过小的连通域、对边缘做膨胀操作。但这里有一个最核心的工程问题:产线环境是在变的,同一批料在早上和傍晚拍出来的图像亮度可能不同,模型在某些时刻会突然误检激增。
我的应对是先做好图像质量预检:每次抓拍后,先算图像的整体灰度均值和方差,如果这些统计量和训练集偏差过大,就告警而不是直接判定。这相当于给模型加了一个“状态边界”——在数据分布内说话,偏离分布就不胡猜。这一条比起调阈值,对产线实际漏检率的影响更大。
推理性能够不够,可以用一个简单指标衡量:单张图从取图到判定完成的总耗时,控制在产线节拍的单件生产时间内即可。如果超了,优先检查模型输入尺寸是否过大,其次考虑TensorRT或ONNX加速,不要一上来就换小模型,以免丢失微小缺陷的检出能力。
5. 避坑指南:这五个坑我踩过,你别再踩
5.1 缺陷样本太少,模型学了空气
现象:训练集只有50张缺陷图,模型训练完在验证集上精度还行,一到产线实测,漏检率直接超过20%。
原因:工业缺陷是小概率事件,天然样本少,模型没有见过足够多的缺陷形态,把“没见过”等同于“正常”。
解决:我一般会先保证每个缺陷类别至少有200张以上的图,实在不够就用裁剪、旋转、翻转、缩放等经典增强扩充到1000张级别。如果产线还在爬坡,就先做试运行阶段,利用前两周持续收图,等缺陷种类丰富一点再训练。别急着上模型,样本量不足时再好的模型都是空转。
5.2 标注不一致,模型学会了“噪声”
现象:同一块缺陷,上午标注成了“划伤”,下午标成了“脏污”,模型训练时一会学这个、一会学那个,最后两个类的置信度都特别低。
原因:多人标注时缺少统一标准,不同人的判断尺度不同,边界也不一致。
解决:先做标注规范文档,给每个缺陷类别配三五个典型示例图,并明确边界情况。交标注前先让标注员标同一批50张图,做一致性比对,把差异大的类别重新定义或合并。如果团队只有一个人标,也要定期回头复查,防止疲劳导致标准漂移。
5.3 数据不平衡,良品淹没缺陷
现象:训练集里良品占比超过99%,模型轻易就把所有图判为良品,因为这样准确率也有99%。
原因:类别权重失衡,BCE损失几乎全被良品主导。
解决:用focal loss或给损失函数加类别权重,比如缺陷类的权重设为良品的10倍以上。另一种更直接的做法是欠采样良品图,把良品和缺陷的训练比例控制在1:1左右,缺陷占比太低时甚至可以让缺陷图占主导,再结合数据增强让模型学会泛化。
5.4 过拟合:模型把背景纹理当成了缺陷特征
现象:训练集Loss很低,验证集Loss很高,模型只在“看过”的图上表现好,换一个新型号的料就完全失灵。
原因:缺陷样本太少,模型学到了图片的背景纹理,而不是真正的缺陷边界。
解决:除了增强数据,还要检查输入图像是否归一化到与训练时一致。生产环境的光照和训练环境不一致时,模型更容易把光照差异当成缺陷特征。最好在训练时加入亮度扰动、对比度扰动和随机裁剪,让模型不能依赖背景纹理做判断。
5.5 部署环境和训练环境不一致,推理结果莫名其妙变了
现象:同样的权重,在训练服务器上精度正常,部署到工控机上之后,缺陷检测率下降了5个点。
原因:训练和推理的前处理不一致,比如缩放插值方法不同,或图像颜色空间有差异;也可能是推理时模型量化导致精度损失。
解决:我在部署时会固定一份C++或Python的前处理代码,和训练脚本完全一致,包括缩放算法和归一化参数。尽量用与训练一致的推理后端,比如都用PyTorch或都用ONNX Runtime,避免跨框架的微小数值偏差累积。
6. 进阶技巧:用CutMix增强和历史样本库把模型“养胖”
当最小闭环跑通之后,真正决定项目长期价值的,是模型的持续进化能力。产线的缺陷形态是活的:换了材料批次,划痕可能变得更细;换了工艺参数,气泡可能变得更大。如果模型训完就固定不动,大概三个月后就会开始出现漏检率回升。我的做法是建立两个机制:训练侧的CutMix数据增强,以及运行侧的缺陷样本回流库。
CutMix的做法是把两张训练图各裁一块,在像素级别混合,标签也对应混合。让模型在缺陷类别的“过渡态”上训练,相当于人为制造新样本。比如一张划痕图和一个脏污图各取一半拼在一起,模型就更可能学会在“又划痕又脏污”的真实场景下正确分类。这个技巧不改变模型结构,只需在数据加载时多做一步,收益却很明显——尤其在缺陷形态互相重叠、边界不清晰的场景里,它能直接降低误检率。
样本回流库则是更关键的长线策略。我会在部署现场保留一个“待确认”目录,把模型置信度在40%到70%之间的图存下来,毕竟这类图最容易出错。工程师定期把这些图人工复核一遍,确认后归入训练集,再每周做一次增量微调。刚开始这个回流库可能小得可怜,但跑半年以后,你已经积累了数百张外面的团队拿不到的现场缺陷图,这种数据资产的护城河比模型本身更深。
最后验证一下这套方案稳不稳:选一段连续三天的产线数据,固定模型不做任何调整,统计每天的误检率和漏检率。如果三天数据波动幅度在1%以内,就可以放心交付;如果波动大,先从光照稳定性查起,再回头看前处理和模型阈值。我吃过最大的亏就是把生产波动当成模型问题来调,最后发现是光源衰减,白白浪费了两周。所以现在我习惯在每次排查的第一步就检查光源亮度记录,投影到灰度均值上,任何一点细微的变化,都先确认是不是成像端出了问题。
这个方向值不值得做?只要你的产线上还有人工目检,它就值得。把人工的标准盘下来、把模型跑稳、把迭代机制立起来,这套东西就是从一次性项目变成持续资产。希望帮到你。
本文还有配套的精品资源,点击获取