☰
YOLOv11工业质检实战:小目标缺陷检测与实时分类部署
2026/10/5 2:38:53 网站建设 项目流程

简介:这份PDF文档面向工业质检领域的技术开发人员与算法工程师,围绕YOLOv11目标检测算法,系统讲解高精度缺陷检测与实时分类的完整解决方案,适合具备一定深度学习基础、希望将目标检测落地到产线质检场景的读者参考。文档共37页,为单一PDF文件,压缩包约2.04MB,支持目录章节跳转与阅读器左侧大纲快速定位,查阅体验较为顺畅。内容从工业质检背景与挑战切入,依次展开YOLOv11网络结构、训练过程、数据预处理与增强、特征融合与损失函数改进、模型轻量化与硬件加速等关键技术,并给出电子芯片、汽车零部件、纺织品三类缺陷检测案例,覆盖系统集成部署与未来趋势展望。目前已有73人学习,可帮助读者理清从数据标注、模型调优到部署测试的完整链路,掌握实时检测与分类的优化思路。

1. 从一张机油盖照片说起:YOLOv11 工业质检到底在解决什么

产线上一个机油盖从注塑机出来,表面有没有缩水、划痕、缺料,传统做法是靠人眼在强光工位上看。一个人盯 8 小时,前两小时准确率能到 95%,后面掉到 80% 都算好的,而且漏检的件流到总装就是批量返工。YOLOv11 工业质检这套方案要干的事很具体:用目标检测把缺陷框出来,同时给出类别,再让推理速度跟上产线节拍,做到实时分类。它适合两类人——一类是手上已经有缺陷图片、想训一个能落地产线模型的算法工程师;另一类是做设备集成的,需要把检测结果接到 PLC 或分拣机构上。核心矛盾从来不是模型精度不够,而是小目标缺陷 + 实时性 + 误检率这三者互相拉扯。这篇笔记就按我实际做过的路径,把 YOLOv11 从环境配置、数据标注、训练调参到部署推理整条链路拆开讲,重点放在小目标优化和实时分类这两个最容易翻车的地方。

2. YOLOv11 环境配置与最小可跑通验证:别一上来就装全家桶

2.1 为什么选 Ultralytics 这套而不是自己搭

工业质检场景对框架的要求其实很朴素:训练脚本要短、导出格式要多、推理要能塞进 C++ 或 TensorRT。YOLOv11 在 Ultralytics 仓库里维护,model.train()一行就能起训,model.export()能直接出 ONNX、TensorRT Engine、OpenVINO,这对后面接产线工控机太关键了。自己用 PyTorch 从零搭检测头,光是把 NMS 和 letterbox 对齐就要耗掉一周,而且导出时各种算子不支持,血泪经验就是——能站在成熟仓库上就别造轮子。

选型上还有一个现实理由:YOLOv11 的网络结构在 neck 部分做了改进,对小目标的特征融合比前几代更友好,这对轴承缺陷、机油盖划痕这类只占几十个像素的目标很关键。热词里提到的 hcanet、小波缺陷检测,本质都是在特征提取阶段做增强,但如果你连 baseline 都没跑通,加这些模块只会让排查难度翻倍。

2.2 环境配置的具体命令与版本约束

我一般用 conda 隔离环境,Python 锁 3.10,CUDA 用 11.8 或 12.1,这两个版本和主流 PyTorch 轮子兼容性最好。

# 创建独立环境,避免和系统里的 torch 打架 conda create -n yolo11 python=3.10 -y conda activate yolo11 # 装 PyTorch,CUDA 11.8 对应这个 index pip install torch==2.1.0 torchvision==0.16.0 --index-url https://download.pytorch.org/whl/cu118 # 装 ultralytics,不要指定太老的版本 pip install ultralytics # 验证 GPU 是否真的可用 python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))"

逻辑说明:先隔离环境是因为工业项目经常要在一台机器上跑多个版本模型,混装必炸。PyTorch 版本和 CUDA 版本必须对齐,cu118这个 index 决定了你装的是 GPU 版还是 CPU 版,装错了torch.cuda.is_available()会返回 False,训练时默默跑在 CPU 上,一个 epoch 跑两小时你还以为是数据量大。

参数说明:torch==2.1.0是我验证过和 ultralytics 兼容稳定的版本,再新可能遇到算子导出问题。如果你显卡是 40 系,CUDA 12.1 也可以,把 index 换成cu121。

2.3 用官方权重跑一次推理,确认链路通

