ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于YOLO11cls的曲奇饼干缺陷分类:小样本实战与踩坑指南

2026/9/20 20:43:14 拓冰建站 浏览量
基于YOLO11cls的曲奇饼干缺陷分类:小样本实战与踩坑指南 简介面向食品工业质检与图像分类算法实践者这份资源围绕曲奇饼干缺陷检测场景提供真实产线采集的高质量饼干图片共一千张涵盖 Defect_Color、Defect_No、Defect_Object、Defect_Shape 四个缺陷类别已按类别文件夹整理完毕可直接用于 YOLO 分类模型训练。资源以 PDF 文件交付共一个文件约 5.87MBPDF 内详细说明了数据集构成、类别分布与目录结构并附有百度网盘下载方式方便获取完整图像数据。同时资源额外提供了 YOLO11cls 一键训练脚本和博主训练结果日志可帮助初学者快速复现训练流程、对比效果。目前已有二十八人学习适合正在做食品缺陷检测、需要现成分类数据集或参考训练代码的读者。整体体量轻整合了数据说明与训练脚本适合快速启动曲奇饼干质检项目。 曲奇饼干缺陷检测听起来是个小项目真做起来才发现坑一点也不少。尤其是我们手头只有1000张图的团队既想快速验证AI视觉落地的可能性又不想在数据标注上投入太多人力最后敲定的方案就是目标分类图像分类路线配合YOLO11cls做一版一键训练脚本。这篇文章就聊聊这个数据集是怎么整理出来的训练脚本怎么设计以及在实测中踩过的那些坑。如果你也在做食品、工业品表面缺陷分类只有几百到一千张图想用YOLO系列快速跑通一个可用的模型那这篇文章应该能帮你省下不少时间。我会尽量把从原始图片清洗、文件夹整理到训练脚本参数再到常见问题的排查方法都讲清楚。1. 项目背景与数据集定位1.1 为什么选择图像分类而不是目标检测接到这个需求时我们第一反应确实是目标检测——毕竟“缺陷检测”四个字听起来就应该把缺陷框出来。但冷静之后算了一笔账曲奇饼干产线上的常见缺陷比如焦边、裂纹、缺角大部分情况下其实不需要边界框。产线只需要知道“这一块饼干是不是合格的”“不合格属于哪类”并不需要告诉机械臂缺陷的精确坐标。图像分类Image Classification天然适合这种场景。YOLO11cls是Ultralytics在YOLO11里加入的分类分支可以直接拿预训练模型迁移学习不用画框也不用生成目标检测那种XML或TXT标注文件。对于1000张图的小型项目目标检测的标注工作量会非常疼而分类只需要按文件夹放好图片YOLO11cls训练时能自动从目录名读取类别标签省掉了大量前置工作。还有一个原因是部署速度。分类模型推理只输出一个类别概率数组可以很轻量地嵌入单片机或边缘盒子也不需要依赖复杂的NMS后处理。对于曲奇饼干这类高速产线来说快和稳定比“框得准”更重要。所以最终我们放弃了检测方案专攻分类。1.2 1000张图与对应分类文件夹设计数据集原始素材来自两个渠道一是产线相机拍摄的流水线照片二是手机/相机补拍的高清样本。原始图片统一筛选并清洗后最终保留了1000张有效图。这1000张不是简单堆在一个文件夹里而是对应分类文件夹整理好的标准结构datasets/cookie_cls/ ├── train/ │ ├── normal/ # 正常曲奇 │ ├── burnt/ # 焦边/烤色过深 │ ├── cracked/ # 裂纹 │ └── chipped/ # 缺角/边缘缺损 ├── val/ │ ├── normal/ │ ├── burnt/ │ ├── cracked/ │ └── chipped/ └── test/ ├── normal/ ├── burnt/ ├── cracked/ └── chipped/训练、验证、测试按8:1:1划分也就是训练集800张验证集和测试集各100张。类别上我们没有做单纯二分类“良品/缺陷”而是细分为4类因为生产现场更关心缺陷类型方便追溯是烘烤温度问题还是模具撞击问题。这里有个关键点YOLO11cls训练时并不需要单独的label文件它靠的是这个目录结构来推断标签。所以“对应分类文件夹”这个整理动作就是标注本身。目录名不要带空格和中文统一用小写英文每个类别目录下只放该类的图片不要混放不确定的样本否则模型会被带偏。2. 数据集整理与预处理2.1 原始图片清洗与命名规范拿到手的原始图里有不少问题最典型的是模糊、重复、强反光和拍摄角度过于刁钻。这些图如果直接丢进训练集会让模型学到错误的特征。我一般会先做一个半自动清洗流程先用Python脚本把所有图片统一重命名并去重再逐张快速浏览筛掉明显不能用的图。清洗脚本的核心逻辑很简单先读图做MD5去重再按类别重命名import hashlib import os import shutil from PIL import Image def md5_file(path): h hashlib.md5() with open(path, rb) as f: while chunk : f.read(8192): h.update(chunk) return h.hexdigest() def clean_and_rename(src_root, dst_root, class_name): seen {} os.makedirs(dst_root, exist_okTrue) for idx, filename in enumerate(os.listdir(src_root)): if not filename.lower().endswith((.jpg, .jpeg, .png)): continue src os.path.join(src_root, filename) digest md5_file(src) if digest in seen: continue # 重复图片直接跳过 seen[digest] src ext os.path.splitext(filename)[1].lower() new_name f{class_name}_{idx:04d}{ext} shutil.copy(src, os.path.join(dst_root, new_name))命名格式统一为“类别_序号.jpg”比如cracked_0012.jpg。这样做的好处是后面训练完只要看到文件名就知道它的原始类别排查误判样本时特别方便。清洗时还要注意图片尺寸差异有些手机原图是4000x3000有些产线图只有640x480训练前需要统一尺寸。我没有用简单拉伸而是采用“resize加灰边”的方案在保持宽高比的同时补到正方形避免饼干形状被拉伸变形。2.2 分类体系与缺陷类型定义分类体系直接决定项目的上限。我们一开始只分“正常”和“缺陷”结果发现模型能判断有没有问题却说不出什么问题产线那边根本没法用。后来改成4类normal颜色均匀、轮廓完整、表面无异常纹理。burnt整体或局部颜色过深尤其是边缘焦黑。cracked表面有不规则裂纹有时裂纹很细微。chipped边缘掉块或轮廓缺损形状不完整。从图像特征看burnt主要靠颜色区分cracked主要靠纹理chipped主要靠轮廓形状。这三类特征差异比较大但实际图像中经常出现“又焦又有裂纹”的复合缺陷。这时候我建议统一归到更严重的那一类或者在整理数据时单独建一个“burnt_cracked”类。但1000张图本来就少再拆细每类样本会严重不足所以我的经验是宁可让模型在一个复合样本上学到模糊边界也不要硬塞给两个类造成标签冲突。类别目录整理好后我习惯再用脚本统计一下每类图片数量打印出分布from pathlib import Path for cls_dir in Path(datasets/cookie_cls/train).iterdir(): if cls_dir.is_dir(): count len(list(cls_dir.glob(*.jpg))) len(list(cls_dir.glob(*.png))) print(f{cls_dir.name}: {count})这一步能提前发现数据缺失或类别目录为空的问题。YOLO11cls训练时如果某个类别只有几张图loss会非常难看模型对这类缺陷基本学不会。2.3 数据增强与样本均衡1000张图对深度学习模型来说偏少尤其还是4分类差不多每类平均只有200张训练图。靠数据增强来补是必须的但我推荐“在线增强”不要提前把增强后的图片全部落盘。原因是离线扩充会导致大量重复样本进入训练模型很容易记住同一张图的不同裁剪验证集也容易失真。YOLO11cls训练时本身内置了一些增强策略比如随机翻转、色彩抖动、Mosaic等。对于曲奇饼干这种表面纹理不均匀的食品我还会额外写一个torchvision的增强变换作为“心理安慰”也好但实测下来确实有效。一般会用随机旋转、亮度对比度调整、轻微随机裁剪和添加高斯噪声from torchvision import transforms train_transform transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomRotation(30), transforms.RandomResizedCrop(size(224, 224), scale(0.8, 1.0)), transforms.ColorJitter(brightness0.3, contrast0.3, saturation0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])旋转角度不要太大曲奇虽然有圆形但也有方形产品超过45度可能引入不符合产线实际的姿态。椒盐噪声和高斯噪声我适量加了一点点目的是让模型对高低分辨率摄像头都更鲁棒。类别均衡方面如果某类样本特别少可以通过设置训练脚本里的class_weight或者简单粗暴地“对少数类重复采样”。但我更推荐优先采集真实样本数据增强只能缓解不能根治。1000张图里正常情况下正常类别可能拍得最多缺陷类别往往难以收集所以前期的拍摄和筛选要有意识地向缺陷样本倾斜。3. YOLO11cls一键训练脚本实战3.1 安装与环境验证Ultralytics的YOLO11cls基于PyTorch环境准备不算复杂。我用的是Python 3.10 CUDA 11.8安装命令pip install ultralytics torch torchvision --index-url https://download.pytorch.org/whl/cu118如果只是快速验证用CPU也能跑但1000张图训练100个epochCPU可能要跑几个小时。建议还是用带NVIDIA GPU的机器。装好后先验证CUDA是否可用python -c import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))输出True NVIDIA GeForce RTX 3060这样的信息就说明环境没问题。接着从Ultralytics官方权重里下载YOLO11分类预训练模型比如yolo11n-cls.ptnano版本或yolo11s-cls.ptsmall版本。我们最终选了nano预训练权重因为产品部署目标是边缘设备nano类别数虽然少但通过迁移学习足够应对曲奇这种比较简单的图像分类任务。3.2 一键训练脚本设计与参数说明所谓“一键训练脚本”就是把数据划分、数据集格式检查和模型训练封装成一个Python脚本让团队里不熟悉深度学习的同事也能直接跑。脚本里我加了一个split_data函数自动把原始类别文件夹按比例划分成train/val/test三份避免手工拷贝出错。另一个函数会检查每个目录下图片数量是否为0防止YOLO加载时类别数对不上。下面是精简后的完整脚本基本可以直接用在类似项目上import argparse import os import random import shutil from pathlib import Path def split_data(data_dir, output_dir, val_ratio0.1, test_ratio0.1): classes [d for d in os.listdir(data_dir) if os.path.isdir(os.path.join(data_dir, d))] for split in [train, val, test]: for cls in classes: os.makedirs(os.path.join(output_dir, split, cls), exist_okTrue) random.seed(42) for cls in classes: cls_path os.path.join(data_dir, cls) images [f for f in os.listdir(cls_path) if f.lower().endswith((.jpg, .jpeg, .png))] random.shuffle(images) val_n int(len(images) * val_ratio) test_n int(len(images) * test_ratio) partitions { train: images[val_n test_n:], val: images[:val_n], test: images[val_n:val_n test_n] } for split, split_images in partitions.items(): for img in split_images: src os.path.join(cls_path, img) dst os.path.join(output_dir, split, cls, img) shutil.copy(src, dst) print(数据划分完成输出目录:, output_dir) def check_dataset(data_dir): train_dir os.path.join(data_dir, train) classes [d for d in os.listdir(train_dir) if os.path.isdir(os.path.join(train_dir, d))] if len(classes) 2: raise ValueError(训练集至少需要两个类别文件夹) for cls in classes: for split in [train, val, test]: split_cls os.path.join(data_dir, split, cls) count len([f for f in os.listdir(split_cls) if f.lower().endswith((.jpg, .jpeg, .png))]) print(f{split}/{cls}: {count}) if split train and count 0: raise ValueError(f训练集类别 {cls} 为空) return classes if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--data, typestr, defaultdatasets/cookie_cls_raw) parser.add_argument(--output, typestr, defaultdatasets/cookie_cls) parser.add_argument(--model, typestr, defaultyolo11n-cls.pt) parser.add_argument(--epochs, typeint, default100) parser.add_argument(--batch, typeint, default32) parser.add_argument(--imgsz, typeint, default224) parser.add_argument(--device, typeint, default0) parser.add_argument(--train, actionstore_true, help是否执行数据划分并开始训练) args parser.parse_args() if args.train: split_data(args.data, args.output) classes check_dataset(args.output) print(类别列表:, classes) from ultralytics import YOLO model YOLO(args.model) model.train( dataargs.output, epochsargs.epochs, batchargs.batch, imgszargs.imgsz, deviceargs.device, patience20, ampTrue, projectruns/classify, namecookie_cls )执行命令很简单python train_cls.py --train --data datasets/cookie_cls_raw --epochs 100 --batch 32 --imgsz 224关于参数有几点想特别提醒。data参数指向的是包含train/val/test子目录的根目录不是具体的文件夹YOLO11cls会自动找。imgsz不要一开始就拉满先用224跑通再往上调。patience是早停参数设20表示20个epoch内验证集top1准确率不提升就自动停止能省不少时间。如果显存不够把batch降到16或8配合ampTrue混合精度基本可以覆盖大多数消费级显卡。3.3 训练监控与结果评估训练日志里最需要关注的是top1_acc、top5_acc和loss曲线。小数据集上训练集准确率很快会冲到95%以上验证集如果跟不上基本就是过拟合需要加强数据增强或减少模型复杂度。我实测的时候YOLO11n在1000张图、训练100个epoch后验证集top1准确率大约在91%左右虽然不算高但已经能覆盖“焦边”和“正常”这两个占大头的类别。训练结束后Ultralytics会在runs/classify/cookie_cls/weights/下生成best.pt和last.pt。best.pt是按验证集指标保存的最优权重部署时直接用这个。想要详细看每个类别的精确率和召回率可以用内置的验证接口yolo classify val modelruns/classify/cookie_cls/weights/best.pt datadatasets/cookie_cls它会输出混淆矩阵相关的图表。我第一版模型最大的混淆发生在“cracked”和“chipped”之间因为有些裂纹很长拍出来确实像边缘缺损。后来我重新审视了训练数据把大量带有“复合缺陷”的图片单独处理并增加了几十张边界模糊的样本混淆情况才明显好转。4. 常见问题与踩坑实录4.1 类别不平衡导致过拟合1000张图看起来不多但如果正常类有600张、焦边只有80张就是明显的类别不平衡。我在第一版训练里吃过这个亏模型整体准确率92%但焦边类几乎全被预测成正常因为模型“偷懒”把所有不确定样本都归到数量多的类了。解决办法有三个层面。第一优先调整采集策略尽量让每个类别都至少有150张以上训练图第二使用WeightedRandomSampler按类别数量的倒数给少数类样本更高的抽样概率代码量不大第三对少数类做更强的数据增强比如焦边图多做一些色温变化、亮度变化让模型更关注“焦”的颜色特征而不是其他杂讯。训练时也可以开启类别权重参数不过Ultralytics分类训练默认不支持直接传class weight所以我更推荐在数据划分阶段就尽量均衡。如果实在没办法补齐真实样本至少保证每个类别在验证集里都有一定数量否则测试指标会虚高。4.2 尺寸、缓存与数据加载的坑YOLO11cls默认会读取数据目录下的图片并按目录分类听起来很简单但坑也不少。首先是图片尺寸不一致有些大图是4000x3000YOLO训练时resize的效率会特别低。我写脚本统一先缩放到256x256再做中心裁剪训练速度提升明显。其次是隐藏文件问题。MacOS会在文件夹里生成.DS_StoreWindows也可能有Thumbs.db如果这些文件混进图片文件夹会出现加载报错或类别数量异常。建议在check_dataset函数里过滤掉以.开头的文件。还有就是cache参数。Ultralytics支持cacheTrue把图片缓存到显存或内存里能显著加快训练。但1000张图全部缓存到显存可能超显存特别是开了大batch之后。我的建议是如果显存紧张不开cache如果内存很大设置cacheram比cachedisk更快。4.3 显存不足与训练速度优化训练过程中最常见的错误就是CUDA out of memory。我刚开始用imgsz224、batch64训练直接在6GB显存的卡上崩了。后来把batch降到32开amp混合精度问题解决。如果batch降到8还不够可以用梯度累积相当于增大有效batch但不会额外占用显存。另一个拖慢训练的因素是数据加载。图片小、文件多的时候CPU可能来不及喂数据。workers参数默认是8可以调高到16或32。但要注意有些Windows电脑上workers过高会报错建议在Linux环境训练。还有一个小技巧先用小模型yolo11n-cls.pt调通整个流程包括数据划分、训练、验证、导出然后再试yolo11s-cls.pt或yolo11m-cls.pt。这样调试期不会浪费太多时间在等待上。4.4 模型导出与推理部署训练完不是终点模型要能够部署才有价值。Ultralytics支持把PyTorch权重导出为ONNX、TensorRT等格式yolo classify export modelruns/classify/cookie_cls/weights/best.pt formatonnx imgsz224导出ONNX后可以用ONNX Runtime或者TensorRT做推理。这张分类模型的推理代码非常简单用Ultralytics的Python接口可以直接跑from ultralytics import YOLO model YOLO(runs/classify/cookie_cls/weights/best.pt) results model.predict(test_image.jpg, imgsz224) top1_idx results[0].probs.top1 top1_conf results[0].probs.top1conf class_names model.names print(f预测类别: {class_names[top1_idx]}, 置信度: {top1_conf:.4f})部署时特别要注意推理尺寸必须和训练尺寸一致否则会掉点。另外实际产线的光照和摄像头角度跟训练数据差距很大建议在部署前再做一次“现场数据回灌测试”拿一小部分产线实时拍摄的图片验证模型表现而不是只盯着测试集指标看。最后再分享一个我自己的习惯每次训练完除了看准确率我一定会把测试集里那些“预测错误”的图片单独打印成拼图一张一张看。很多问题是靠肉眼看图才能发现的比如某类样本标签错了、某个批次图片偏暗、某个缺陷形态在数据里压根没出现过。这个小动作看着原始但比换模型、调参更管用。如果你也准备做类似的小样本食物或工业品缺陷分类强烈建议先按“数据清洗—目录整理—YOLO11cls跑通—错误样本回看”这条流程走一遍跑的越完整后面踩的坑就越少。本文还有配套的精品资源点击获取