简介:面向易拉罐外观质检场景的缺陷识别数据集,原始图像集中于罐身划痕、罐底压痕等常见瑕疵,配套YOLOv8格式标注,可直接用于目标检测模型的训练、验证与评估。包内共1709个文件,包含854张JPG原图、854个对应的TXT标注文件以及1个YAML配置文件,压缩包约38.27MB;TXT中为归一化边界框坐标,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, indent=2) 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( data="can_defect/data.yaml", epochs=100, imgsz=640, batch=16, patience=20, # 连续20个epoch没提升就早停 optimizer="AdamW", # 小数据集上收敛比SGD稳 lr0=0.001, # 初始学习率,微调场景别用默认的0.01 augment=True, # 打开内置马赛克等增强 device=0, # 指定GPU编号 seed=42, # 固定随机种子,方便复现 project="runs/can_defect", name="yolov8s_baseline" )模型选型上,yolov8s是推荐起点:比nano精度高,比medium训练快,工业场景的易拉罐缺陷识别不需要超大模型。epochs=100配合patience=20是合理的组合,如果训练到第40轮val_loss就开始反弹,早停会在第60轮附近自动截断,不需要人工盯。imgsz=640是速度与精度的平衡点,如果你的缺陷目标特别小——比如细划痕在整张图里占比不超过5%——可以试试960,但显存占用会涨一大截。batch=16根据显存来定,跑不起来就降到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( source="can_defect/images/test", conf=0.25, # 置信度阈值,工业场景建议从0.25开始调 iou=0.45, # NMS的IoU阈值,目标密集时适当降低 imgsz=640, # 推理尺寸要和训练一致 save=True, # 保存画了框的结果图 save_txt=True, # 另存一份预测结果的txt,方便后续分析 device=0, # 优先用GPU project="runs/infer_test", name="baseline" )conf是最需要根据场景调整的参数,没有标准答案。置信度设低了,漏检少但误检多;设高了,误检少但可能把模糊的划痕放过去。我的做法是先按0.25跑一遍测试集,统计误检和漏检分别是什么,再微调阈值做对比。如果产线上后面还有人工复检环节,阈值可以放低到0.15追求不遗漏;如果是全自动剔除,就要调到0.4以上减少误杀。
save_txt=True这个参数很多人忽略,但它对分析模型行为非常重要。它会生成格式和训练标注一致的预测结果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 现象:显存不够,训练直接中断
bacth=16、imgsz=640在8GB显存上跑YOLOv8s经常爆显存。解决路径有几个,按开销从小到大排:先把batch降到8;再不行开梯度累积accumulate=2,等效于batch=16但显存占用减半;还不行就换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(source="production_frames", conf=0.2, save=False) os.makedirs("hard_negatives", exist_ok=True) 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 = f"hard_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、回灌重训,一个环节都不跳过。这套流程也希望能帮到你,如果你正在用这份数据集训练自己的易拉罐缺陷模型,沿着这个路径走,可以少走很多我当年走过的弯路。
本文还有配套的精品资源,点击获取