ARTICLE DETAIL

资讯详情

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

易拉罐缺陷检测实战:基于YOLOv8的数据集与训练全流程解析

易拉罐缺陷检测实战:基于YOLOv8的数据集与训练全流程解析 简介面向易拉罐外观质检场景的缺陷识别数据集原始图像集中于罐身划痕、罐底压痕等常见瑕疵配套YOLOv8格式标注可直接用于目标检测模型的训练、验证与评估。包内共1709个文件包含854张JPG原图、854个对应的TXT标注文件以及1个YAML配置文件压缩包约38.27MBTXT中为归一化边界框坐标YAML定义了类别与数据路径符合YOLOv8项目标准目录清晰便于划分训练集与验证集。已有468人学习下载平均正确识别率可达98.9%适合计算机视觉初学者练习数据标注与模型调优也适用于工厂质检项目快速搭建缺陷检测原型。数据预览显示图像来自真实产线拍摄包含多角度、多光照条件下的缺陷样本标注要素完整可帮助读者理解工业场景中易拉罐缺陷的形态差异有效缩短自建数据集的时间成本。1. 易拉罐缺陷识别数据集把YOLOv8直接跑起来的一套标注数据做工业质检的人都有一个共同痛点模型结构不是瓶颈标注数据才是。易拉罐缺陷识别数据集就是为这个场景准备的——它把易拉罐罐体表面的划痕、罐底缺陷等典型瑕疵整理成了可以直接喂给YOLOv8的训练数据标注格式同时覆盖YOLO txt、COCO JSON、Pascal VOC XML三种主流格式意味着你不用在格式转换上耽误时间拿到就能开工。数据集的平均正确识别率能达到98.9%这个数字是发布方在测试集上的统计口径落到你自己的产线场景还需要重新验证但作为起点已经相当够用。适合两类人一类是做食品饮料包装质检的工程师想快速验证YOLOv8在易拉罐缺陷上的效果另一类是刚接触目标检测的初学者用这份真实工业场景数据跑一遍完整训练流程比用公开玩具数据集学到的实战经验多得多。2. 拆开数据集看内部三种标注格式与采集环境的关键信息2.1 三种标注格式共存入口不同但内容一致这份数据集最省事的地方在于同一批图片同时导出了三种格式YOLO txt、COCO JSON、Pascal VOC XML。YOLO格式是训练YOLOv8时直接用的每张图片对应一个同名txt文件每行记录一个目标框class_id x_center y_center width height注意这里的坐标是归一化到0到1之间的相对值不是像素值除以图片宽高得到。COCO JSON适合用mmdetection、Detectron2这类框架Pascal VOC XML适合用SSD、Faster R-CNN等传统检测模型。但实际项目中很少只依赖发布方给的原始文件。我在拿到数据集后一般会先写一个格式转换脚本放在手边因为随着训练推进你大概率会自己补充标注或删掉质量差的图片这时候重新生成COCO或VOC格式比手工改快得多。下面是一个把YOLO txt转换为COCO JSON的最小脚本# yolo_to_coco.py import json, os from glob import glob from PIL import Image # 按你的 data.yaml 里的类别顺序填顺序不能乱 CLASSES [scratch, bottom_defect] def yolo_to_coco(images_dir, labels_dir, output_file): images, annotations [], [] ann_id 1 for img_id, txt_path in enumerate(sorted(glob(os.path.join(labels_dir, *.txt)))): # 跳过没有标注的空文件Roboflow 导出时空标签对应图片通常会被过滤掉 if os.path.getsize(txt_path) 0: continue img_path os.path.join(images_dir, os.path.basename(txt_path).replace(.txt, .jpg)) img Image.open(img_path) w, h img.size images.append({ id: img_id, file_name: os.path.basename(img_path), width: w, height: h }) for line in open(txt_path).read().strip().splitlines(): parts line.strip().split() cls_id int(parts[0]) # YOLO 存的是中心点坐标COCO 需要左上角坐标和宽高要乘回像素值 x_c, y_c, bw, bh map(float, parts[1:]) x (x_c - bw / 2) * w y (y_c - bh / 2) * h bw_px, bh_px bw * w, bh * h annotations.append({ id: ann_id, image_id: img_id, category_id: cls_id 1, # COCO 的 category_id 从 1 开始 bbox: [x, y, bw_px, bh_px], area: bw_px * bh_px, iscrowd: 0 }) ann_id 1 coco { images: images, annotations: annotations, categories: [{id: i 1, name: name} for i, name in enumerate(CLASSES)] } with open(output_file, w) as f: json.dump(coco, f, indent2) if __name__ __main__: yolo_to_coco(images/train, labels/train, coco.json)这段代码核心是坐标换算YOLO格式存归一化的中心点坐标和宽高COCO JSON存像素坐标系下的左上角坐标加宽高所以必须用图片真实尺寸把相对坐标转回像素值。另外COCO的category_id从1开始而YOLO的类别索引从0开始差了一个偏移量。跑完脚本后建议用COCO的官方可视化工具抽查几张确认框的位置没有偏移。2.2 从文件名读采集环境这决定了你的泛化边界看这份数据集的图片文件名能读出不少有用的采集信息。文件名形如WIN_20240410_20_40_37_Pro_jpg.rf.b46e051eab21da1eebf7cbc6ee892f93.jpg其中WIN_和_Pro说明数据来自Windows环境下带Pro模式摄像头的设备20240410_20_40_37是2024年4月10日20点40分37秒的拍摄时间戳.rf.后面的长字符串是Roboflow平台导出时附加的哈希标识。这些信息不是无关紧要的元数据。时间戳跨度从20:39到21:15说明样本采集集中在半小时内意味着拍摄光线条件比较一致。如果你把这份数据训练出来的模型直接丢到白天强光或者夜间灯光完全不同的产线上识别率大概率达不到发布方说的98.9%——不是模型不行而是训练数据覆盖的光照域太窄。我的处理习惯是用这份数据训练出基线模型后再采集不同时段、不同光源角度下的真实产线图片补充进去做二次微调。2.3 类别和样本量检查动手训练前的必做动作YOLOv8不会主动告诉你某个类别样本太少它只会悄悄把这个类别的AP压得很低。所以我在任何数据集上开工之前都先跑一遍类别分布统计这是见过太多翻车案例之后养成的习惯。统计脚本很简单# check_labels.py import os from collections import Counter from glob import glob labels_dir labels/train counts Counter() empty 0 for txt_path in glob(os.path.join(labels_dir, *.txt)): lines [l for l in open(txt_path).read().strip().splitlines() if l] if not lines: empty 1 continue for line in lines: cls int(line.split()[0]) counts[cls] 1 # 顺便检查有没有异常坐标归一化值必须在 0~1 之间超出就是标注导出问题 for txt_path in glob(os.path.join(labels_dir, *.txt)): for line in open(txt_path).read().strip().splitlines(): parts line.split() if len(parts) 0: continue coords list(map(float, parts[1:])) for idx, val in enumerate(coords): if val 0 or val 1: print(f异常坐标: {txt_path} 第{idx}个值{val}) print(类别分布:, dict(counts)) print(空标注文件数:, empty)类别分布一出来问题就很直观了如果划痕类有几百个框罐底缺陷只有几十个框那训练时模型会倾向于学成划痕检测器罐底缺陷基本学不到。对策后面单独讲先记住这个检查步骤不能省。3. 训练自己的YOLOv8模型目录搭建、data.yaml与参数落地3.1 目录结构一次搭对少踩一半坑YOLOv8的Ultralytics框架对数据目录有固定约定目录结构不对训练直接报错或者数据加载量为0而且报错信息不一定直观。很多我见过的新手死在第一步就是root目录指错了。推荐直接用官方约定的结构can_defect/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml创建目录和移动文件用一段bash就能搞定mkdir -p can_defect/{images/{train,val,test},labels/{train,val,test}} cp download/train/*.jpg can_defect/images/train/ cp download/train/*.txt can_defect/labels/train/ # val 和 test 同理注意图片和同名txt必须成对出现在对应目录这里最容易犯的错是图片和标注文件没有严格同名。YOLOv8加载数据时靠的是前缀名匹配a.jpg配a.txt一旦有一个文件名字对不上那张图就直接被跳过而且训练日志里不会给你红色警告只是mAP莫名其妙地低。我自己踩过这个坑之后每次复制完文件都会强制跑一遍ls images/train | wc -l和ls labels/train | wc -l做数量核对。3.2 data.yaml写什么每个字段意味着什么data.yaml是YOLOv8训练时的核心配置文件内容不多但每个字段都有关键作用# can_defect/data.yaml path: /home/user/can_defect # 数据集根目录的绝对路径 train: images/train val: images/val test: images/test # 类别列表顺序必须和标注txt里的class_id一致 names: 0: scratch 1: bottom_defectpath字段建议写绝对路径不要写相对路径。原因很实际Ultralytics会基于这个路径拼接train和val路径如果写相对路径当前工作目录一变数据就加载不到了。names的顺序是训练中的硬约束——你的标注txt里类别索引0对应什么这里就必须写什么顺序错了模型学出来的就是错位的类别映射。如果你不确认自己的类别顺序打开任意一个txt看一眼第一行的第一个数字然后对照数据集发布页的类别说明。3.3 训练参数说明epochs、imgsz、batch怎么选数据准备好了直接开训练。我用Ultralytics的Python API训练因为比命令行更好控制参数和回调# train.py from ultralytics import YOLO model YOLO(yolov8s.pt) # 从COCO预训练权重开始迁移学习 results model.train( datacan_defect/data.yaml, epochs100, imgsz640, batch16, patience20, # 连续20个epoch没提升就早停 optimizerAdamW, # 小数据集上收敛比SGD稳 lr00.001, # 初始学习率微调场景别用默认的0.01 augmentTrue, # 打开内置马赛克等增强 device0, # 指定GPU编号 seed42, # 固定随机种子方便复现 projectruns/can_defect, nameyolov8s_baseline )模型选型上yolov8s是推荐起点比nano精度高比medium训练快工业场景的易拉罐缺陷识别不需要超大模型。epochs100配合patience20是合理的组合如果训练到第40轮val_loss就开始反弹早停会在第60轮附近自动截断不需要人工盯。imgsz640是速度与精度的平衡点如果你的缺陷目标特别小——比如细划痕在整张图里占比不超过5%——可以试试960但显存占用会涨一大截。batch16根据显存来定跑不起来就降到8别硬扛。训练完看runs/can_defect/yolov8s_baseline/目录weights/best.pt是按验证集指标选出的最优权重weights/last.pt是最后一轮的权重。做推理和后续微调只用best.pt。4. 推理与指标验证把98.9%的识别率复现到自己场景4.1 批量推理脚本与置信度设置训练出best.pt之后第一件事是用它跑一遍测试集而不是直接上产线。YOLOv8的predict接口很简单但有几个参数值得细抠# infer.py from ultralytics import YOLO import os model YOLO(runs/can_defect/yolov8s_baseline/weights/best.pt) results model.predict( sourcecan_defect/images/test, conf0.25, # 置信度阈值工业场景建议从0.25开始调 iou0.45, # NMS的IoU阈值目标密集时适当降低 imgsz640, # 推理尺寸要和训练一致 saveTrue, # 保存画了框的结果图 save_txtTrue, # 另存一份预测结果的txt方便后续分析 device0, # 优先用GPU projectruns/infer_test, namebaseline )conf是最需要根据场景调整的参数没有标准答案。置信度设低了漏检少但误检多设高了误检少但可能把模糊的划痕放过去。我的做法是先按0.25跑一遍测试集统计误检和漏检分别是什么再微调阈值做对比。如果产线上后面还有人工复检环节阈值可以放低到0.15追求不遗漏如果是全自动剔除就要调到0.4以上减少误杀。save_txtTrue这个参数很多人忽略但它对分析模型行为非常重要。它会生成格式和训练标注一致的预测结果txt方便你这样对比标注里有的框模型没检出来是漏检标注里没有的框模型检出来了是误检两类样本都可以从这里筛出来做进一步分析。4.2 准确率、召回率和误检指标怎么配合看98.9%这个平均正确识别率在YOLOv8的训练日志里对应的是mAP50也就是IoU阈值0.5下的平均精度。但这个数字要拆开看不能只看单一指标。我在验证模型时至少有三种信息每个类别的AP、混淆矩阵、以及实际误检样本图。YOLOv8训练目录下自动生成的confusion_matrix.png和results.png就是这两个来源。各品类AP的差别是最先暴露问题的。比如划痕的AP是99%罐底缺陷的AP只有85%这说明平衡性有问题深度学习模型的玄学之处就在这——整体mAP可能被大样本类别撑住了小样本类别的真实表现远低于平均线。所以看指标一定要按类别看model.val()输出的表格里每一行对应一个类别的精确率、召回率和mAP50逐个看而不是只看总平均。另外验证集的指标天然比真实场景乐观。原因很简单验证集和训练集来自同一次采集活动光线、角度、缺陷形态高度相似。98.9%在实验室里测出来和在产线上测出来是两个数字中间隔着的就是数据分布的偏移。5. 易拉罐缺陷识别的避坑记录五个最容易翻车的环节5.1 现象训练正常跑完但mAP一直为0这是最打击信心的情况训练loss在降数据加载没问题但验证集mAP全程为0。原因排查后发现多数情况是标注框的坐标范围异常——比如归一化坐标写成像素坐标导致框跑到图片外面去了。另一个常见原因是图片和标注文件不是一一对应验证集里混入了没有对应txt的图片模型在验证时加载不到标签所有预测都被判为误检。解决训练前用上面给的check_labels.py脚本扫一遍所有标注文件检查坐标是否超出0到1范围核对图片和txt文件数量是否一致。这一步花不了两分钟但能省掉半天排查时间。5.2 现象镜面高光被当成划痕易拉罐的金属表面反光非常强罐体曲面在灯光下形成的高光条带视觉上和划痕极其相似。模型把高光误判成划痕属于典型的误检。根本原因是采集时的打光角度不对高光区域暴露了与划痕相似的纹理特征而训练数据里没有足够的高光但无缺陷的负样本。解决先不要急着调模型回到数据层面。方法是在产线上多拍一些干净罐体的高光照片作为负样本加入训练集让模型学会区分高光纹理和真实划痕。如果补充数据不方便退而求其次调高置信度阈值但代价是真正的浅划痕也可能被漏掉所以我一般把数据补充作为首选方案。5.3 现象罐底缺陷类别学不到类别分布检查之后就能预判到这个坑如果划痕样本占了85%罐底缺陷只有15%模型就会倾向把所有能检的都归类为划痕。这是典型的类别不平衡问题。除了去采集更多罐底缺陷样本之外我一般还会配合两个手段一是对罐底缺陷类别做过采样训练时把它对应的图片重复两份甚至三份人为拉平分布二是对罐底缺陷区域做裁剪增强把缺陷区域的局部图切成小块插回训练集相当于把有限的缺陷样本变成多个角度和尺度。5.4 现象显存不够训练直接中断bacth16、imgsz640在8GB显存上跑YOLOv8s经常爆显存。解决路径有几个按开销从小到大排先把batch降到8再不行开梯度累积accumulate2等效于batch16但显存占用减半还不行就换yolov8n模型。注意一点不要为了省显存把imgsz从640降到320这会让小目标缺陷直接消失。在工业场景细节分辨率比模型规模重要得多。有条件的话上16GB以上显存的卡训练yolov8s会从容很多。5.5 现象验证集指标虚高换真实场景就不行这是最隐蔽的坑数据集的图片来自一段连续拍摄的视频抽帧相邻帧之间的背景和缺陷形态高度相似。如果划分训练集和验证集时用随机划分验证集里会出现大量和训练集几乎一样的图片指标虚高是必然的。这不是模型好是验证集泄题了。解决按时间顺序划分数据集比如前70%的时间段做训练后30%做验证。如果数据来自多条视频整条视频为单位划分不要跨视频抽帧混合。划分方法比模型参数更影响你对自己模型的判断这个顺序不能反。6. 再压一截漏检率ROI级联检测与Hard Negative挖掘基线模型跑通之后真正决定产线上好不好用的是你怎么处理漏检和误检的边界情况。这里分享两个我实际项目的做法。第一个是ROI级联检测。不要指望一个模型处理全景图中所有尺度的缺陷。易拉罐质检的物理特征决定了缺陷永远出现在几个固定区域罐体侧面、罐底、罐盖。先训练一个轻量的罐体定位模型把罐体从全景图中框出来然后再让缺陷分类模型只看这个ROI区域分辨率自然就提高了。我用过YOLOv8n做罐体定位推理时间几乎可以忽略但它把缺陷模型的输入图像从整张产线全景变成了单个罐体特写小划痕的像素占比从2%提高到15%漏检率下降明显。具体做法是让定位模型的输出框向外扩10%后裁剪再缩放到640px喂给缺陷模型代码里加一个padding参数就行。第二个方法是Hard Negative挖掘。那些被模型以0.3到0.4置信度误判成缺陷的正常罐体是最有价值的训练样本。把它们收集起来回灌到训练集里重新训练一轮模型对真实缺陷和干扰纹理的区分能力会明显提升。这个循环做一次效果往往比单纯加100张普通样本还好。# collect_hard_negatives.py # 从推理结果里筛出置信度在0.2~0.45之间的误检框弹窗人工确认后存为负样本 from ultralytics import YOLO import cv2, os model YOLO(best.pt) results model.predict(sourceproduction_frames, conf0.2, saveFalse) os.makedirs(hard_negatives, exist_okTrue) for idx, r in enumerate(results): for i, box in enumerate(r.boxes): conf float(box.conf[0]) # 这个区间是模型觉得像但不敢确定的模糊地带 if 0.20 conf 0.45: x1, y1, x2, y2 map(int, box.xyxy[0]) crop r.orig_img[y1:y2, x1:x2] save_path fhard_negatives/frame{idx}_conf{conf:.2f}.jpg cv2.imwrite(save_path, crop) print(f候选hard negative: {save_path})0.2到0.45这个阈值区间是模型犹豫区它的判断最不可信但也最值得人工复核。低于0.2的基本是背景噪点没有学习价值高于0.45的通常是正确检测。跑完这个脚本把crop出来的图快速过一遍确认是误检的归入负样本目录下一次训练时作为背景类样本加入。工业质检项目做到最后拼的都是这种细节。从那以后我每次做类似的数据集项目都强制走一遍完整流程查类别分布、按时间划分数据集、跑基线、筛hard negative、回灌重训一个环节都不跳过。这套流程也希望能帮到你如果你正在用这份数据集训练自己的易拉罐缺陷模型沿着这个路径走可以少走很多我当年走过的弯路。本文还有配套的精品资源点击获取
返回列表