在碰自己的数据之前,先用预训练权重跑一张图,确认从加载模型到出结果整条链路没问题。

from ultralytics import YOLO # 加载预训练权重,第一次会自动下载 model = YOLO("yolo11n.pt") # 对单张图推理,save=True 会把结果存到 runs/detect/predict results = model.predict( source="test.jpg", conf=0.25, # 置信度阈值,工业场景一般先设低一点看召回 iou=0.45, # NMS 的 IoU 阈值 save=True, device=0 # 用第 0 号 GPU ) # 打印每个框的类别和坐标 for box in results[0].boxes: print(box.cls, box.conf, box.xyxy)

逻辑说明:这一步的价值是排除环境问题。如果这里报错,八成是 CUDA 或 torchvision 没装对,而不是你的数据有问题。save=True会把带框的图存下来,肉眼确认框的位置对不对。

参数说明:conf=0.25是通用起点,工业质检里如果漏检代价高,可以降到 0.1 先看召回;iou=0.45控制重叠框合并,缺陷密集时调低能保留更多框。device=0指定 GPU,多卡用device=[0,1]。

3. 数据标注与格式转换:缺陷检测的成败八成在这里

3.1 工业缺陷数据的三个特殊性

工业质检的数据和 COCO 那种自然图像完全不是一回事。第一,缺陷样本极度不平衡,一批 1000 个件可能只有 20 个有缺陷,正负样本比能到 1:50。第二,缺陷尺度极小,轴承上的点蚀可能就 10×10 像素,机油盖的划痕是细长条。第三,背景高度一致,同一个工位拍的图背景几乎不变,模型很容易学到「背景即正常」的捷径,换个光照就崩。

所以标注策略上,我一般要求标注员对每个缺陷框紧贴边缘,不要留太多余量,因为小目标本身像素就少,框大了特征就被背景稀释。类别定义要克制,能合并的缺陷类型就合并,类别太多会让每个类的样本更少。

3.2 从标注文件到 YOLO 格式的转换脚本

YOLO 要的是每张图一个 txt,每行类别 中心x 中心y 宽 高,全部归一化到 0-1。如果你用 LabelImg 标的是 VOC 的 xml,需要转。

import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_dir, out_dir, classes): # classes 是类别名列表,顺序决定类别 id os.makedirs(out_dir, exist_ok=True) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(xml_dir, xml_file)) root = tree.getroot() size = root.find("size") w = int(size.find("width").text) h = int(size.find("height").text) lines = [] for obj in root.iter("object"): cls_name = obj.find("name").text if cls_name not in classes: continue # 跳过未定义类别,避免训练时报错 cls_id = classes.index(cls_name) bbox = obj.find("bndbox") x1 = float(bbox.find("xmin").text) y1 = float(bbox.find("ymin").text) x2 = float(bbox.find("xmax").text) y2 = float(bbox.find("ymax").text) # 归一化并转成中心点+宽高 cx = (x1 + x2) / 2.0 / w cy = (y1 + y2) / 2.0 / h bw = (x2 - x1) / w bh = (y2 - y1) / h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") out_path = os.path.join(out_dir, xml_file.replace(".xml", ".txt")) with open(out_path, "w") as f: f.write("\n".join(lines)) voc_to_yolo("annotations", "labels", ["scratch", "dent", "missing"])

逻辑说明:归一化是关键,YOLO 不认绝对像素坐标。classes.index把类别名映射成 id,顺序必须和后面 data.yaml 里一致,否则训练出来的模型类别全错。跳过未定义类别是防止标注员手滑写了个新名字导致训练中断。

参数说明:cx, cy是框中心点,bw, bh是宽高,都除以原图宽高做归一化。保留 6 位小数是为了小目标精度,小框归一化后数值很小,位数不够会丢精度。

3.3 data.yaml 的写法与目录结构

# data.yaml path: /data/quality_check # 数据集根目录 train: images/train # 训练集图片相对路径 val: images/val # 验证集图片相对路径 nc: 3 # 类别数 names: # 类别名,顺序必须和转换脚本一致 0: scratch 1: dent 2: missing

目录结构要严格对齐:images/train放图,labels/train放同名 txt,YOLO 会自动去找。很多人翻车是因为图片叫a.jpg,标签叫a.txt但放在了别的目录,训练时提示找不到标签,模型把所有图当负样本训,最后啥也检测不出来。

提示:划分训练验证集时,如果同一批次的图背景几乎一样,要按批次划分而不是随机划分,否则验证集里全是训练集见过的背景,指标虚高。

