
最开始接到齿轮缺陷检测这个需求我以为难度会在网络结构上后来发现真正费劲的是让模型在车间那种复杂光照环境里稳定输出。这套基于YOLOv8的齿轮缺陷检测系统支持图像、视频、摄像头三种方式的实时检测核心是PyTorch框架下的Python源码我前后迭代了好几版才算达到能放进产线试运行的水平。如果你正打算做类似的工业视觉质检项目或者已经跑通了模型但部署到实际环境时效果不稳定这篇东西应该能帮你少踩不少坑。先说清楚这套系统能干什么输入一张齿轮照片它能框出缺齿、崩角、裂纹、划伤、点蚀这些缺陷的位置和类别输入一段视频它能对每一帧做检测并把结果画在画面上接上USB或网口摄像头它能保持实时检测适合做产线工位的在线抽检。整个链路从数据标注、模型训练到推理脚本都是Python源码基于PyTorch框架二次开发起来比较顺手。1. 为什么齿轮质检这个场景最终选了YOLOv81.1 传统视觉方案在齿轮表面缺陷上到底差在哪齿轮缺陷检测在工业现场并不是个新问题。早些年大家用的一直是传统机器视觉那套阈值分割、边缘提取、模板匹配。这套方案在一些光线极其稳定、拍摄角度完全固定的场合确实能用一旦条件稍微浮动就很容易崩。比如齿轮表面是金属反光面不同角度进来的光会在齿面形成完全不同的灰度分布同一个划痕一会儿看得清一会儿看不清阈值分割结果忽好忽坏。再加上齿轮本身形状复杂齿根、齿顶、端面各有各的反光特性传统算法得为每一种缺陷单独写规则光调参数就能调掉一两个星期。还有一个绕不开的问题传统视觉只能做“有或无”的判断很难给出缺陷的位置信息。实际质检里工人不光要知道这个齿轮有没有问题还得知道缺陷发生在哪个齿、什么位置、大概多大面积这样才能决定是报废还是返修。目标检测模型天然输出框和类别位置信息直接就有了这省了不少后处理工作。1.2 YOLOv8在工业质检场景里的几个关键优势视觉检测任务可选的方向很多分类网络、语义分割、实例分割、目标检测都能沾边但最终我选了YOLOv8核心原因是它在精度、速度和工程易用性之间取得了很实际的平衡。首先是推理速度。YOLOv8作为单阶段检测器一张640x640的图像在普通消费级显卡上跑一次前向只需要10毫秒左右换算下来单张图像检测完全可以接受视频流也能做到实时的程度。这对产线工位的检测节拍很重要节拍快的产线可能要求2秒内完成一件检完慢一点的也就5到8秒单阶段检测方案在这个时间预算里非常从容。其次是工程生态。Ultralytics提供的Python包把训练、验证、导出、推理封装得很完整。数据集准备好之后一条命令就能开训导出ONNX也只需要一行代码。对于以项目落地为目标而不是以发论文为目标的团队来说这种开箱即用的特性非常宝贵。再有一个容易被忽略的点YOLOv8的多尺度预测能力。齿轮上的缺陷尺寸跨度非常大缺齿是几十上百像素的大目标而细微裂纹可能只有几个像素宽。YOLOv8的检测头在三个不同尺度的特征图上做预测大目标小目标都能覆盖到这比把整张图粗暴resize到固定尺寸再分类的传统路子要合理得多。1.3 模型尺寸怎么选n、s、m不是越大越好YOLOv8按参数量分成了n、s、m、l、x几个版本。我见过不少刚开始做项目的人一上来就直接上yolov8x理由是“模型越大精度越高”。话是没错但工业质检场景首先考虑的不是极限精度而是综合性价比。我把几个主流尺寸的实测情况整理过一遍可以给你参考模型尺寸输入尺寸单帧推理耗时RTX 3060模型体积适合场景YOLOv8n640x640约6-8ms约6MB快速验证、低配置机器、实时性要求极高的产线YOLOv8s640x640约9-12ms约22MB一般工业质检项目的首选精度和速度比较均衡YOLOv8m640x640约15-20ms约52MB对精度要求高、推理设备配置较好的场景YOLOv8l/x640x64025ms以上上百MB离线批量检测实时场景很少用我的建议是从yolov8s入手。先花半天时间把整个流程跑通确认数据和标注没有问题再根据实际精度决定要不要换m。很多人跑不动就以为是自己模型不够大其实80%的情况问题出在数据集质量上模型反而背了锅。2. 数据才是这个项目的大头采集、标注与增强2.1 打光和拍摄规范决定了模型的天花板做工业视觉项目的人经常说一句话算法只占20%数据占80%。这个比例在齿轮缺陷检测上体现得淋漓尽致。缺陷能不能被模型看见首先取决于物理成像。同样的一个划痕低角度环形光下可能非常清晰换成高角度同轴光就完全消失。齿轮端面的细小裂纹尤其挑剔需要特定角度的光才能照出明暗对比。我建议在数据采集阶段一次性把光源方案定死并且记录下来后面所有训练数据的拍摄都严格遵守同一套规范。我当时用的是一套低角度环光和同轴光可切换的光源相机固定在一个高度镜头光轴垂直于齿轮端面背景用深色无纺布跟齿轮的本体钢色形成明显反差。这样的好处是目标在图像里非常突出模型不需要花太多能力去学习区分齿轮和背景可以集中精力学缺陷特征。拍摄角度和工况也要提前想好。如果产线实际是斜着拍的训练数据里就必须包含斜视角度的图像。如果齿轮会放在不同位置训练数据里就不能只有居中一个位置。这些看起来不是算法的活却直接决定模型落地时好不好用。2.2 标注细节决定缺陷能不能学得会数据采集完成后标注是整个项目里最繁琐也最关键的环节。我用LabelImg手动标注导出成YOLO格式的txt文件。这里有几个实际操作中总结出来的经验第一缺陷边界要贴实际边缘尤其是裂纹这种长条形缺陷。标注框稍微松一点模型就会学到一个边界模糊的正样本预测框也会变得松松垮垮最后IOU算下来很难看。小裂纹哪怕只有几个像素宽只要肉眼能分辨就应该尽量精准地框出来。第二类别别分得太细。我一开始把划伤分成“轻微划伤”和“重度划伤”两个类别结果两个类别之间的界限很模糊标注到后来前后标准都不一致模型训练出来两个类互相打架。后来合并成一个类精度立刻上来了。分类的核心是“能不能帮助质检做决策”而不是“能不能描述得更细”。第三负样本一定要加。很多第一次做检测项目的人会漏掉这个环节。负样本就是没有缺陷的正常齿轮图片模型需要大量负样本才能学会“什么都不输出”。没有负样本或者负样本比例太低模型会倾向于误报。我的经验是负样本占总数20%到30%比较合适。2.3 数据增强要围绕“模拟真实打光变化”来做YOLOv8在训练时默认会做mosaic、翻转、颜色扰动等增强。默认配置可以直接用但针对这个项目我额外调整了增强策略。齿轮是旋转对称体但它不是随便什么角度都成立。齿的朝向对缺陷检测没有本质影响旋转90度、180度完全合法但如果转一个37度这种任意角度图像边缘会出现大片空区域mosaic拼图时会引入大量无关背景。所以我在增强里只保留了90度倍数的旋转另外加了水平翻转和垂直翻转。光照扰动我特意调大了强度。因为产线实际运行时灯光的亮度会因为老化、电压波动产生细微变化同一个齿轮在不同时段拍出来灰度值会不一样。训练时把亮度、对比度、HSV的扰动范围适当加大模型对光照变化的鲁棒性会明显提高。实测下来加了这层增强之后换到新光环境下掉点的情况减少了很多。3. 训练流程复盘从dataset.yaml到最终权重3.1 数据集格式转换标注完成后数据是这种结构gear_defect_dataset/ ├── images/ │ ├── train/ # 训练集图片 │ └── val/ # 验证集图片 ├── labels/ │ ├── train/ # 对应的txt标签 │ └── val/ └── gear_defect.yaml每张图片对应的txt文件里每一行代表一个目标格式是类别id、中心点x坐标、中心点y坐标、框宽、框高。这些坐标都是相对图像宽高的比例值数值在0到1之间。标注工具导出的原始坐标是像素坐标需要一个转换脚本。这个脚本逻辑很简单读取标注框的左上角xy和右下角xy换算成中心点加宽高的归一化格式再写入txt。注意写文件时浮点精度要保留6位否则后续训练读取时会有微小的框偏移。3.2 配置文件与训练命令在gear_defect.yaml里写好路径和类别名path: gear_defect_dataset/ train: images/train val: images/val nc: 4 names: [chip, crack, scratch, pit]这里的nc是类别总数names的顺序必须和标注时类别的id一一对应顺序错了训练出来的模型预测结果全乱。然后跑训练yolo train datagear_defect.yaml modelyolov8s.pt epochs150 imgsz640 batch16 device0第一次跑项目的时候我在batch大小上犯过错误。显卡显存不够用batch设太大直接OOM设太小训练速度又很慢。实测下来16G显存跑yolov8s、640输入、batch16比较合适batch8也可以就是慢一些。3.3 训练策略冻结权重与后段精调工业数据集一般不像开源数据集那么大齿轮缺陷类别又比较特殊从头训练很容易欠拟合。我的做法是用官方在COCO上预训练好的yolov8s.pt作为起点做迁移学习。训练过程中我用了一个小技巧先冻结backbone只训练检测头跑几十个epoch让新类别的特征映射先收敛然后解冻全部参数用小学习率精调整个网络。这样做的好处是前期不容易破坏预训练提取到的通用特征后期又能让深层特征更贴合齿轮缺陷数据。超参数上我用的是半精度FP16混合精度训练显存占用和训练时间都省了不少。优化器方面AdamW在自定义小数据集上表现稳定收敛也比SGD更平稳。学习率初始值设置在0.0005到0.001之间用余弦退火逐步下降。训练过程中要一直盯着每个epoch结束保存的验证集mAP50和mAP50-95。mAP50更贴近缺陷检测的实际口径我更看重这个指标。如果训练到最后mAP50不升反降说明模型开始过拟合了要么加数据增强强度要么减少epoch数要么早停别硬着头皮跑满。4. 图像/视频/摄像头三种模式背后的统一推理架构4.1 检测核心一个函数走天下三种检测模式最开始看起来像三个功能本质上可以共用同一条推理链路。模型加载一次检测逻辑封装成一个函数图像、视频、摄像头三种输入源只是把各自的帧喂给同一个函数。核心代码结构大概是这样的import cv2 from ultralytics import YOLO class GearDefectDetector: def __init__(self, weights_path, conf_threshold0.25): self.model YOLO(weights_path) self.conf_threshold conf_threshold def detect_frame(self, frame_bgr): results self.model.predict( sourceframe_bgr, confself.conf_threshold, imgsz640, verboseFalse ) return results[0]传入一帧BGR图像返回检测结果结果里包含每个目标框的坐标、类别和置信度。图像检测就是读一张图、调一次这个函数、把结果画上去。视频检测就是循环读取每一帧、逐帧调用。置信度阈值这个参数值得说一下。调太低了模型会把一些纹理复杂的正常区域误判成缺陷调太高了真实缺陷又会漏检。我的经验是先设0.25在验证集上跑一遍统计误报和漏报的分布再根据业务上哪一个代价更高来微调。如果产线漏检一个坏齿轮的损失远大于误检一个好齿轮那就应该往低调阈值。4.2 视频流处理必须加跳帧策略逐帧检测在慢速齿轮转动的场景下是可以的但遇到轴向移动速度快的产线连续帧之间的检测结果高度重复逐帧推理浪费算力。我的做法是加一个跳帧参数每隔N帧检测一次其余帧直接复制上一次的检测结果。跳帧逻辑不复杂frame_interval 2 frame_count 0 while True: ret, frame cap.read() if not ret: break if frame_count % frame_interval 0: result detector.detect_frame(frame) frame draw_result(frame, result) else: # 上一帧的检测框继续叠加显示 frame cv2.addWeighted(frame, 0.6, overlay_cache, 0.4, 0) frame_count 1实测下来1080p视频流开启跳帧后处理帧率能提升到不跳帧情况下的近3倍。检测结果因为帧间变化很小跳帧带来的效果损失基本看不出来。4.3 摄像头实时检测用线程加队列避免界面卡死摄像头模式最需要规避的一个问题如果直接把解码、推理、画框、显示全部串在一个主循环里一旦推理耗时波动画面就会一卡一卡而且窗口的键盘响应会变迟钝。解决办法是拆分生产者和消费者一个线程负责从摄像头拉流把帧放进队列另一个线程负责从队列取帧、推理、显示。from threading import Thread from queue import Queue frame_queue Queue(maxsize2) def capture_thread(cap): while True: ret, frame cap.read() if ret: if frame_queue.full(): # 如果队列满了丢弃最旧的帧保证画面延迟可控 frame_queue.get() frame_queue.put(frame) # 主线程 Thread(targetcapture_thread, args(cap,), daemonTrue).start() while True: if not frame_queue.empty(): frame frame_queue.get() result detector.detect_frame(frame) overlay draw_result(frame, result) cv2.imshow(Gear Defect Detection, overlay) if cv2.waitKey(1) 0xFF ord(q): break队列大小我特意限制成2这样处理速度跟不上采集速度时新的帧会直接替代旧帧画面延迟始终控制在一到两帧内不会越积越多越来越卡。这个设计的出发点很简单实时检测场景里看“现在”比看“半秒前”的结果更重要。5. 实测数据与两个绕不过去的工程坑5.1 跑下来的实际性能数据我用一张RTX 3060显卡做推理测试输入640x640yolov8s权重统计下来几个模式的表现如下模式平均耗时吞吐/帧率说明图像检测9-14ms/张70-100张/秒不含图像解码时间视频流检测1080p逐帧33-40ms/帧25-30fps含解码和画框视频流检测1080p跳1帧约12ms/帧约55fps跳帧后性能明显提升摄像头实时检测640x480约12ms/帧30fps稳定USB摄像头mAP50在验证集上最终稳定在0.91左右mAP50-95在0.74左右。这个水平对于缺陷检测来说够用但前提是验证集的打光环境和训练集一致。换到完全不同的环境数字会往下掉。5.2 坑一长条形细小裂纹漏检调阈值没用第一次用验证集测试的时候发现崩角、缺齿这类大缺陷基本都能框准细裂纹漏检却非常严重。一开始我以为是置信度阈值太高把阈值从0.25一路降到0.1漏检是少了一点但误报暴增背景纹理大量被判成裂纹。后来排查发现问题的本质是输入分辨率。细裂纹在640x640的输入图像里往往只有几个像素宽经过特征提取网络下采样32倍之后到最高层特征图上可能连一个像素都占不满特征完全消失了。我的解决办法分两步。第一把输入分辨率提高到1280x1280小目标的像素宽度在特征图里能占据更多有效位置漏检明显改善。代价是推理耗时从12ms涨到接近30ms但对1到2秒节拍的产线完全能接受。第二给裂纹类别单独增加了一批局部放大图就是把齿轮的高分辨率照片裁剪出裂纹区域让模型见过更多“小目标长啥样”的正样本。两步做完裂纹召回了大概三成。这个坑给我的教训是小目标漏检先别急着调阈值先检查输入分辨率够不够、小目标的特征在深层网络里到底还有没有生存空间。数据层面不解决光是调阈值只会把问题从一个坑推进另一个坑。5.3 坑二模型过拟合到打光环境换机器就掉点第二个坑是部署到另一台设备上踩的。训练用的数据集是在A环境下拍的模型在本地验证集上表现很好拿到B环境一测mAP直接掉了十多个点。第一反应以为是模型泛化能力不行后来对比了A和B环境拍出来的同一类齿轮图像发现灰度分布差异非常大。A环境偏亮B环境偏暗齿轮表面反光的角度也不一样。这说明模型学到的东西里面有很大一部分不是缺陷本身而是在绑定特定的光照背景。解决办法从两方面下手一是统一现场环境把两边的光源色温、亮度、拍摄高度都校准到同一标准尽量减少视觉差异二是让模型见更多世面把B环境采集的图像加入训练集同时加大亮度扰动和HSV扰动的范围让模型不再依赖某一个固定的灰度分布。这套组合拳打完再迁移到C环境去测虽然精度还是有轻微下降但起码在可接受范围内。关键收获是深度学习模型比你想象的更容易偷懒它会去找数据里最简单稳定的规律——有时候是缺陷纹理有时候只是光源角度。6. 上产线前的三点收尾经验如果只是跑通demo做到前面那些就够了。但要把这套系统真正放到产线上用有几个工程细节必须收尾处理到位。第一把所有参数配置化别把阈值、模型路径、输入尺寸散落在代码里。我最后把所有可调参数统一收进一个config.yaml包括置信度阈值、跳帧数、模型路径、光源参数记录等等。产线调试的时候改配置比改代码方便得多也不会因为改某一行重新训练而出错。第二模型迭代时要做“老样本回放”。工人反馈某个缺陷类型漏检了我补了一批新样本去训练结果新版本模型在老样本上的精度反而掉下来了。这是典型的灾难性遗忘。解决办法是把旧版本的误检、漏检样本按比例混进新训练集让模型同时复习旧知识、学习新知识。每次迭代完都要在固定测试集上回归一遍确认整体精度没有倒退。第三保存坏样本用于追溯和持续优化。系统推理时把置信度处于模糊区间的结果一律保存到本地文件夹定期让人工复查。这些模糊样本就是下一轮迭代最有价值的数据比你自己去现场重新拍图高效得多。模型质检再怎么说也是一个持续迭代的过程上线不是结束收集现场反馈才是下一轮精度的起点。这套系统从数据准备到三个模式跑通前后花了一个多月。回过头看最耗时间的不是训练也不是推理优化而是采集符合产线真实工况的缺陷样本、把标注标准落到每个人手里、再把模型在真实环境里反复调稳。做工业视觉项目的朋友如果也在类似的路子上希望这篇复盘能帮你把弯路绕开一些。