我站在车间巡检通道上,看着老师傅拿着手电筒一台一台设备照过去,蹲下、起身、记录、再走向下一台。整个过程他很熟练,但我心里清楚,这样的巡检方式漏检是早晚的事——疲劳、光线、视角、注意力,任何一环出问题,异常就会被放过。后来我接到的任务很明确:做一套基于YOLO的工业设备异常检测系统,用固定相机加视觉模型,代替人工完成高频次、重复性的设备外观巡检。
这套系统做下来,踩的坑比想象中多得多,但沉淀下来的方法论也更值钱。这篇文章不打算讲那些官方文档里都能查到的API用法,而是想把这套系统从选型、数据、训练到部署、运维的完整决策过程摊开来讲。如果你正在做类似的工业视觉项目,或者准备把一个目标检测Demo真正推到产线上跑,这篇文章应该能帮你少走不少弯路。
1. 为什么工业异常检测选中了YOLO,而不是传统视觉方案
1.1 工业场景对检测方案的三大硬性需求
先回到需求本身。工业设备异常检测和通用目标检测有个很大的区别:它的场景是受限的,但苛刻程度一点都不低。
第一个硬性需求是实时性。产线不会因为你要做检测就停下来。设备在转、工件在走、传送带在跑,你的检测结果必须跟得上生产节拍。有的工位要求30帧每秒连续分析,有的允许每秒抽两三帧,但不管怎样,"拍一张照片拿回去慢慢分析"的模式在大部分现场是行不通的。这意味着我基本不考虑两阶段检测器——先提候选框再逐框分类的流程,在算力有限的工控机上很难跑出令人满意的帧率。
第二个需求是鲁棒性。工业现场的光照不是实验室里那种恒定的环境光。白天有阳光从窗户斜射进来,晚上有车间顶灯照下来,设备表面还有油污、反光、灰尘。传统视觉方案在处理这类场景时非常脆弱。我见过太多基于边缘检测、阈值分割、模板匹配的旧项目,前期调试得好好的,换个班次、换个天气就崩了。原因很简单:这些方法依赖的是人工设计的特征,而人工特征无法覆盖真实世界的无穷变化。
第三个需求往往被忽略,就是可迭代性。现场的设备会老化,新的故障形态会冒出来,原有的告警规则需要不停调整。一套好的检测方案必须能低成本地适应这些变化。这时候,基于数据驱动的深度学习方案就体现出了巨大优势:新问题出现了,采集一批新样本,标注、微调、重新部署,整个闭环以天为单位滚动。而传统方案每加一种缺陷就要重新写一套特征规则,成本完全不在一个量级。
1.2 YOLO系列选型的实际考量
在深度学习检测器里,模型选择其实是个多层次的问题。
从算法家族看,YOLO属于单阶段检测器,一次前向传播同时输出类别和框位置,天然适合实时场景。相比两阶段模型,它在小目标检测上的精度略有牺牲,但在工业设备异常这个场景里,我们检测的往往是泄漏区域、裂纹、异物这类有一定尺寸的目标,单阶段的精度完全够用。相比某些Transformer类的检测模型,YOLO对算力的要求友好得多,部署生态也更成熟,在边缘平台上跑起来的资料和工具链都更完善。
从具体版本和尺寸看,我建议按算力和目标大小来选,而不是一味追新。如果现场用的是中端工业级GPU,选small或者medium尺寸就够了;如果打算部署到嵌入式边缘盒子,就得用nano或small,并且接受精度的适当让步。还有一个常被忽略的点:小缺陷靠提分辨率比靠换大模型更有效。同一个模型,输入从640提到1280,对小裂缝、小泄漏的召回率提升往往非常明显,代价只是推理时间增加。所以在项目初期,我通常会先把输入分辨率拉高试一轮,再决定要不要升级更大的模型参数。
2. 系统架构怎么搭:从产线相机到最终告警的一条完整链路
2.1 图像采集端的组件与选型
很多做算法的同学容易忽略采集端,但我可以负责任地说,这套系统里图像质量直接决定模型上限。相机、镜头、光源、触发方式,每一环都值得认真对待。
项目里我用的是500万像素的工业面阵相机,接GigE接口,选全局快门而不是卷帘快门。原因很简单:全局快门在设备振动或快速运动时不会产生果冻效应,拍出来的图像不会因为运动而扭曲。镜头焦距根据工作距离和视野范围来定:大概一米左右的距离拍一台设备,用12mm到16mm的定焦镜头比较合适,视野能覆盖设备主体,又能保留足够的细节分辨率。
光源这块,环形LED灯是标准答案,因为它能提供均匀、无频闪的照明。但我特别想强调一点:如果设备表面是金属材质,直射光会产生强烈反光,反光区域在图像里往往是一片白,模型根本看不到下面的裂纹和泄漏。这种情况下,加偏振片是性价比极高的解法。偏振片能滤掉大部分镜面反射光,只保留漫反射光,金属表面的细节立刻清晰很多。这套东西成本不高,但效果比调模型参数显著得多。
触发方式上,我建议用光电传感器或编码器信号来触发相机拍照,而不是让相机自己按固定帧率采集。固定帧率的问题在于,它拍到的画面和设备的运行节奏不同步,可能正好错过关键位置。外部触发能保证每次拍照时设备都处于相同或相似的姿态,后续检测的一致性会好很多。
2.2 检测服务、告警策略与数据回流的职责划分
整个系统的软件侧,我倾向于拆成三个独立的服务模块,而不是全部塞进一个进程里。
采集服务负责从相机取图,做基本的图像预处理,比如裁剪ROI、缩放、格式转换,然后把图像丢进消息队列。推理服务从队列里消费图像,跑模型推理,输出检测框、类别、置信度。告警服务接收推理结果,执行误报抑制策略,决定是否触发声光报警或者推送消息到值班室。三个模块之间用消息队列解耦,好处是相机卡顿或者偶发的推理超时不会拖垮整个链路,任何一个模块重启都不影响其他模块运行。
这个架构里还藏着一个关键模块:数据回流。每次推理产生的结果,无论是否触发报警,我都会把图像和检测信息落盘保存,特别是那些置信度接近阈值的"暧昧"样本。这些数据是后续迭代训练的命根子。很多项目上线跑了一周,模型表现开始下滑,但工程师拿不出现场的真实数据来分析,只能干瞪眼。有了数据回流,你随时能回溯"模型在那一帧到底看到了什么",也能快速攒出一批新样本用于再训练。
3. 数据集工程:异常样本的采集、标注与平衡
3.1 定义异常类别:从"有问题"到"可学习的问题"
做数据集之前最重要的一件事,是跟现场的设备工程师、维修师傅坐在一起,把"异常"这个词拆成可标注、可学习的类别。
我见过不少项目想做一个万能的"异常检测"模型,把所有不在正常范围内的情况都标成一类,结果模型训练出来完全不可用。原因是"异常"这个概念太抽象,视觉特征不统一。漏油是一团暗色的液体区域,异物是形状突兀的物体,指示灯异常是颜色和亮度的变化——这三者的图像特征完全不同,硬塞进一个类别只会让模型无所适从。
正确的做法是把异常类别拆细。在这个项目里,我最终定义了五个类别:润滑油脂泄漏、表面裂纹、紧固件缺失、异物附着、状态指示灯异常。每个类别都有明确的视觉判别依据,标注员能分清,模型也能学到各自的区分性特征。这里有个判断原则:如果一个类别连人都无法通过外观轻易区分,那趁早把它从检测任务里拿掉,别指望模型能做到人做不到的事。
3.2 样本采集的现实约束与替代方案
数据采集是我在工业项目里遇到的最大瓶颈,没有之一。正常样本非常好办,相机架好跑一天就能攒下上万张。异常样本就难了,因为设备大部分时间确实是正常的,真正的故障一天到晚可能就发生几次。
我的做法是三条路并行。第一条,翻历史监控录像和维保记录,把过去半年里出现过异常的图像片段都捞出来,人工截帧。这条路的成本低,但图像质量参差不齐,需要大量筛选。第二条,做故障模拟。在不影响生产安全的前提下,和设备工程师配合,人为制造一些异常场景:在设备表面涂抹模拟油液,放上异形工件,拆掉一个螺栓,然后拍照记录。这是最高效的方式,能在一个下午采集到几百张高质量的异常样本,而且类别、位置、严重程度都能控制。第三条,从维修车间收集报废零件。这些零件上有真实的裂纹、磨损、锈蚀,把它们放到设备附近拍一批照片,能增加模型对真实缺陷形态的认知。
采集时要刻意覆盖多样性:不同角度、不同距离、不同光照、不同严重程度。千万别只拍"完美"的异常样本。模型在训练时没见过接近临界状态的样本,部署后就容易在模糊情况下摇摆不定。
3.3 标注规范和增强策略的实际经验
标注工作看起来简单,实际上坑很多。首先,标注框要紧贴异常区域,但不能切掉边缘特征。比如漏油区域往往有一片晕开的痕迹,如果只框最浓的部分,模型学到的特征就不完整;如果框得太大把周边正常表面也包进去,又容易误检。这类尺度把握问题,需要在标注规范里写清楚,并且让两个标注员交叉抽检。
其次,过小的标注框要格外小心。假如一张500万像素的图里,某个螺栓缺失区域只占几十个像素,这个标注框基本就是标签噪声,训练时反而会干扰模型。我的经验是设置一个最小标注尺寸,小于这个尺寸的框直接放弃或者用更高分辨率的采集方式来处理,而不是硬标。
数据增强方面,我做得比较克制。工业场景本身是相对稳定的,不像自动驾驶那样需要应对无奇不有的路况。我做的主要是亮度抖动、轻微模糊、小角度旋转、随机裁剪,以及针对小目标问题的复制粘贴增强——把异常区域裁剪出来,随机粘贴到正常图像的不同位置,相当于在小样本情况下变出了大量带位置多样性的正样本。马赛克增强我用的比较少,因为它会把小目标缩得更小,有时反而让模型学不到小裂纹的特征。另外,一定要留一部分纯正常图像作为负样本,不标注任何框,直接参与训练。这一招对降低误报率的效果极其明显。
4. 训练阶段的关键配置与调参记录
4.1 模型版本、输入尺寸与超参数的设置思路
训练阶段的第一步是确定输入尺寸。我先在几台不同设备场景下统计了标注框的像素尺寸。如果大多数异常框的长边在40像素以上,640的输入就够了;如果很多异常框只有二三十像素,那直接把输入提到1280,带来的召回提升比换大模型更直观。
超参数这块,我的基准配置是这样的:优化器用AdamW,初始学习率0.001,权重衰减适当开一点;批次大小根据显存来定,12GB显存跑640输入可以到16到32;训练轮数200到300,配合早停策略,以验证集上的mAP为监控指标。预训练权重用通用开源数据集上预训练好的通用目标检测权重,即使检测类别完全不同,它学到的底层纹理和边缘特征对工业图像依然非常有价值,能让模型在更少的轮次内收敛。
要不要冻结主干网络,取决于数据量。如果异常样本只有几百张,我建议先冻结主干训练50轮,让检测头先稳定下来,再解冻全部参数做全量微调,这样能显著减少过拟合。如果样本量已经上千,就直接全量微调,收敛更快。
4.2 训练曲线怎么看:过拟合、欠拟合与损失不降
训练过程不会一帆风顺,我整理了几个最常遇到的现象:
| 观察到的现象 | 可能的根因 | 处理方向 |
|---|---|---|
| 损失值震荡或直接发散 | 学习率过高或标注中存在明显错误 | 降低学习率,抽检标注数据 |
| 训练损失持续下降但验证mAP停滞 | 过拟合 | 加强正则、增加负样本、减少轮数 |
| 验证集召回率高但精确率低 | 误检多,模型学到的是宽松特征 | 提高置信度阈值、增加ROI约束 |
| 验证集精确率高但召回率低 | 小目标漏检 | 提高输入分辨率、补充正样本 |
我在项目里遇到最典型的情况是过拟合。异常样本少,模型很快就把训练集"背"下来了,训练损失一路降到0.2以下,但验证集mAP只有0.5出头。这时候我没有急着加网络复杂度,而是先扩充了负样本和做一些现场拍摄的难例补充,同时把数据增强强度往上调了一档。两轮之后,验证mAP明显回升,说明问题不在模型容量,而在数据多样性。
4.3 评测指标以业务为准:mAP不等于现场效果
mAP是算法圈的标准语言,但现场运维人员不认这个。他们关心的是两件事:真正发生异常的时候系统能不能报警,以及一天下来会误报多少次。
所以在评测阶段,除了计算mAP@0.5和mAP@0.5:0.95这类离线指标,我还会做一次"业务模拟":从现场采集连续几小时的视频片段,跑完整个推理链路,统计报警次数、漏报次数和误报次数。这个测试里,告警策略、置信度阈值、时间平滑逻辑都会真实生效,结果才反映了系统上线后的实际体验。
我当时定的验收标准是:关键类别召回率不低于95%,误报率不超过每个班次2次。这个标准看着简单,但为了同时满足这两条,我在部署阶段动了很多脑筋,接下来详细说。
5. 部署落地:模型导出、推理加速与误报抑制
5.1 从训练权重到可上线服务的完整流程
训练完的模型不能直接拿到现场跑,还需要几步关键处理。
首先把模型导出成开放的标准交换格式,这一步是为了摆脱对训练框架的依赖,让后续在推理引擎里做加速成为可能。导出时把输入尺寸固定下来,不要留动态维度,动态维度虽然灵活,但会拖慢推理速度,还可能在部分推理引擎上触发额外的时间开销。导出后我习惯写个小脚本,喂同一张图给训练模型和导出模型,对比输出框的坐标和置信度,确保转换过程中没有精度损失。这一步很容易被忽略,但一旦转换有损,后面所有调参都是在错误的基础上进行。
接下来是精度压缩。工业现场如果能跑半精度推理,尽量用半精度,速度提升明显而精度损失通常可以忽略。如果是部署到更小的边缘设备,可以考虑量化到8位整型,但务必先跑一遍完整的验证集对比指标。我见过量化之后个别类别召回率掉五六个点的情况,因为工业异常图像里的细节纹理对量化误差很敏感。
服务层面,我把推理服务做成无状态的HTTP接口,输入一张图返回检测结果。告警服务收到结果后,再根据策略判断是否报警。整个服务做成容器化部署,方便在不同机器之间迁移,也方便做模型版本的回滚。
5.2 推理性能实测与瓶颈分析
我把自己在实际环境中跑出来的大致数据列一下,给个参考范围,具体数值会随硬件和模型版本变化:
| 模型尺寸 | 输入分辨率 | 运行平台 | 单帧推理耗时 | 显存占用 |
|---|---|---|---|---|
| nano | 640 | 边缘盒子 | 25-40ms | 低于1GB |
| small | 640 | 中端GPU | 8-12ms | 2-3GB |
| medium | 640 | 中端GPU | 15-20ms | 4-6GB |
| small | 1280 | 中端GPU | 30-50ms | 6-8GB |
我在项目里用的是small模型加1280输入,单帧总延迟大概40-50ms,算上网络传输和告警处理,整套链路稳定跑在20帧每秒左右。这个速度对于巡检设备足够了,但如果你想同时分析四五个相机流,就得考虑降帧率、拆多路推理,或者把模型换成nano。
有个性能瓶颈值得一提:图像预处理。很多人只盯着模型推理时间,忽略了Resize和归一化也要吃CPU。如果图像分辨率高、流量大,预处理线程会逐渐成为瓶颈。我的做法是开独立的预处理线程池,并且用共享内存避免图像数据在模块间反复拷贝,这个优化能省下不少时间。
5.3 误报抑制:置信度、时间平滑和区域约束
误报是工业检测系统能否被现场接纳的生命线。一个每天乱叫的系统,运维人员第一周还会去看看,第二周就会把报警声音关掉,整个项目就名存实亡了。
我常用的抑制方法有四层。第一层是类别级置信度阈值。不同类别的置信度分布不一样,比如漏油检测的置信度普遍较高,异物检测可能偏低,我会给每个类别单独设阈值,而不是用全局一刀切。第二层是时间平滑,核心逻辑是:不能因为某一帧检测到框就立刻报警,而要连续几帧内多次确认才触发。第三层是ROI区域掩码,手动把画面里不需要检测的区域(比如行人通道、旁边的其他设备)遮掉,这些区域正常运行时根本不会出现异常,框出来只能制造噪音。第四层是冷却时间,同一个位置报警之后,在规定时间内不再重复报警,避免一个持续性的异常刷屏。
时间平滑我单独写下:
from collections import deque # M=5:滑动窗口长度;N=3:窗口内命中次数 window = deque(maxlen=5) def on_frame_detected(detections): # 当前帧只要有满足条件的检测框就算命中 hit = len(detections) > 0 window.append(hit) hit_count = sum(window) if hit_count >= 3 and not alarm_state.active: alarm_state.active = True trigger_alarm(detections) elif hit_count < 3: alarm_state.active = False这套逻辑的代价是报警延迟几帧,对于慢速移动的设备异常完全可接受。但对快速变化的场景,比如传送带上瞬时通过的异物,窗口就不能设太大,否则异常已经飞出画面才报警,就错过了。
6. 现场运维的坑:光照、遮挡、样本漂移,一个都不能少
6.1 光照变化:工业现场最常见的"隐形杀手"
上线运行之后,第一个给我教训的就是光照。
项目初期我用的是自动曝光相机,结果发现同一台设备在不同时段的亮度差异很大,模型时好时坏。更头疼的是自动曝光在遇到画面里人员走动时,会突然调整曝光参数,导致整个画面闪一下,这一闪可能就多出几个假检框。解决办法非常朴素:把所有相机改成固定曝光、固定白平衡,把现场的光源环境固化下来。如果需要应对夜晚和白天的差异,就在硬件层面加遮光罩,或者补充恒定的人工光源,而不是指望软件去适应光照波动。
我印象最深的一个案例是:某个漏油检测点一到下午就开始误报,排查了一个星期才发现是西边的窗户在特定角度把阳光射进来,在地面上形成了一片暗色光影,形状恰好和油液泄漏区域很像。最后的解法和AI没关系:拉上窗帘,加一块遮光布,问题立刻消失。做工业视觉,先解决物理世界的问题,再谈模型优化。
6.2 遮挡与反光:单镜头视野内外的博弈
工业现场永远不可能像实验室那样干净。操作工会在设备前走动,维修人员会蹲在设备旁边,叉车会从镜头前开过。这些都可能导致目标被短暂遮挡,模型漏检。说实话,遮挡情况下的漏检很难完全避免,我的处理思路是降低期望、增加冗余:一是优化相机安装位置,选择人员活动较少的视角;二是告警界面里给低置信度帧打上标记,运维人员看到标记会知道这一帧被遮挡了,需要人工复核,而不是直接信任"无异常"的结果。
反光问题在金属加工车间特别突出。车床、冲压机、传送带框架都是金属,表面有大量镜面反射。明明没有油污,但反光区域在图像里就是一片亮白色,有时还带着彩色光晕,模型很容易把这类区域误判成异常。偏振片依然是第一推荐方案,同时我会在数据增强里加入高光模拟,让模型见过这种"亮到发白"的样本,它就不会一看到高光就紧张。
6.3 持续迭代机制:模型上线不是终点
模型部署上去只是系统生命周期的开始,这一点是我在整个项目里体会最深的地方。
设备会老化,表面会掉漆,新的零部件可能长得和旧的不一样,车间里的背景也可能因为改造而变化。这些变化积累到一定程度,模型精度就会悄悄下滑。我建立了一套以周为单位的迭代机制:每天从数据回流里抽取那些置信度接近阈值的样本和误检样本,每周做一次标注和筛选,每两周在积累的新数据上做一次微调训练,然后在固定的测试集上对比新旧版本的指标,只要新版本在召回率不降的前提下提升了精确率,就发布到生产环境。
这套闭环的价值在于,它让系统具备了自适应的能力。三个月后,模型对现场新情况的熟悉程度已经远超刚上线时的状态。而做到这一切的前提,正是前面提到的数据回流模块——没有它,迭代机制就是空中楼阁。
最后再分享一个操作层面的小技巧:在处理误报问题时,不要一上来就动模型。先把误报图像调出来,分析它们出现的场景规律,很多误报用ROI掩码、光照调整、相机角度微调就能解决,这些手段成本低、见效快、不影响召回。我碰到过不少团队,误报率高了就盲目加阈值或者重训模型,结果误报没压下去多少,真正的异常反而开始漏了。先修物理环境,再调推理策略,最后才考虑动模型——这个优先级顺序,能帮你省下大量无效工作。