4. 训练调参与小目标优化:让模型真正看见小缺陷

4.1 起训命令与关键参数

yolo detect train \ model=yolo11s.pt \ data=data.yaml \ epochs=200 \ imgsz=640 \ batch=16 \ lr0=0.01 \ lrf=0.01 \ patience=50 \ augment=True \ cache=True \ device=0

逻辑说明:model=yolo11s.pt选 s 而不是 n,是因为工业缺陷特征弱,n 版容量太小容易欠拟合;但如果你要部署到边缘设备,n 版更合适,这是个精度和速度的权衡。patience=50是早停,50 个 epoch 验证指标不涨就停,省时间。

参数说明:imgsz=640是默认输入尺寸,小目标多的话可以提到 1280,但显存和速度代价大;batch=16按显存调,爆显存就减半;lr0=0.01初始学习率,lrf=0.01是最终学习率系数,cos 衰减到初始的 1%。cache=True把图缓存到内存,数据量不大时能显著加速。

4.2 小目标优化的四个实操手段

热词里 yolov11 小目标优化是高频问题,我实际用下来有效的就这几招。

第一,提高输入分辨率。640 提到 1024 或 1280,小目标在特征图上的像素变多,检测率能明显涨。代价是推理变慢,要算清楚产线节拍能不能接受。

第二,加 P2 检测层。YOLOv11 默认在 P3/P4/P5 三个尺度检测,P2 是更高分辨率的浅层特征,专门抓小目标。改法是修改模型 yaml,在 head 部分加一个 P2 分支。这个改动会让参数量和计算量上升,但小目标召回提升明显。

第三,数据增强针对性调。默认的 mosaic 对小目标有帮助,但copy_paste和mixup在缺陷检测里要慎用,因为可能把缺陷贴到不合理的位置,模型学到错误上下文。我一般把mosaic保留,scale范围调窄,避免小目标被缩得更小。

第四,anchor 或损失权重调整。YOLOv11 是 anchor-free 的,但可以在损失里给小目标更高权重。这个改动比较深,建议先把前三招用满再考虑。

4.3 训练过程怎么判断有没有跑偏

看runs/detect/train下的results.png,重点看三条线:box_loss应该稳定下降,如果震荡剧烈说明学习率太大;mAP50和mAP50-95的差距,如果 50 高但 50-95 低,说明框的位置不够准,小目标定位差;cls_loss如果一直不降,可能是类别不平衡太严重。

还有一个黑匣子式的现象:训练集指标很好,验证集一塌糊涂,这基本是过拟合或者数据泄漏。工业场景里最常见的是同一张图的不同增强版本同时进了训练和验证集,指标虚高,上线就崩。解决办法是按件号或批次划分数据集,别按图片随机分。

5. 推理部署与实时分类:从模型到产线节拍

5.1 导出成部署格式

from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") # 导出 ONNX,通用性最好 model.export(format="onnx", imgsz=640, simplify=True, opset=12) # 如果有 TensorRT 环境,导出 engine 速度最快 model.export(format="engine", imgsz=640, half=True, device=0)

逻辑说明:ONNX 适合跨平台,工控机上用 ONNX Runtime 就能跑;TensorRT Engine 是 NVIDIA 显卡上的极致速度,但和显卡型号、CUDA 版本绑定,换机器要重新导。simplify=True会做图优化,去掉冗余算子。half=True用 FP16,速度翻倍精度掉一点点,工业场景一般能接受。

参数说明:opset=12兼容性好,太新有些推理引擎不支持。imgsz必须和训练时一致,否则精度会掉。

5.2 实时分类的两种接法

标题里的「实时分类」有两层意思。一层是检测框出来之后,根据框的类别做分拣决策,这个直接在推理结果里读box.cls就行。另一层是整图分类,判断这个件是 OK 还是 NG,这个可以用检测结果聚合——只要检出任何缺陷框就判 NG。

import cv2 from ultralytics import YOLO model = YOLO("best.engine") # 用 TensorRT 引擎 cap = cv2.VideoCapture(0) # 接工业相机 while True: ret, frame = cap.read() if not ret: break results = model.predict(frame, conf=0.3, verbose=False) boxes = results[0].boxes # 聚合决策:有框就是 NG status = "NG" if len(boxes) > 0 else "OK" # 这里可以把 status 通过串口或 PLC 发出去 print(status, len(boxes)) # 保存推理结果图,方便追溯 annotated = results[0].plot() cv2.imwrite(f"log/{int(cap.get(1))}.jpg", annotated)

