ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

YOLOv5香烟破损检测落地指南:从数据标注到工业部署的完整闭环

YOLOv5香烟破损检测落地指南:从数据标注到工业部署的完整闭环 简介面向目标检测入门与工业质检场景的 YOLOv5 实战项目针对香烟破损缺陷提供完整解决方案可识别头部破损、滤嘴破损等 6 类问题。资源包含可运行的检测代码、3024×4032 大尺度 RGB 数据集与训练好的权重文件经测试可直接使用。项目共 1065 个文件压缩包约 608MB其中 jpeg/jpg 图像与 txt 标签构成训练验证样本py/yaml 文件对应模型训练与配置pt 权重用于推理另含 Dockerfile、脚本等部署辅助内容。训练迭代 100 个 epoch验证集上 map0.5 达 0.90、map0.5:0.95 为 0.55runs 目录保存混淆矩阵、PR 曲线、F1 曲线及验证集全部推理结果便于对照评估。已有 220 人学习下载适合希望快速搭建香烟缺陷检测基线、复现训练指标或基于 YOLOv5 做二次改进的开发者。1. 香烟破损检测选 YOLOv5一份可落地的完整体检单在产线视觉检测里香烟破损的难点不是“能不能检测到一支烟”而是“破损区域往往只有几个像素”。包装膜的强反光、烟盒上的烫金字、输送带抖动都会让背景比缺陷更像缺陷。YOLOv5 恰好在这个场景里有完整闭环项目自带整理好的数据集、可改的源码和已经训练好的权重文件比从目标检测论文起步省掉大量时间。把数据、代码、权重三者按顺序用对就能在两周内从拿到压缩包到产线试跑。这篇笔记写给两类人一类是刚接手视觉质检的工程师想快速搞清楚这类项目怎么消化另一类是已经在跑其他检测框架、想评测 YOLOv5 在破损检测上值不值得上的人。下面按一条真实落地链推进先过数据关再训练再部署最后给出几条保命的排错记录。2. 数据集要过三关目录结构、标签规范与划分逻辑2.1 打开项目先对照 data.yaml别急着解压即训标题里写着包含数据但数据不是解压之后就可以直接开训的。第一步应该检查目录结构并把 data.yaml 里的路径和真实文件对照一遍。我见过不少人把压缩包解压后直接跑 train.py结果跑了两小时才发现图片全没加载进去浪费的不仅是时间还容易让人误判模型问题。YOLOv5 标准数据目录要满足三点图片和标签文件名一致、train/val 子路径真实存在、类别编号从 0 开始连续。我一般先执行这样一段检查脚本把标签里的非法坐标、空标签、类别越界全部扫出来import os from pathlib import Path label_dir Path(datasets/cigarette/labels) for label_file in label_dir.rglob(*.txt): lines label_file.read_text().strip().splitlines() for line in lines: parts line.split() if len(parts) ! 5: print(f非法行: {label_file}: {line}) continue cls, cx, cy, w, h int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) if not (0 cls 1) or not (0 cx 1) or not (0 cy 1) or w 0 or h 0: print(f标签超界: {label_file}: {line})这段脚本只做一件事按 YOLO 格式读每个 txt检查类别编号和归一化坐标是否合法。YOLO 的标签中心点 cx/cy 和宽高 w/h 都必须在 0 到 1 之间一旦出现 w0 或 cx1训练时会被当作无效样本跳过造成“数据量没少有效样本少了一批”的错觉。cls 上限我写成了 1因为这里是单类破损检测如果你后续改成了多分类要改成真实类别数减一。检查完标签后再看 data.yaml 里的 path 和 train/val 字段。YOLOv5 对相对路径很敏感建议统一用绝对路径或把数据集放在 yolov5 主目录下用相对路径。我踩过一次data.yaml 里写的是../cigarette/images但主进程从另一个目录启动所有图片都找不到路径白费了半个下午。比较稳的方式是先 pwd 确认当前目录再决定怎么填路径。2.2 标注破损框矩形框的边界怎么画才不把模型带偏YOLOv5 输出的是水平矩形框所以标注质量直接卡死模型上限。香烟破损常见四种形态破洞、裂纹、皱褶、缺角。如果你的项目标签只有一个类那这四个形态都写 0但框的边界要统一。我一般定两条规则破损区域的主轮廓必须占到框面积的 70%框可以比破损实际边界外扩 2 到 3 像素用来吸收边缘模糊区域。不要只框破损的实心部分不然模型学到的是“烟纸上的高光斑块是破损”而不是“纹理断裂是破损”。特别是透明包装膜上的破洞远看像一个白色区域如果标注时只框白色高光模型很容易把反光也学进去。多分类时建议把“破损”和“疑似破损待复核”分两个类吗我的经验是不要一开始就搞这种复杂设计先单类跑通再按撤换需求扩展。分类多了标注成本跟着涨而 YOLOv5 对类间相似度高的目标容易混淆反而把漏检率拉高。2.3 按批次划分数据防止验证集被“剧透”这条建议可能是整篇里最值得抄的。很多项目在划分 train/val 时直接按文件随机分结果同一支烟在连拍的不同帧里一帧进训练、一帧进验证验证 mAP 虚高到 0.99上线却漏检严重。原因就是验证集“被剧透”了模型在训练时已经见过同一目标的近似外观测试结果自然好看。正确做法是按拍摄批次分。可以用文件名前缀或父目录作为组 id。下面这段脚本按组划分组内文件不会跨界import random import shutil from pathlib import Path src_img_dir Path(cigarette_raw/images) src_lbl_dir Path(cigarette_raw/labels) out_img_dir Path(datasets/cigarette/images) out_lbl_dir Path(datasets/cigarette/labels) random.seed(2024) files list(src_img_dir.glob(*.jpg)) group_map {} for f in files: group_id f.name.split(_)[0] # 文件名前缀代表批次 group_map.setdefault(group_id, []).append(f) group_ids list(group_map.keys()) random.shuffle(group_ids) val_groups set(group_ids[: int(len(group_ids) * 0.2)]) for f in files: is_val f.name.split(_)[0] in val_groups img_path out_img_dir / val / f.name if is_val else out_img_dir / train / f.name lbl_path out_lbl_dir / val / (f.stem .txt) if is_val else out_lbl_dir / train / (f.stem .txt) shutil.copy(f, img_path) shutil.copy(src_lbl_dir / (f.stem .txt), lbl_path)脚本逻辑是先用文件名前缀分组再对组 id 做随机划分最后按组复制。关键参数是 val 比例和随机种子val 取 15% 到 20% 够用种子固定是为了复现。如果你手里是连续视频帧建议按视频片段分组或者每隔 5 帧抽一帧避免相邻帧高度相似。2.4 哪些增强对香烟破损有用从 mosaic 到 HSV 扰动YOLOv5 自带数据增强这部分不需要你额外写太多代码但要在 hyp 配置里做取舍。mosaic 增强把四张图拼成一张对增加上下文有效可当破损区域只有几个像素时再缩放到 640 之下会更难认。常见做法是把 mosaic 从 1.0 降到 0.5并且在训练最后 10 个 epoch 关闭。HSV 扰动里的饱和度上调能让透明膜的蓝色反光更接近真实产线建议保留。如果项目里破损样本少不要迷信“增强能变出样本”。增强只是把同一缺陷的形变稍微放大真正的召回率还是来自样本多样性。最有效的离线增强是水平翻转和随机旋转 90 度因为香烟在输送带上的朝向确实会变。要注意翻转标签要跟着变YOLOv5 的 dataloader 会处理离线增强脚本就要自己处理。3. 训练自己的 YOLOv5 模型环境配置、命令与超参数调法3.1 conda 建环境先对齐 CUDA 再装 PyTorchYOLOv5 环境配置的坑多但九成都在 CUDA 版本和 PyTorch 的匹配上。如果项目包里没有 requirements.txt可以在 YOLOv5 官方仓库根目录找到有就用它装。我常用的安装顺序是conda create -n yolo python3.8 -y conda activate yolo pip install torch2.0.1 torchvision0.15.1 --index-url https://download.pytorch.org/whl/cu118 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt里指定 torch 版本是为了和 CUDA 11.8 配套。如果你的显卡驱动支持 CUDA 12.x也可以装 cu121 对应的轮子但不要直接用pip install torch装到 CPU 版。装完后跑一句python -c import torch; print(torch.cuda.is_available())输出 True 才说明 GPU 真的可用。很多人在 conda yolov5 环境里卡住不是因为代码问题而是环境里混入了别的 torch 版本打印出的 CUDA 版本不匹配。3.2 用 YOLOv5 训练香烟破损模型预训练权重与训练参数的选择环境就绪后先用项目自带的权重文件做一次推理确认代码和权重匹配再开始训练。训练命令我一般这样写python train.py \ --data cigarette.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --device 0 \ --workers 4 \ --name cigarette_s参数含义--img 640是输入分辨率破损检测不建议低于 640如果显存允许直接上 736--batch 16按 8GB 显存估算12GB 可以开到 32--epochs 100不是必须抄先看 50 轮是否收敛再加--device 0指定第一块 GPU--workers 4是数据加载线程数。--weights可以填yolov5s.pt、yolov5m.pt或项目自带的 best.pt。如果目标是快速验证用 s如果现场漏检率卡在 2% 下不去试 m。不要一上来就用 x推理速度和误检率一起涨产线未必扛得住。3.3 超参数调整anchor、mosaic、loss 权重的落地建议YOLOv5 超参数集中在data/hyps/hyp.scratch-low.yaml和hyp.scratch-high.yaml。第一次训练建议先不动等基线跑完再改。常见的改动有三个方向。第一anchor。YOLOv5 会在训练里自动计算 anchor但自动计算的前提是数据集要能代表现场。如果现场镜头离烟盒很近目标尺寸比通用 COCO 大很多就该加--noautoanchor或手工编辑 anchor。我一般让它自动跑一次再打开runs/train/exp/anchors.png看 anchor 和真实框的分布差太多才改。第二mosaic。在 hyp.yaml 里把mosaic: 1.0改成0.5并检查学习率热身参数。破损检测的小目标对背景一致性要求高mosaic 偶尔会把缺陷裁到边缘牺牲一点精度换更稳的召回是值得的。第三类别权重。YOLOv5 默认不做类别加权。如果背景样本远多于破损样本可以用--cls 0.5调分类损失的权重但注意这个值太大会把误检拉高。我一般用--cls 0.5起步观察混淆矩阵再动。3.4 从 results.csv 判断模型状态别被 loss 下降骗了训练结束后不要只看最终的 best.pt要看runs/train/cigarette_s/results.csv。YOLOv5 每个 epoch 都会记录 train loss、val loss、precision、recall、mAP0.5、mAP0.5:0.95。判断模型是否真的能上线我只看三个曲线val loss 在最后 20 个 epoch 是否还在下降如果抖动大说明 lr 或 batch 不合适recall 是否到了 90% 以上破损检测漏检比误检难处理recall 上不去先怀疑样本和标注而不是网络mAP0.5:0.95 和 mAP0.5 差距是否过大如果从 0.95 掉到 0.3说明框的定位精度不足破损框偏大偏小这时优先提高输入分辨率。另外要把 val_batch_pred.jpg 和 confusion_matrix.png 打开看一眼。YOLOv5 会把验证集预测直接画出来眼见为实。确认没有把背景框出来再去部署。4. 用训练好的权重文件部署香烟破损模型CLI、Python API 与视频流4.1 先拿 CLI 验收detect.py 的输出到底显示什么项目自带权重文件第一步是用 CLI 跑通。命令行是最快验证“代码、权重、数据路径”三者是否匹配的方式python detect.py \ --weights runs/train/cigarette_s/weights/best.pt \ --source ./test_images \ --img 640 \ --conf-thres 0.25 \ --iou-thres 0.45 \ --save-txt看到runs/detect/exp/下有标注好的图片说明权重文件能被正确加载。--conf-thres 0.25是置信度阈值--iou-thres 0.45是 NMS 的 IoU 阈值。这个组合只适合试跑上线前要把阈值和你现场的漏检/误检成本对齐详细理由放在第五部分。CLI 验收通过后我不建议在产线直接用 detect.py。它每次启动都要重新加载模型、初始化环境等于把模型的全部推理成本又加了一遍产线机台等不起。4.2 工业集成用 Python API推理与后处理完整代码产线部署的常规做法是把 YOLOv5 封装成 Python 模块在主程序里只加载一次模型然后循环处理送入的帧。下面这段代码是去掉 CLI 外壳后的最小推理实现import cv2 import torch model torch.hub.load(ultralytics/yolov5, custom, pathbest.pt, force_reloadTrue) model.conf 0.25 model.iou 0.45 model.max_det 50 cap cv2.VideoCapture(rtsp://192.168.1.64/cigarette) while True: ret, frame cap.read() if not ret: continue results model(frame, size640) boxes results.xyxy[0].cpu().numpy() for box in boxes: x1, y1, x2, y2, conf, cls box cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 0, 255), 2) cv2.putText(frame, fcrack {conf:.2f}, (int(x1), int(y1) - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imshow(detect, frame) if cv2.waitKey(1) 0xFF ord(q): break这段代码最坑的地方在model.conf和model.iou。torch.hub 加载后这两个参数默认不是 CLI 里的值必须显式赋值。results.xyxy是 NumPy 风格的张量格式是[x1, y1, x2, y2, conf, cls]用results.pandas().xyxy[0]更易读但性能略差批量推理时用xyxy。size640必须和训练时的--img一致否则推理结果可靠度下降。代码里我保留了cv2.imshow实际产线会把显示窗口去掉把boxes交给 PLC 或告警模块。4.3 视频流接入抽帧、缓冲与告警不逐帧死扛产线的视频流一般是 RTSP 编码流不是摄像头直连的裸 RGB。直接cap.read()逐帧跑GPU 推理速度跟不上输入帧率内存会先爆掉。我习惯在视频流和检测模块之间加一个队列from queue import Queue from threading import Thread frame_q Queue(maxsize8) def capture_loop(rtsp_url): cap cv2.VideoCapture(rtsp_url) while True: ret, frame cap.read() if ret and not frame_q.full(): frame_q.put(frame) def detect_loop(): while True: frame frame_q.get() results model(frame, size640) # 抽取置信度最高的破损框触发告警队列长度 8 是缓冲上限超过就丢帧。这样检测线程不会因为一次慢推理把采集线程拖死画面偶尔卡顿但告警不会停。生产线部署时还会把 conf 阈值调到 0.1 到 0.2 之间宁可误报多一些也不要漏检再用后端逻辑二次筛选这一点在避坑章展开说。5. YOLOv5 香烟破损检测的 5 个常见坑现象、原因、对策5.1 训练曲线正常但验证 mAP 虚高问题出在数据划分现象是训练 loss 一路降到 0.02验证 mAP0.5 到 0.99但现场抽样漏检一大片。原因是数据按文件名随机分同一支烟的连续帧被同时分进 train 和 val模型等于在开卷考试。解决方法是先按批次或视频组划分数据集划分后把训练集和验证集的文件名列表做一次交差对比保证一个组 id 只出现在一侧。如果数据集是对同一只烟拍的 500 帧建议每秒抽 1 帧不要全量放入。5.2 权重复现报 KeyErrorYOLOv5 小版本不兼容现象是项目给的 best.pt 在自己的代码里加载时报KeyError: model或RuntimeError: Error(s) in loading state_dict。原因是 YOLOv5 每个小版本的 checkpoint 结构都在演化6.0 和 7.0 的 anchors、stride 保存方式不同混用就会报错。解决方法是先确认项目里用的是哪个版本一般 requirements.txt 或 train.py 头部会注明再把代码切到对应 tag 下比如git checkout v6.0。如果项目没有注明版本就先用python detect.py --weights best.pt探测一次报错就把代码换成权重同代版本。5.3 细长裂纹漏检小目标不是玄学是特征分辨率现象是裂纹宽度只有 2 像素、长度 20 像素模型经常检不出。原因是 YOLOv5 下采样 32 倍后一个 2 像素宽的目标在特征图上不到 0.1 个像素特征基本被抹平。解决有三种手段叠加第一把输入分辨率从 640 提到 960 或 1280代价是推理时间增加第二在模型 yaml 里开启 P2 检测头让浅层特征参与小目标预测第三更便宜的办法是切图推理把大图切成四块每块单独检测后合并。对烟盒这类大小固定的画面切图效果好但要注意切图重叠区会重复检测合并时用一次 NMS 去重。5.4 显存爆炸或推理慢不是模型大是预处理在拖后腿现象是 8GB 显存跑 yolov5s 都报 OOM但同样的模型在同事电脑上 4GB 都跑得动。原因多半是视频流读帧后直接把 1080p 原图送进模型YOLOv5 内部虽然会 resize但预处理过程中仍然保留了原图尺寸的中间 buffer。解决方法是部署前先手动cv2.resize(frame, (640, 640))再用模型推理训练时把 batch 降到 8并确认开了 cache 或关闭 mosaic。如果显存还是不够把模型导出成 half 精度推理时用model.half()显存占用能降一半。5.5 烫金字被误报为破损后处理与负样本补强现象是烟盒上的烫金字和破损区域在灰度上很像模型把印刷噪点当成裂纹。原因是模型学到的是局部灰度突变不一定是纹理断裂。解决不能只调高 conf因为破损样本的 conf 也会被压低。先从数据层面补强在训练集里加入烫金字、镭射标的纯负样本让模型见过这些“假破损”。再从后处理层面做二次过滤统计检测框内边缘的方向连续性用 OpenCV 边缘检测算一下框内边缘是否呈细长连续方向杂乱的直接丢弃。这种方法虽然土但在破损检测这种高误检场景里非常有效。6. 收尾离线回放验证与导出加速的实战习惯模型在测试集上的 mAP 再高也不能直接拍板。我最后的习惯是做一个离线回放验证找三段不同班次的真实产线录像各截取 30 分钟用训练好的权重跑一遍统计每段的检出框总数、漏检帧数和误检帧数。阈值要按回放结果来定如果 30 分钟内误报超过 3 次就调高 conf 0.05 再跑如果漏检每次都出现在同一个机位角度那就该考虑调整相机位置或增加一路检测视角。验证通过后再把模型导出为 ONNX 或 TensorRTpython export.py --weights best.pt --img 640 --include onnx engine --half导出后要在同一批回放数据上对比 PyTorch 和 engine 的推理结果不是只看速度。--img 640必须和训练一致--half开启半精度能让推理更快有些 GPU 上要确认数值差异是否还在容忍范围内。engine 文件在 TensorRT 上通常比 PyTorch 快一倍以上尤其适合几十路视频同时拉流的场景。我习惯把导出的 ONNX 也一起归档这样将来换平台时不用重新训练。这个项目的价值不在于跑通一次而在于你能不能把数据、代码、权重都纳入一个可持续迭代的流程。每条新产线都要跑一次离线回放把当时的误检率和漏检率记到文档里。这是我带项目的土办法希望帮到你。本文还有配套的精品资源点击获取
返回列表