
简介这份资源是面向深度学习入门者、毕业设计或课程设计学生的YOLO异物检测完整项目包聚焦工业制造场景下的实时目标检测与质量控制问题。压缩包共382个文件约52MB以120张jpg与72张png图像数据、112个pt模型权重、26个Python脚本为主辅以yaml配置、csv训练日志、md说明文档及ipynb笔记本覆盖数据、训练、评估到部署的完整链路。已有61人学习下载。项目内含yolo11m、yolov8m、yolo11n等多版本权重与同步训练结果脚本读者可借此复现从数据预处理、模型训练到性能测试的全流程理解YOLO将检测转化为回归问题的实时性优势并掌握GPU加速与版本控制等工程细节适合作为期末大作业或课题参考。1. 异物检测为什么总在产线末端翻车从一条误报说起凌晨两点产线班长打电话说包装线又停了视觉系统把一包正常薯片判成了异物。我赶到现场调出原图发现是包装膜褶皱在环形光源下形成了一条高亮细纹YOLO 模型把它当成了金属丝。这类误报在异物检测里太常见了——不是模型不行是异物检测这个任务本身就和常规目标检测不一样。常规检测里目标是固定类别、固定形态而异物是开放集合头发、虫尸、金属屑、塑料片、纤维形态千变万化样本还极度不均衡。基于 YOLO 的异物检测设计核心要解决的就是三件事怎么定义异物类别、怎么造出足够真实的训练数据、怎么把误报压到产线能接受的水平。这套方案适合食品、药品、电子元器件、纺织等有明确质检工位的场景也适合想从零搭一套工业视觉检测系统的工程师。下面按我实际落地的顺序把选型、数据、训练、部署和调参讲透。2. 选型与数据YOLO 版本怎么挑、异物数据集怎么造2.1 为什么异物检测优先选 YOLOv8 而不是更老的 v5异物检测的部署环境通常是产线工控机或边缘盒子算力有限但节拍要求高。YOLOv5 生态成熟、资料多但它的 anchor 机制在异物这种小目标、极端长宽比场景下需要重新聚类调参成本高。YOLOv8 换成 anchor-free 解耦头小目标召回明显更稳而且官方支持导出 ONNX 和 TensorRT部署链路短。我一般会先跑 YOLOv8n 或 YOLOv8s 做基线mAP 够用就不上大模型因为产线要的是稳定和速度不是刷榜。如果异物尺寸普遍小于 32×32 像素可以在模型里加一个 P2 小目标检测层代价是推理慢 15% 左右但召回能提 5 到 8 个点。至于 YOLOv10、YOLO11 这些新版本NMS-free 确实省后处理时间但工业现场更看重可解释和可复现我目前还是以 v8 为主力新版本先在小批量数据上验证再切。2.2 异物数据集的四类来源和标注边界异物检测最难的不是模型是数据。公开数据集里没有现成的“薯片里的头发”这种类别必须自己造。我通常从四个来源凑第一真实产线不良品图这是最值钱的但数量少第二人工植入异物后拍摄可控但要注意异物和背景的贴合度第三用正常品图做合成把异物素材贴上去做随机旋转、缩放、亮度扰动第四负样本也就是正常品图必须占总量 20% 到 30%否则模型会把正常纹理当异物。标注边界要统一异物被遮挡超过 50% 就不标小于 8×8 像素的不标因为标了模型也学不稳反而拉低精度。类别命名别用“other”这种模糊词按材质分metal、hair、fiber、plastic、insect每类至少 200 个实例再考虑开训。2.3 用脚本把 VOC 标注转成 YOLO 格式并做数据校验拿到手的标注经常是 VOC 的 XMLYOLO 要的是归一化 txt。转换脚本网上一堆但坑在坐标越界和空标注。下面这个脚本我用了三年带校验和日志。import os import xml.etree.ElementTree as ET from pathlib import Path # 类别映射顺序必须和 data.yaml 里的 names 一致 CLASS_MAP {metal: 0, hair: 1, fiber: 2, plastic: 3, insect: 4} def convert_voc_to_yolo(xml_dir, out_dir, img_w1920, img_h1080): xml_dir Path(xml_dir) out_dir Path(out_dir) out_dir.mkdir(parentsTrue, exist_okTrue) skipped [] for xml_file in xml_dir.glob(*.xml): tree ET.parse(xml_file) root tree.getroot() # 优先读 XML 里的真实尺寸读不到才用默认值 size root.find(size) if size is not None: w int(size.find(width).text) h int(size.find(height).text) else: w, h img_w, img_h lines [] for obj in root.iter(object): name obj.find(name).text.strip() if name not in CLASS_MAP: continue bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 越界裁剪这是最常见的脏数据 xmin max(0, min(xmin, w)) xmax max(0, min(xmax, w)) ymin max(0, min(ymin, h)) ymax max(0, min(ymax, h)) bw xmax - xmin bh ymax - ymin if bw 4 or bh 4: continue # 过滤掉退化的框 cx (xmin xmax) / 2.0 / w cy (ymin ymax) / 2.0 / h nw bw / w nh bh / h lines.append(f{CLASS_MAP[name]} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) if not lines: skipped.append(xml_file.name) continue out_file out_dir / (xml_file.stem .txt) out_file.write_text(\n.join(lines), encodingutf-8) print(f跳过空标注文件 {len(skipped)} 个: {skipped[:5]}) if __name__ __main__: convert_voc_to_yolo(./annotations, ./labels)逻辑说明先读 XML 里的真实宽高避免用固定值导致归一化错位对每个框做越界裁剪因为标注员手抖写出 xmax 大于图宽是常事宽高小于 4 像素的框直接丢这种框训练时是噪声。参数上CLASS_MAP 的顺序必须和训练时 data.yaml 的 names 完全一致错一位整个类别就乱了。跑完脚本后用labelImg或labelme抽检 20 张确认框位置没偏移。2.4 data.yaml 的五个字段和路径写法YOLO 训练靠 data.yaml 找数据字段写错是最常见的翻车点。path: /data/foreign_object # 数据集根目录绝对路径最稳 train: images/train # 相对 path 的训练图目录 val: images/val test: images/test nc: 5 # 类别数必须和 names 长度一致 names: [metal, hair, fiber, plastic, insect]path 用绝对路径别用..相对路径否则换机器就找不到。train 和 val 指向的是图片目录YOLO 会自动去找同级的 labels 目录所以图片和标签的目录结构必须镜像images/train/a.jpg对应labels/train/a.txt。nc 和 names 数量不一致会直接报错这个错误信息很明确看到就改。建议 train:val:test 按 7:2:1 切如果总图少于 500 张test 可以先不切全放 train 和 val。3. 训练与调参让异物召回率从 0.6 提到 0.9 的关键设置3.1 从预训练权重起步的最小训练命令别从零训异物数据量通常不够。用 COCO 预训练的 yolov8s.pt 起步收敛快很多。yolo detect train \ data/data/foreign_object/data.yaml \ modelyolov8s.pt \ epochs200 \ imgsz640 \ batch16 \ device0 \ workers8 \ projectruns/foreign \ nameexp_v1 \ patience30 \ pretrainedTrue参数说明imgsz640 是速度和精度的平衡点如果异物普遍很小可以提到 960但显存和耗时都涨。batch16 在 8G 显存上跑 yolov8s 比较稳爆显存就降到 8。patience30 表示 30 轮没提升就早停省时间。workers 设成 CPU 核数的 0.7 倍左右设太大反而因为 IO 争抢变慢。第一次跑先看 loss 曲线如果 cls_loss 一直不降多半是标签格式或类别映射错了。3.2 异物检测必调的四个超参和它们的实际影响默认超参在常规数据集上够用但异物检测有几个参数必须动。第一lr0初始学习率默认 0.01异物数据少时容易过拟合我一般降到 0.005 到 0.008。第二box和cls的损失权重默认 7.5 和 0.5如果误报多把 cls 提到 0.8 到 1.0让分类更严。第三mosaic数据增强概率默认 1.0但异物和背景的上下文关系重要mosaic 拼四张图会破坏这种关系我通常降到 0.5后期再关掉。第四copy_paste这个对稀有类别特别有用把异物实例复制粘贴到其他图上能显著提升小类召回设 0.3 左右。这些值不是拍脑袋是拿验证集 mAP 和误报率一起看的每次只动一个记录结果。3.3 用验证集看混淆矩阵定位误报来源训练完别只看 mAP混淆矩阵才是定位问题的关键。跑yolo detect val时加plotsTrue会生成 confusion_matrix.png。如果正常背景被大量判成某类异物说明该类别的负样本不够或者该类特征和背景纹理太像。我遇到过一次“fiber”和包装膜纹理混淆矩阵里 fiber 那一列背景误判特别高后来补了 300 张纯背景图进训练集误报降了一半。另外看val_batch0_pred.jpg直接肉眼对比预测框和真实框比看数字快。3.4 小目标异物召回低的三种补救手段异物小于 32 像素时默认模型召回经常只有 0.5 到 0.6。第一加 P2 层在模型配置里把检测头往下扩一层代价是推理变慢。第二提高输入分辨率到 960 或 1280但要注意训练和推理分辨率必须一致否则精度掉得厉害。第三切片推理把大图切成 640×640 的小块分别检测再合并适合超大分辨率相机但会引入重复框需要 NMS 调 IoU 阈值。我一般先试提分辨率不行再加 P2切片是最后手段因为工程复杂度高。4. 部署与加速从 PyTorch 到 TensorRT 的落地链路4.1 导出 ONNX 的三个必查项训练完的 .pt 不能直接上产线先导 ONNX。yolo export modelruns/foreign/exp_v1/weights/best.pt \ formatonnx \ imgsz640 \ opset12 \ simplifyTrue \ dynamicFalseopset 用 12兼容性好别追新。simplifyTrue 会做图优化去掉冗余算子。dynamicFalse 表示固定 batch 和尺寸产线通常固定输入固定尺寸能让 TensorRT 优化更充分。导出后用onnxruntime跑一张图对比 PyTorch 输出数值差在 1e-3 以内才算正常。如果差太多多半是 opset 或 simplify 的问题换 opset11 再试。4.2 TensorRT 引擎构建和 FP16 量化的取舍TensorRT 加速在工控机上通常能到 2 到 3 倍。构建引擎时FP16 量化几乎无损INT8 需要校准集精度可能掉 1 到 3 个点。异物检测对召回敏感我一般用 FP16除非速度实在不够再考虑 INT8。构建命令用trtexectrtexec --onnxbest.onnx \ --saveEnginebest_fp16.engine \ --fp16 \ --workspace4096 \ --minShapesimages:1x3x640x640 \ --optShapesimages:1x3x640x640 \ --maxShapesimages:1x3x640x640workspace 单位是 MB4096 够用。min/opt/max 三个 shape 设成一样表示固定 batch这样引擎最优化。如果产线要动态 batch把 max 设大但显存占用和构建时间都会涨。构建完用trtexec --loadEnginebest_fp16.engine --shapesimages:1x3x640x640测一下延迟记下 P50 和 P99产线看的是 P99。4.3 多路视频流下的吞吐估算和瓶颈定位有人问 1080p25 帧用 TensorRT YOLO 640 能支持多少路这没有固定答案取决于 GPU 和预处理。以 T4 为例FP16 下 YOLOv8s 640 单帧推理约 3 到 4 毫秒理论 250 帧每秒但解码和预处理往往才是瓶颈。1080p 解码用 NVDEC 能到几百帧但如果你用 CPU 解码再缩放单路就可能吃掉一个核。实际落地时我按每路 25 帧算T4 上跑 8 到 12 路比较稳留 30% 余量。定位瓶颈用nvidia-smi看 GPU 利用率如果 GPU 没满但帧率上不去就是解码或 CPU 预处理卡住了换 GPU 解码或批处理能解决。5. 避坑与排查异物检测落地时最常踩的五个坑5.1 模型在验证集 mAP 很高上线却疯狂误报现象验证集 mAP 0.9产线误报率却超过 5%。原因验证集和产线数据分布不一致最常见的是光照变化和相机参数不同。解决从产线抓 200 张实际图包括误报图重新标注后加入训练集再跑一轮微调。同时检查训练时的亮度、对比度增强是否和产线匹配不匹配就关掉或调小。5.2 训练 loss 正常但验证 mAP 一直是 0现象训练 loss 在降但验证 mAP 始终为 0。原因九成是标签路径或格式问题。YOLO 找标签是按图片路径替换images为labels再换后缀如果目录结构不对就找不到标签全当背景训。解决检查labels/train下是否有对应 txt打开一个 txt 看是不是类别 cx cy w h五列归一化值。用yolo detect train时加verboseTrue看它加载了多少张图多少标签。5.3 导出 ONNX 后推理结果和 PyTorch 对不上现象ONNX 输出框位置偏移或类别错乱。原因预处理不一致。PyTorch 推理时 YOLO 会自动做 letterbox 和归一化ONNX 只负责网络部分预处理要自己写。解决自己实现 letterbox保持长宽比缩放并填充灰边归一化用img / 255.0通道顺序 RGB。对比时用同一张图先跑 PyTorch 存下输出再跑 ONNX逐元素比。5.4 产线节拍跟不上单帧延迟波动大现象平均延迟 5 毫秒但偶尔跳到 50 毫秒导致丢帧。原因CPU 预处理和 GPU 推理没流水线化或者内存拷贝阻塞。解决用 CUDA 流做异步预处理放 GPU 上做或者用 TensorRT 的execute_async。另外检查是否每帧都重新分配显存改成预分配 buffer 复用。P99 延迟比平均值重要产线按 P99 设计。5.5 异物类别新增后旧类别精度下降现象加了“玻璃屑”类别重新训练原来“金属”的召回掉了。原因类别不平衡新类样本少模型偏向新类。解决新类样本至少补到和其他类同一量级或者用 copy_paste 增强新类。训练时用cls权重动态调整或者分两阶段先冻结主干只训头再全量微调。别一次性加太多新类一次加一个验证稳定再加下一个。6. 把误报压到千分之三一个可复用的阈值调优技巧模型训完只是开始产线能不能用最后卡在阈值上。YOLO 默认置信度阈值 0.25IoU 0.45这套值在异物检测里几乎必然误报超标。我的做法是拿一批产线真实图包含正常品和已知不良品跑一遍推理导出所有框的置信度和是否真阳性然后画 PR 曲线找召回 0.95 对应的置信度作为下限。但光这样不够因为异物检测的误报往往集中在某几类比如 fiber 和背景纹理混淆。这时候做类别级阈值对容易误报的类单独提高阈值对稀有但重要的类降低阈值。下面这段代码就是按类别扫阈值输出每类的最佳工作点。import numpy as np from collections import defaultdict # preds: list of (cls_id, conf, is_tp)is_tp 是人工核对后的真阳性标记 def per_class_threshold(preds, target_recall0.95): by_cls defaultdict(list) for cls_id, conf, is_tp in preds: by_cls[cls_id].append((conf, is_tp)) result {} for cls_id, items in by_cls.items(): items.sort(keylambda x: -x[0]) # 按置信度降序 total_pos sum(1 for _, tp in items if tp) if total_pos 0: continue tp 0 best_conf 1.0 for conf, is_tp in items: if is_tp: tp 1 recall tp / total_pos if recall target_recall: best_conf conf break # 统计该阈值下的误报数 fp sum(1 for c, tp in items if c best_conf and not tp) result[cls_id] {conf: round(best_conf, 3), recall: round(recall, 3), fp: fp} return result # 示例类别 1 是 hair容易误报单独看 preds [(1, 0.91, True), (1, 0.88, True), (1, 0.85, False), (1, 0.80, True), (1, 0.72, False)] print(per_class_threshold(preds, target_recall0.95))逻辑说明按类别分组按置信度从高到低扫累计真阳性到目标召回时记下当前置信度作为该类阈值。参数 target_recall 按产线要求设食品行业通常 0.95 以上电子件可以 0.90。跑完得到每类的 conf写进推理配置。注意这个阈值要在独立于训练集的验证集上扫否则过拟合。我一般还会留 10% 的产线图做最终验收误报率按千分比算超过 3‰ 就回去补负样本或调增强。这套流程跑下来误报从 5% 压到 0.3% 是能做到的但需要反复迭代没有一次到位的后悔药。希望帮到你。本文还有配套的精品资源点击获取