逻辑说明:verbose=False关掉每帧打印,否则日志刷屏拖慢速度。results[0].plot()把框画回原图,存下来做质量追溯,热词里 yolov11 保存推理结果说的就是这个。聚合决策是最简单的实时分类逻辑,实际产线可能还要加连续帧确认,避免单帧误检触发停机。

参数说明:conf=0.3比训练时略高,是为了压误检,产线上误检停机比漏检还烦。相机帧率要和推理速度匹配,如果推理一帧要 50ms,相机就别开 60fps,会积压。

5.3 速度与精度的平衡表

配置输入尺寸精度 mAP50单帧耗时适用场景
yolo11n + ONNX640基准快边缘设备、节拍快
yolo11s + TensorRT FP16640略高很快主流工控机
yolo11s + TensorRT FP161024高中等小目标多、节拍宽松
yolo11m + TensorRT1280最高慢离线复检

这张表是我实际测出来的相对关系,具体数字因硬件而异。选型逻辑是先用 s + 640 跑通,看漏检率,不够就提分辨率或换大模型,直到满足产线要求为止。

6. 避坑与排查:那些让模型上线就崩的细节

6.1 训练 loss 正常但验证 mAP 为 0

现象:训练日志里 box_loss 在降,但验证集 mAP 一直是 0。原因九成是标签路径或格式不对,YOLO 没读到标签,把所有框当背景训。解决:检查labels/val目录是否存在且和图片同名,打开一个 txt 确认是类别 cx cy w h五列且归一化。用yolo detect train时加--verbose看它报的标签数量。

6.2 小目标全部漏检

现象:大缺陷能检出,小缺陷一个都框不出来。原因通常是输入分辨率不够,小目标在 P3 特征图上只剩一两个像素。解决:先把imgsz提到 1024 试,如果还不行就加 P2 检测层。另外检查标注框是不是画得太大,把背景也框进去了,导致特征被稀释。

6.3 换一批产品就误检爆炸

现象:训练集上好好的,换了个批次的件,背景稍微变了一点,误检率飙升。原因是模型学到了背景捷径,没学到缺陷本质。解决:训练时加更强的颜色抖动和亮度扰动,数据里混入不同批次的正常件做负样本,让模型知道背景变化不是缺陷。

6.4 导出 ONNX 后精度掉一大截

现象:PyTorch 里推理正常,导出 ONNX 后框的位置偏了或类别错了。原因多半是预处理没对齐,YOLO 的 letterbox 填充方式在导出后可能和训练时不一致。解决:导出时加simplify=True,推理时用 ultralytics 自己的predict接口而不是自己写预处理,或者严格复现 letterbox 的缩放和填充逻辑。

6.5 推理速度达不到产线节拍

现象:单帧耗时 80ms,产线要求 30ms。原因可能是没用 TensorRT、没开 FP16、或者后处理 NMS 在 CPU 上跑。解决:导出 engine 并开half=True,把 NMS 放到 GPU 上,或者降低输入分辨率。如果还不行,考虑模型剪枝或换 n 版。

7. 一个提精度的小技巧:用检测结果反哺难例挖掘

模型训完第一版之后,别急着上线。拿它去跑一批没标注的产线图,把置信度在 0.1 到 0.4 之间的框导出来,这些就是模型「犹豫」的难例。人工复核这批图,把漏标的补上、误检的标成负样本,再混进训练集重新训一轮。这个循环做两到三轮,mAP 通常能再涨几个点,比盲目加数据有效得多。

from ultralytics import YOLO model = YOLO("best.pt") results = model.predict("unlabeled_images", conf=0.1, save=False) # 收集低置信度样本的文件名 hard_cases = [] for r in results: for box in r.boxes: if 0.1 < box.conf < 0.4: hard_cases.append(r.path) break print(f"待复核难例数量: {len(hard_cases)}")

逻辑说明:conf=0.1放低阈值是为了把模型不确定的框都捞出来,0.4是上限,高于这个的模型已经很确信,不用复核。收集到的文件名交给标注员重点看,这批数据的信息量远大于随机抽的图。

参数说明:阈值区间可以按实际情况调,模型越成熟,这个区间可以收得越窄。复核完记得把难例按比例混入训练集,别全塞进去导致分布偏移。

我自己踩过最深的坑是急着上线,第一版模型没做难例挖掘就直接部署,结果产线上一种罕见的斜向划痕一直漏检,返工了一批货才发现。后来养成习惯,任何模型上线前必须跑一轮难例挖掘,哪怕多花两天。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询